我正在阅读“实践中的Java并发性”一书,以详细了解Java并发性。然后我遇到了一个叫做“陈旧数据”的术语。
书中说:程序同步不足可能会引起意外
结果->过时的数据。
书中提供了一个示例,该示例使用'synchronized'关键字保护mutator方法,并在其字段上使用'@GuardedBy'注释。我想测试一下。
import net.jcip.annotations.*;
public class ThreadTest4 extends Thread{
private MyWork4 myWork= new MyWork4();
public static void main(String[] args){
ThreadTest4 thread1 = new ThreadTest4();
ThreadTest4 thread2 = new ThreadTest4();
ThreadTest4 thread3 = new ThreadTest4();
ThreadTest4 thread4 = new ThreadTest4();
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
public void run(){
myWork.setA();
System.out.println(myWork.getA());
}
}
class MyWork4{
@GuardedBy("this") private static int a;
public synchronized int getA(){
return a;
}
public synchronized void setA(){
a++;
}
}
但是它的结果仍然让我感到惊讶!可能是什么原因?
最佳答案
你有两个问题。
首先,您要锁定的myWork
对象是每个线程的私有对象,因此线程仅在调用setA()
试图修改静态int时才相互锁定,这需要锁定FIRST OBJECT in为了允许值更改。因此,除了Thread1之外,没有getA()
调用依赖于等待锁。
由此产生的第二个问题是您的set和get调用重叠。 @GuardedBy
防止该项目被不带锁的项目修改,并且同步方法只能由拥有该锁的调用方调用。所有线程都注册其setA()
调用,但必须等待锁修改值。 Thread1从锁开始,然后修改该值,然后释放该锁,然后通过其getA()
调用再次请求它。
然后,当Thread1释放锁时,将执行Thread2的setA()
调用。线程2在增加值后释放锁,然后使用自己的getA()
调用注册其对锁的请求。
Thread1现在重新获得锁以执行其等待的getA()
调用,并打印出2
,因为此时Thread1和Thread2已经修改了该值。
Thread3接下来获取锁并执行其等待的setA()
调用,然后再次增大该值并释放该锁,然后注册其getA()
调用。
然后,线程2将锁取回以等待其等待的getA()
调用,并打印出3
并释放该锁。
线程3的等待getA()
调用接下来发生,并再次打印出3
,因为还没有发生其他setter调用。
最后,最先启动的Thread4开始运行,并注册递增值的setA()
调用,然后打印出新递增的getA()
调用,因为它不再等待锁。
您的run方法是不同步的,并且您的各个线程没有什么要订购的,除非首先请求锁定,这取决于足够多的不同因素,基本上是随机的。
这是一个修改,可以使您的订单更加可预测:
public class ThreadTest4 extends Thread {
static Object lock = new Object[0];
private MyWork4 myWork = new MyWork4();
public static void main(String[] args) {
ThreadTest4 thread1 = new ThreadTest4();
ThreadTest4 thread2 = new ThreadTest4();
ThreadTest4 thread3 = new ThreadTest4();
ThreadTest4 thread4 = new ThreadTest4();
thread1.start();
thread2.start();
thread3.start();
thread4.start();
}
public void run() {
synchronized(lock){
myWork.setA();
System.out.println(myWork.getA());
}
}
}
class MyWork4 {
@GuardedBy("lock")
private static int a;
public synchronized int getA() {
return a;
}
public synchronized void setA() {
a++;
}
}
这是可行的,因为锁是外部的,并且在线程之间显式共享。所有线程都使用相同的锁,因此它们在下一个线程被赋予锁之前按顺序执行
setA()
调用和getA()
调用,这使它们发挥得更好。