前三篇我们主要说了多线程访问共享可变状态时需要进行正确的同步处理,保证同一时刻只有一个线程访问相同的数据,我们使用synchronized关键字来实现原子性操作。
今天我们在来认识一下同步的另一个重要方面:内存可见性,这个概念其实很好理解,就是保证在同一个时刻,共享可变状态对访问它的线程呈现出自己最新的状态变化。
我们经常遇到的情景是这样的,一个全局变量计数器,一个线程负责更新该数值,另一些线程获取这个值,那么可见性就是获取值的线程,可以获取到更新线程更新的最新的值。
让我们先来看一个例子,在没有做同步的情况下会发生什么。
package com.home.thread;
/**
* @author gaoxu
*
*/
public class ThreadStart {
static boolean ready ;
static int number;
private static class ReaderThread extends Thread{
public void run(){
number = 1;
}
}
private static class ReaderThread1 extends Thread{
public void run(){
if(ready){System.out.println("="+number);}
}
}
public static void main(String[] para){
new ReaderThread().start();
ready = true;
new ReaderThread1().start();
}
}
以上这段代码的运行结果可能会呈现出以下的结果:
第一种:
=0
第二种
=1
这其中只有第二种是我们想要看到的结果。
我们把第一种情况现象叫做“重排序”,重排序现象往往会产生无效数据,第一行情况就是一个无效数据,解决这种现象就必须采用同步加锁机制。(当然还有volatile变量,以后我们会重点说一下)
下面我们看一下如何实现一个线程安全的可变整数类。
package com.home.thread;
/**
* @author gaoxu
*
*/
@safe
public class SafeThread {
static int number;
@safe
public static synchronized int getNumber() {
return number;
}
@safe
public static synchronized void setNumber(int number) {
SafeThread.number = number;
}
}
package com.home.thread;
/**
* @author gaoxu
*
*/
public class ThreadStart {
private static class ReaderThread extends Thread{
public void run(){
SafeThread.setNumber(1);
}
}
private static class ReaderThread1 extends Thread{
public void run(){
System.out.println("="+SafeThread.getNumber());
}
}
public static void main(String[] para){
new ReaderThread().start();
new ReaderThread1().start();
}
}
加锁后我们会看到输出结果永远是 1了。
让我们总结一下加锁同步的含义:加锁的含义不仅仅局限于原子操作行为,还可以保证内存可见性。当然最重要的一点是多个读写线程必须是保证都在同一个锁上同步的操作可变状态变量。
我们现在了解完了同步的主要特性,互斥性和可见性。可是我们知道同步对性能的影响是非常大的,那么我们该怎么样平衡呢?