Java多线程——深入理解"脏读"
脏读:某线程取到的数据是被其他线程所修改过的。
在Java中,若没有使用加锁操作,所有的线程之间是异步执行的,因此就会产生"脏读"导致数据的丢失或错误。
首先来看根本没有任何加锁操作的情况。
class MyThread implements Runnable{
private int num = 5;
@Override
public void run() {
showNum();
}
public void showNum(){
while (num>0){
num--;
System.out.println(Thread.currentThread().getName()+":"+num);
}
}
}
public class DirtyReadTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
new Thread(myThread,"A").start();
new Thread(myThread,"B").start();
}
}
// 执行结果
B:3
A:3
A:2
A:1
A:0
程序解读:从这个执行结果可以看出,A:3并且B:3,同一个3出现了两次,明显与逻辑不符,出现了"脏读"。
此时需要给该方法加上synchronized关键字,从而保证一次只会有一个线程进入该方法,保证同步即线程安全。
public synchronized void showNum(){
while (num>0){
num--;
System.out.println(Thread.currentThread().getName()+":"+num);
}
}
// 执行结果
A:4
A:3
A:2
A:1
A:0
程序解读:此时线程A始终拥有该对象的锁,由于线程A和线程B都是在调用对象MyThread,因此A反复进行打印知道num=-1,而当A释放锁后,B获取到锁,此时第一次判断num>0的结果就是false,while循环不会进入,程序执行结束。
现在就产生一个疑惑,有了synchronized关键字修饰真的就完全保证了同步吗?
实际上与程序是如何编写的有很大的关系,举个栗子:
class PublicVar{
private String username = "A";
private String password = "AA";
public synchronized 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);
}
}
class ThreadA extends Thread{
PublicVar publicVar = null;
public ThreadA(PublicVar publicVar) {
this.publicVar = publicVar;
}
@Override
public void run() {
publicVar.setValue("B","BB");
}
}
public class DirtyReadTest2 {
public static void main(String[] args) {
try {
PublicVar publicVar = new PublicVar();
ThreadA threadA = new ThreadA(publicVar);
threadA.start();
// 程序的结果与这个睡眠时间相关
// 此处的睡眠时间>setValue()中的睡眠时间:
等设置完值后才取得值,不会出现脏读。
// 此处的睡眠时间 |