同步不仅指原子性,还包括内存可见性。我们不仅防止某个线程正在使用对象状态而另一个线程在同时修改该状态。还希望确保当一个线程修改对象状态后,其他线程能够看到发生的状态变化。
当多个线程同时操纵一个可变对象时,对象状态的可见性就暴露出来了。
package com.buff.visibility;
public class NoVisibility {
private static boolean ready = false;
private static int number;
private static class ReaderThread extends Thread{
public void run(){
System.out.println("run start");
while(!ready){
Thread.yield();
System.out.println(number);
}
System.out.println("run stop");
}
}
public static void main(String[] args) {
new ReaderThread().start();
number = 41;
ready = true;
}
}
可能出现的情况:
1、无限循环下去:ReaderThread线程在启动读取的变量为ready=false,而后ready=true的更新却一次没有被子线程看到。
2、输出0:即number=41在主线程中进行变量更新后,ReaderThread却没有看到更新结果,输出的却是整数的默认值0。
3、不进入循环体:即线程启动后执行run方法时,ready的值已被更新为true。(本人测试出的该情况)
针对以上2、3的情况,是由于重排序(Reordering)造成的。
加锁行为不仅局限于互斥行为,还包括内存可见性。
以下定义的类不是线程安全的
package com.buff.mutableInteger;
public class MutableInteger {
private int value;
public int getValue() {
return value;
}
public void setValue(int value) {
this.value = value;
}
}
测试类
package com.buff.mutableInteger;
public class ClientThread extends Thread {
private MutableInteger mi;
public ClientThread(MutableInteger mi) {
this.mi = mi;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("1[" + Thread.currentThread().getName() + "]" + mi.getValue());
mi.setValue(mi.getValue() + 1);
}
}
public static void main(String[] args) {
MutableInteger mi = new MutableInteger();
new ClientThread(mi).start();
// mi.setValue(10);
new ClientThread(mi).start();
}
}
运行结果:
1[Thread-0]0
1[Thread-0]1
1[Thread-1]1
1[Thread-1]2
1[Thread-1]3
1[Thread-1]4
1[Thread-1]5
1[Thread-0]7
1[Thread-0]8
1[Thread-0]9
按照书中修改的方式,将set和get方法同步后
package com.buff.mutableInteger;
public class ThreadSafeMutableInteger {
private int value;
public synchronized int getValue() {
return value;
}
public synchronized void setValue(int value) {
this.value = value;
}
}
测试类
package com.buff.mutableInteger;
public class ClientThread2 extends Thread {
private ThreadSafeMutableInteger ts;
public ClientThread2(ThreadSafeMutableInteger ts) {
this.ts = ts;
}
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("2[" + Thread.currentThread().getName() + "]" + ts.getValue());
ts.setValue(ts.getValue() + 1);
}
}
public static void main(String[] args) {
ThreadSafeMutableInteger mi = new ThreadSafeMutableInteger();
new ClientThread2(mi).start();
// mi.setValue(10);
new ClientThread2(mi).start();
}
}
运行结果:
2[Thread-0]0
2[Thread-1]0
2[Thread-1]2
2[Thread-1]3
2[Thread-1]4
2[Thread-1]5
2[Thread-0]1
2[Thread-0]7
2[Thread-0]8
2[Thread-0]9
可以看出这样同步还是有问题,其实作者在后边解释了同步的一致性。正确的方式如下:
package com.buff.mutableInteger;
public class ClientThread2 extends Thread {
private ThreadSafeMutableInteger ts;
public ClientThread2(ThreadSafeMutableInteger ts) {
this.ts = ts;
}
public void run() {
for (int i = 0; i < 5; i++) {
synchronized (ts) {
System.out.println("2[" + Thread.currentThread().getName() + "]" + ts.getValue());
ts.setValue(ts.getValue() + 1);
}
}
}
public static void main(String[] args) {
ThreadSafeMutableInteger mi = new ThreadSafeMutableInteger();
new ClientThread2(mi).start();
// mi.setValue(10);
new ClientThread2(mi).start();
}
}
运行结果:
2[Thread-0]0
2[Thread-0]1
2[Thread-0]2
2[Thread-0]3
2[Thread-0]4
2[Thread-1]5
2[Thread-1]6
2[Thread-1]7
2[Thread-1]8
2[Thread-1]9
通过运行结果可以看出这是顺序执行的,在性能上应该是和单线程一样的。原因是我们同步块是在run方法,而且基本包括了所有的run方法代码块。这种方式set和get方法也可以不同步
其实同步是在发生读写冲突的地方才进行的,如果分别对读写进行分离同步,则达不到同步的效果
网友评论