标签:
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