多线程之——共享数据
数据不一致
让我们先来看一个案例
public class ShareData { //共享数据data static int data = 0; public static void main(String[] args) { //创建两个线程并启动 ShareThread1 st1 = new ShareThread1(); ShareThread2 st2 = new ShareThread2(); new Thread(st1).start(); new Thread(st2).start(); } //内部类,访问类中的静态成员变量data private static class ShareThread1 implements Runnable{ //线程1中的run()方法循环输出data,每次循环休眠1秒 @Override public void run() { while(data<10){ try { Thread.sleep(1000); System.out.println("这个小于10的数据是:"+data++); } catch (InterruptedException e) { e.printStackTrace(); } } } } //内部类,访问类中的静态成员变量data private static class ShareThread2 implements Runnable{ //线程2中的run()方法变量data自增 @Override public void run() { while(data<100){ data++; } } } }
我们看到:ShareDate 类中有两个内部类ShareThread1 和ShareThread2类,这两个内部类都共享并访问ShareDate 类中静态成员变量data 。其中ShareThread1类中的run()方法判断:当data 小于10时进行输出,不过在输出前通过调用sleep()方法让该线程等待1秒。而ShareThread2类中的run()方法让data 循环执行自加的操作,直到data 不小于100时停止。
输出结果:
这个小于10的数据是:100
很明显,这并不是我们所希望的结果。出现这样结果的原因是:一开始,当ShareThread1类的对象在判断data <10之时,data 的值是小于10的,所以能进去到run ()方法中的while循环中。但是当进入while 循环后,在输出前需要等待1秒,在这1秒里面,ShareThread2类的线程对象通过run()方法不停地进行data 自加操作,直到data=100停止。此时,ShareThread1类的线程对象再进行输出操作,其结果自然是这个“小于10的data 数据“就变成了100。
这个案例说明了:当一个数据被多个线程进行读取操作时,通过检查这个数据的值来进行判断并执行之后的操作是极其不安全的。因为在判断之后,这个数据的值很可能已经被其他线程修改了,判断条件也可能已经不成立了,但此时已经过了判断,之后的操作也就“将错就错”地继续进行。
控制共享数据
针对上面的案例中——共享数据data 被不同的线程操作出现了数据不一致的情况,Java 提供了同步机制来解决控制共享数据的问题,Java可以使用synchronized 关键字确保数据在各个线程间共享数据的正确分享。使用synchronized 关键字修改上面案例的代码
public class ShareData2 { //共享数据data static int data = 0; //定义了一个锁对象lock static final Object lock = new Object(); public static void main(String[] args) { //创建两个线程并启动 ShareThread1 st1 = new ShareThread1(); ShareThread2 st2 = new ShareThread2(); new Thread(st1).start(); new Thread(st2).start(); } //内部类,访问类中的静态成员变量data private static class ShareThread1 implements Runnable{ //ShareThread1类中的run()方法循环输出data,每次循环休眠1秒 @Override public void run() { //对lock对象上锁 synchronized (lock) { while(data<10){ try { Thread.sleep(1000); System.out.println("这个小于10的数据是:"+data++); } catch (InterruptedException e) { e.printStackTrace(); } } } } } //内部类,访问类中的静态成员变量data private static class ShareThread2 implements Runnable{ //ShareThread2类中的run()方法变量data自增 @Override public void run() { //对lock对象上锁 synchronized (lock) { while(data<100){ data++; } } } } }
运行结果:
这个小于10的数据是:0 这个小于10的数据是:1 这个小于10的数据是:2 这个小于10的数据是:3 这个小于10的数据是:4 这个小于10的数据是:5 这个小于10的数据是:6 这个小于10的数据是:7 这个小于10的数据是:8 这个小于10的数据是:9
在上面这个程序中,首先定义了一个静态变量lock ,然后在ShareThread1和ShareThread2类的run()方法里,使用synchronized (lock){…}代码对lock 对象“上锁”,其含义是:一旦一个线程执行synchronized (lock){…}代码块,则“锁住”lock 对象,其他针对lock 对象上锁的synchronized (lock){…}代码块将不允许被执行,直到之前运行的代码块运行结束,释放lock 对象锁后其他代码块才允许执行。