2.1.6 脏读与解决
在多个线程调用同一个方法时,为了避免数据出现交叉的情况,使用synchronized关键字来进行同步。虽然在赋值时进行了同步,但在取值时有可能出现一些意想不到的情况,这种情况就是脏读(dirty read)。发生脏读的原因是在读取实例变量时,此值已经被其他线程更改过了。
创建t3项目,PublicVar.java文件代码如下:
package entity; public class PublicVar { public String username = "A"; public String password = "AA"; synchronized public void setValue(String username, String password) { try { this.username = username; Thread.sleep(5000); this.password = password; System.out.println("setValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); } catch (InterruptedException e) { e.printStackTrace(); } } public void getValue() { System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); } }
同步方法setValue()的锁属于类PublicVar的实例。
创建线程类ThreadA.java的代码如下:
package extthread; import entity.PublicVar; public class ThreadA extends Thread { private PublicVar publicVar; public ThreadA(PublicVar publicVar) { super(); this.publicVar = publicVar; } @Override public void run() { super.run(); publicVar.setValue("B", "BB"); } }
文件Test.java代码如下:
package test; import entity.PublicVar; import extthread.ThreadA; public class Test { public static void main(String[] args) { try { PublicVar publicVarRef = new PublicVar(); ThreadA thread = new ThreadA(publicVarRef); thread.start(); Thread.sleep(200); // 输出结果受此值大小影响 publicVarRef.getValue(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
程序运行结果如图2-11所示。
图2-11 出现脏读情况
出现脏读是因为public void getValue()方法并不是同步的,所以可以在任意时候进行调用,解决办法是加上同步synchronized关键字,代码如下:
synchronized public void getValue() { System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); }
程序运行结果如图2-12所示。
图2-12 不出现脏读了
方法setValue()和getValue()被依次执行,通过这个示例不仅要知道脏读是通过synchronized关键字解决的,还要知道如下内容。
当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕后才可以调用X方法,但B线程可以随意调用其他的非synchronized同步方法。
当A线程调用anyObject对象加入synchronized关键字的X方法时,A线程就获得了X方法所在对象的锁,所以其他线程必须等A线程执行完毕后才可以调用X方法,而B线程如果调用声明了synchronized关键字的非X方法时,必须等A线程将X方法执行完,也就是将对象锁释放后才可以调用,这时A线程已经执行了一个完整的任务,也就是说username和password这两个实例变量已经同时被赋值,不存在脏读的基本环境。
多个线程在调用同一个业务对象中不同的同步方法时,是按顺序同步的方式调用的。
脏读前一定会出现不同线程一起去写实例变量的情况,这就是不同线程“争抢”实例变量的结果。