说到多线程, 我觉得我们最重要的是要理解一个临界区概念.
举个例子, 一个班上 1 个女孩子(临界区),49 个男孩子(线程), 男孩子的目标就是这一个女孩子, 就是会有竞争关系(线程安全问题). 推广到实际场景, 例如对一个数相加或者相减等等情形, 因为操作对象就只有一个, 在多线程环境下, 就会产生线程安全问题. 理解临界区概念, 我们对多线程问题可以有一个好意识.
Jav 内存模型(JMM)
谈到多线程就应该了解一下 Java 内存模型 (JMM) 的抽象示意图. 下图:
线程 A 和线程 B 执行的是时候, 会去读取共享变量(临界区), 然后各自拷贝一份回到自己的本地内存, 执行后续操作.
JMM 模型是一种规范, 就像 Java 的接口一样. JMM 会涉及到三个问题: 原子性, 可见性, 有序性.
所谓原子性. 就是说一个线程的执行会不会被其他线程影响的. 他是不可中断的. 举个例子:
int i=1
这个语句在 Jmm 中就是原子性的. 无论是一个线程执行还是多个线程执行这个语句, 读出来的 i 就是等于 1. 那什么是非原子性呢, 按道理如果 Java 的代码都是原子性, 应该就不会有线程问题了啊. 其实 JMM 这是规定某些语句是原子性罢了. 举个非原子性例子:
i ++;
这个操作就不是原子性的了. 因为他就是包含了三个操作: 第一读取 i 的值, 第二将 i 加上 1, 第三将结果赋值回来给 i, 更新 i 的值.
所谓可见性. 可见性表示如果一个值在线程 A 修改了, 线程 B 就会马上知道这个结果.
所谓有序性. 所谓有序性值的是语意的有序性. 就是说代码顺序可能会发生变化. 因为有一个指令重排机制. 所谓指令重排, 他会改变代码执行顺序, 为了让 CPU 执行效率更高. 为了防止重排序出错, JMM 有个 happen-before 规则, 这个规则限制了那些语句执行在前, 那些语句执行在后.
Happen-before:
程序顺序原则: 一个线程内保证语义的串行性
volatile 原则: volatile 变量的写发生在读之前
锁规则: 先加锁再解锁
传递性: a 先于 b,b 先于 c, 则 a 必定先于 c
线程的 start 方法先于他的每一个操作
线程所有的操作先于线程的终结
对象的构造函数执行, 结束先于 finalize()方法.
volatile
进入正题, volatile 可以保证变量 (临界区) 的可见性以及有序性, 但是不能保证原子性. 举个例子:
- public class VolatileTest implements Runnable{
- private static VolatileTest volatileTest = new VolatileTest();
- private static volatile int i= 0;
- public static void main(String[] args) throws InterruptedException {
- for (int j = 0; j < 20; j++) {
- Thread a = new Thread(new VolatileTest());
- Thread b = new Thread(new VolatileTest());
- a.start();b.start();
- a.join();b.join();
- System.out.print(i+"&&");
- }
- }
- @Override
- public void run() {
- for (int j = 0; j < 1000; j++) {
- i++;
- }
- }
- }
- // 输出结果
- // 2000&&4000&&5852&&7852&&9852&&11852&&13655&&15655&&17655&&19655&&21306
- //&&22566&&24566&&26189&&28189&&30189&&32189&&34189&&36189&&38089&&
有结果看到有问题, 虽然 i 已经添加了 volatile 关键字, 说明 volatile 关键字不能保证 i++ 的原子性.
那什么场景适合使用 volatile 关键字
轻量级的 "读 - 写锁" 策略
- private volatile int value;
- public int getValue(){
- return value;
- }
- public synchronized void doubleValue(){
- value = value*value;
- }
2. 单例模式(双检查锁机制
- private volatile static Singleton instace;
- public static Singleton getInstance(){ // 没有使用同步方法, 而是同步方法块
- // 第一次 null 检查 , 利用 volatile 的线程间可见性, 不需要加锁, 性能提高
- if(instance == null){
- synchronized(Singleton.class) { // 锁住类对象, 阻塞其他线程
- // 第二次 null 检查, 以保证不会创建重复的实例
- if(instance == null){
- instance = new Singleton(); // 禁止重排序
- }
- }
- }
- return instance;
参考
《现代操作系统 (第三版) 中文版》
《实战 Java 高并发程序设计》
《Java 并发编程的艺术》
来源: https://www.cnblogs.com/chenzhuantou/p/11932915.html