Java多线程编程核心技术(第3版)
上QQ阅读APP看书,第一时间看更新

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这两个实例变量已经同时被赋值,不存在脏读的基本环境。

多个线程在调用同一个业务对象中不同的同步方法时,是按顺序同步的方式调用的。

脏读前一定会出现不同线程一起去写实例变量的情况,这就是不同线程“争抢”实例变量的结果。