标签:
Java培训、Android培训、iOS培训、.Net培训,期待您的交流
在Java多线程中,如果有多个线程同时操作共享数据时,就可能会发生数据异常
如下面这段代码:
/* * 模拟卖票 */ class Ticket implements Runnable { private int tick = 10; Object obj = new Object(); public void run() { while(true) { if(tick>0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--); } } } } class TicketDemo2 { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); t1.start(); t2.start(); t3.start(); t4.start(); } }
这段代码的运行结果:
Thread-2....sale : 10 Thread-0....sale : 9 Thread-1....sale : 8 Thread-3....sale : 7 Thread-2....sale : 6 Thread-0....sale : 5 Thread-1....sale : 4 Thread-3....sale : 3 Thread-2....sale : 2 Thread-0....sale : 1 Thread-3....sale : 0 Thread-2....sale : -1 Thread-1....sale : -2
通过结果可以看出,票数出现了负数,这就出现了数据异常,也就是程序中出现了“脏数据”。
这种“脏数据”产生的原因是在多线程同时操作共享数据时而产生的,若想避免“脏数据”,就得保证在一个线程操作共享数据时,其他线程不能够操作共享数据。
解决这种问题可以使用synchronied关键字来实现同步。
即在需要同步的代码上加上synchronizd关键字:
public void run() { while(true) { //此处加上synchronized关键字 synchronized (obj) { if(tick>0) { try{Thread.sleep(10);}catch(Exception e){} System.out.println(Thread.currentThread().getName()+"....sale : "+ tick--); } } } }
被synchronized关键字修饰的代码块叫做同步代码块。
它也可以修饰方法,代码如下:
public synchronized void run() { }
从上面的代码可以看出,只要在void和public之间加上synchronized关键字,就可以使run方法同步,也就是说,对于同一个Java类的对象实例,run方法同时只能被一个线程调用,并当前的run执行完后,才能被其他的线程调用。即使当前线程执行到了run方法中的yield方法,也只是暂停了一下。由于其他线程无法执行run方法,因此,最终还是会由当前的线程来继续执行。
也可以用来修饰静态方法:
class Test { public static synchronized void method() { } }
我们熟知的单例设计模式延迟加载方式是线程不安全的,代码如下:
// 线程不安全的Singleton模式 class Singleton { private static Singleton sample; private Singleton() { } public static Singleton getInstance() { if (sample == null) { Thread.yield(); // 为了放大Singleton模式的线程不安全性 sample = new Singleton(); } return sample; } } public class SingletonTest extends Thread { public void run() { Singleton singleton = Singleton.getInstance(); System.out.println(singleton.hashCode()); } public static void main(String[] args) { Thread threads[] = new Thread[5]; for (int i = 0; i < threads.length; i++) threads[i] = new SingletonTest(); for (int i = 0; i < threads.length; i++) threads[i].start(); } }
程序的运行结果如下:
1794515827 1167165921 1442002549 1701381926 1442002549
上面的运行结果可能在不同的运行环境上有所有同,但一般这五行输出不会完全相同。从这个输出结果可以看出,通过getInstance方法得到的对象实例是五个,而不是我们期望的一个。
这是因为当一个线程执行了Thread.yield()后,就将CPU资源交给了另外一个线程。 由于在线程之间切换时并未执行到创建Singleton对象实例的语句,因此,这几个线程都通过了if判断, 所以,就会产生了建立五个对象实例的情况(可能创建的是四个或三个对象实例,这取决于有多少个线程在创建Singleton对象之前通过了if判断,每次运行时可能结果会不一样)。要想使上面的单件模式变成线程安全的,只要为getInstance加上synchronized关键字即可。代码如下:
public static synchronized Singleton getInstance() { }
当然,还有更简单的方法,就是在定义Singleton变量时就建立Singleton对象,即我们俗称的“饱汉式”,代码如下:
private static final Singleton sample = new Singleton();
需要理解的是,无论此关键字修饰的是非静态方法还是静态方法,被锁定的不是这些方法,而是这些方法所在的对象或类。
使用synchronized关键字需要注意synchronized关键字的作用域:
1、是某个对象实例内,synchronized aMethod(){}可以防止多个线程同时访问这个对象的synchronized方法(如果一个对象有多个synchronized方法,只要一个线程访问了其中的一个synchronized方法,其它线程不能同时访问这个对象中任何一个synchronized方法)。这时,不同的对象实例的synchronized方法是不相干扰的。也就是说,其它线程照样可以同时访问相同类的另一个对象实例中的synchronized方法;
2、是某个类的范围,synchronized
static aStaticMethod{}防止多个线程同时访问这个类中的synchronized static 方法。它可以对类的所有对象实例起作用。
2、除了方法前用synchronized关键字,synchronized关键字还可以用于方法中的某个区块中,表示只对这个区块的资源实行互斥访问。用法是: synchronized(this){/*区块*/},它的作用域是当前对象;
3、synchronized关键字是不能继承的,也就是说,基类的方法synchronized f(){} 在继承类中并不自动是synchronized f(){},而是变成了f(){}。继承类需要你显式的指定它的某个方法为synchronized方法。
标签:
原文地址:http://www.cnblogs.com/alvis2015/p/4297888.html