java多线程开发中,控制共享数据比较麻烦,有可见性和同步性。一般控制可见性我们可以通过synchronized和volatile控制,而同步性我们只能通过synchronized或Lock来控制。
我喜欢通过对一个问题的理解,来理解某个知识点,因为我觉得知识就是为了解决问题。
public class SynchronizedTest { private static boolean flag = true; public static void main(String[] args) throws Exception{ new Thread(new Runnable() { @Override public void run() { while(flag) { } System.out.println("子线程执行结束================"); } }).start(); Thread.sleep(1000); flag = false; // 关闭线程输出 System.out.println("flag已被修改为false"); }}
上面的代码在64位JVM机器(准确来说应该是jvm的server模式)上执行一般都不会输出“子线程执行结束================”,而在32位的jvm的client模式下却可以输出“子线程执行结束================”。
通过 java -version 可以查看自己本机jvm运行的模式。
注意:如果你的jvm装的是64位,那么是无法切换到client模式的。
上面的问题引起的原因其实就是由于java的内存模型引起的,java内存模型中分有主内存和工作内存之分,主内存可以理解为共享数据的区域(不知道准确不准确),而工作内存(主内存数据的副本)是每个线程私有的一块区域,每个线程对共享数据的修改,不会直接操作共享数据,一般是先修改工作内存中的数据,然后在某个特定的时候刷新到主存,其他线程才有机会看到其修改。
解决以上问题
- 可以将flag前加上volatile关键字。
private static volatile boolean flag = true;
原因:可以这么简单理解,加了volatile关键字的共享变量,所有线程对其值的获取或修改都直接通过主存,绕过来工作内存,所以主线程对flag的修改子线程马上就可以看到。
volatile的原理就是加内存屏障,所有的读都在写之后,这样保证了子线程对flag的访问在主线程对flag的修改之后。
- 可以使用synchronized
new Thread(new Runnable() { @Override public void run() { int count = 0; while(flag) { synchronized(SynchronizedTest.class){ //这里可以锁任何共享对象 count++; } } System.out.println("子线程执行结束================"); }}).start();
上面之所以加上 count ,是防止JIT优化将无用的锁代码块优化掉。
原因:由于jvm规定在进入synchronized之前会将所有其他线程的工作内存刷新到主存(这个我不太确定是否正确,如果有确定的请留言告诉我,谢谢),在离开synchronized块之前会将本线程的工作内存刷新到主内存。
在验证这个之中我踩过一个坑,我的代码如下:
new Thread(new Runnable() { @Override public void run() { while(flag) { System.out.println("====falg====="); } System.out.println("子线程执行结束================"); }}).start();
这样在任何情况下都可以输出"子线程执行结束================",找了很久才发现原来 System.out.println 方法中有synchronized同步块导致。
这个方法来自 System.out
public void println(String x) { synchronized (this) { print(x); newLine(); } }
之中有不少观点是我自己的观点,可能不准确,或是错误的,希望你们能给我指出,谢谢。