我正在关注这本书' 实践中的Java并发'详细了解Java Concurrency。然后我遇到了一个名为“陈旧数据'
的术语。书中说:不充分同步的程序可能会导致意外 结果 - >陈旧数据。
本书中给出了一个例子,它使用' 同步'来保护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++;
}
}
但结果却让我感到惊讶!!可能是什么原因?
答案 0 :(得分:3)
你有两个问题。
首先,您锁定的myWork
对象对每个线程都是私有的,因此在调用setA()
时,线程只会互相锁定,这会尝试修改静态int,这需要锁定FIRST OBJECT以允许值更改。因此,除了Thread1之外,getA()
调用都不依赖于等待锁定。
由此产生的第二个问题是你的set和get调用是重叠的。 @GuardedBy
阻止项目被没有锁定的项目修改,并且同步方法只能由拥有锁定的调用者调用。所有线程都注册了setA()
个调用,但必须等待锁修改该值。 Thread1以锁定开始,修改该值,然后释放锁定,然后通过getA()
调用再次请求锁定。
然后,当Thread1释放锁时,执行Thread2的setA()
调用。 Thread2在递增值后释放锁定,然后使用自己的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()
调用,这样可以让它们更好地播放。