概述

java工作内存和主内存模型

在多线程中,多个线程访问主存中的临界资源(共享变量)时,需要首先从主存中拷贝一份共享变量的值到自己的工作内存中,然后在线程中每次访问该变量时都是访问的线程工作内存(高速缓存)中的共享的变量副本,而不是每次都去主存读取共享变量的值(因为CPU的读写速率和主存读写速率相差很大,如果CPU每次都访问主存的话那么效率会非常低)。

java线程变量加载的大致流程是,将主内存的变量加载到工作内存进行处理,处理完毕后写会主内存。

java 本地缓存并定时刷新 java本地缓存同步_java

工作内存和主内存数据交换时机

先看下如下代码,主线程运行时开启另一个线程,设置flag为true;但是发现主线程一直都未结束。
这个是大家在学习线程间可见性时,经常会遇到的例子,通常的解决办法是给flag,加上volatile关键字,保证变量的可见性;
对于线程安全问题,很多时候都不是必须的,但有一个奇怪的现象是,下面的程序每次运行都是必现,这又是什么原因导致的呢?

public class MemorySync {
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo threadDemo = new ThreadDemo();
        new Thread(threadDemo, "t1").start();
        while (true) {
            if (threadDemo.getFlag()) {
                System.out.println("end task");
                break;
            }
        }
    }
}

class ThreadDemo implements Runnable {

    private boolean flag = false;

    @SneakyThrows
    @Override
    public void run() {
        TimeUnit.SECONDS.sleep(1);
        flag = true;
        System.out.println("set flag:" + flag);
    }

    public boolean getFlag() {
        return flag;
    }
}

不可见原因分析

  • 为什么主线程没有对ThreadDemo线程的变量变更可见呢?
    答:从内存模型可知,主线程一直用工作内存的变量,没有重新加载主内存被ThreadDemo线程改变的变量。
  • 为什么主线程一直没有重新加载主内存的变量呢?
    答:只有工作内存失效的时候,工作内存才会重新加载主内存的变量。

工作内存什么时候同步主内存的变量

  • 线程中释放锁,线程自己同步主存变量。
    当前线程释放锁后,当前线程会去加载主存变量。
  • 线程上下文切换。
  • CPU有空闲时间时(比如线程休眠、IO操作等)

上面代码工作内存重新加载主内存方案

  1. 加volatile关键字
class ThreadDemo implements Runnable {
    // 方案1:添加volatile关键字
    private volatile boolean flag = false;

    @SneakyThrows
    @Override
    public void run() {
        TimeUnit.SECONDS.sleep(1);
        flag = true;
        System.out.println("set flag:" + flag);
    }

    public boolean getFlag() {
        return flag;
    }
}
  1. main线程加休眠时间
public class MemorySync {
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo threadDemo = new ThreadDemo();
        new Thread(threadDemo, "t1").start();
        while (true) {
            if (threadDemo.getFlag()) {
                System.out.println("end task");
                break;
            }
            // 添加休眠操作,使CPU有空闲时间
            TimeUnit.SECONDS.sleep(1);
        }
    }
}
  1. IO操作等,比如File file = new File(“F://temp.txt”)
public class MemorySync {
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo threadDemo = new ThreadDemo();
        new Thread(threadDemo, "t1").start();
        while (true) {
            if (threadDemo.getFlag()) {
                System.out.println("end task");
                break;
            }
            // 添加io操作,让CPU有空闲时间
            File file = new File("F://text.txt");
        }
    }
}
  1. 线程释放锁
public class MemorySyncVolatile {
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo1 threadDemo = new ThreadDemo1();
        new Thread(threadDemo, "t1").start();
        while (true) {
        	// 线程释放锁
            synchronized (threadDemo) {
                if (threadDemo.getFlag()) {
                    System.out.println("end task");
                    break;
                }
            }

        }
        TimeUnit.SECONDS.sleep(5);
    }
}
// 或者
public class MemorySyncVolatile {
    public static void main(String[] args) throws InterruptedException {
        ThreadDemo1 threadDemo = new ThreadDemo1();
        new Thread(threadDemo, "t1").start();
        Lock lock = new ReentrantLock();
        while (true) {
            lock.lock();
            if (threadDemo.getFlag()) {
                System.out.println("end task");
                break;
            }
            // 释放锁
            lock.unlock();
        }
        TimeUnit.SECONDS.sleep(5);
    }
}

参考

线程的工作内存与主内存同步时机浅析JMM线程工作内存什么时候读取主存变量