脏读:对于对象的同步和异步的方法,我们在设计自己的程序的时候,一定要考虑问题的整体,不然就会出现数据不一致的错误,很经典的错误就是脏读(dirtyread)
我们首先通过一个小demo来认识一下脏读:
public class DirtyRead {
private String username = "zjkj";
private String password = "123";
public synchronized void setValue(String username, String password) {
this.username = username;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.password = password;
System.out.println("setValue最终结果:username = " + username +
" , password = " + password);
}
public void getValue() {
System.out.println("getValue最终结果:username = " + username +
" , password = " + password);
}
public static void main(String[] args) throws InterruptedException {
final DirtyRead dr = new DirtyRead();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
dr.setValue("z3", "456");
}
});
t1.start();
Thread.sleep(1000);
dr.getValue();
}
}
对于这段代码,其实我们想要的结果是:
setValue最终结果:username = z3 , password = 456
getValue最终结果:username = z3, password = 456
事实上,运行后的结果是:
getValue最终结果:username = z3 , password = 123
setValue最终结果:username = z3 , password = 456
这是因为t1线程运行到setValue方法中,sleep了2秒中,此时,username已经被赋值,然后在主线程中,由于只sleep了1秒中,此时开始执行getValue方法,所以先打印的会是getValue,username = z3 , password = 123,这种就是存在线程安全的,也就是所谓的脏读。
为了解决这个问题,可以在getValue方法上也使用synchronized进行修饰,这里面,两次调用方法的是同一个对象,这样运行后的结果就是我们想要的结果。synchronized的定义中有提到过,当一个线程访问object的一个synchronized(this)同步代码块时,其他线程对object中所有其它synchronized(this)同步代码块的访问将被阻塞。从而保证线程是安全的,第一次出现错误的结果,是因为当一个线程访问object的一个synchronized(this)同步代码块时,另一个线程仍然可以访问该object中的非synchronized(this)同步代码块。
总结:在我们对一个对象的方法加锁的时候,需要考虑业务的整体性,即为setValue/getValue方法同时加锁synchronized同步关键字,保证业务(service)的原子性,不然会出现业务错误(也从侧面保证业务的一致性)。