目录
1 多线程下变量的不可见性及解决方案
2 不可见性解决方案
2.1 加锁方式解决
2.2 使用volatile关键字
2.3 while(true)速度快调用本地线程内存,加延时即可访问主内存的共享变量
3 volatile不保证原子性以及解决方案
3.1 案例 volatile 验证不是原子性
3.2 讲解count++和count-- 不是原子操作
3.3 解决volatile 原子性问题
4 volatile 禁止指令重排序
4 实战(懒汉式 双重检查+volatile )
5 场景(只适合纯赋值 )
总结
- 线程间的通讯有不可见性。
- volatile能使线程通讯可见,但是不保证原子性。(解决不了库存超卖)
- volatile 禁止指令重排序
1 多线程下变量的不可见性及解决方案
在多线程并发执行下,多个线程修改共享的成员变量,会出现一个线程修改共享变量后,另一个线程,不能直接看到线程修改后的变量的最新值。
案例 (不可见性)
个人见解流程分析:1 new volatileDemo();将成员变量flag = false;加载到内存中去。
2 开启线程,将 主内存中 flag = false; 读取到副本中去,并作修改 flag = true;
3 向主内存中刷新为 flag = true;
4 cup 去执行 main线程 ,将 主内存中 flag =true; 读取到main线程的 副本中去。打印日志,"主线程获取flag"
public class volatileDemo implements Runnable {
private boolean flag = false;
@Override
public void run() {
// 2子线程已经将flag变成true了
flag = true;
System.out.println(Thread.currentThread().getName()+"flag="+flag);
}
public boolean getFlag(){
return flag;
}
public static void main(String[] args) {
//同一个对象,同一变量 a 锁的是同一个对象,线程安全的
volatileDemo thread = new volatileDemo();
// 1 子线程开始执行
new Thread(thread).start();
while (true){
// 3 获取flag时已经由子线程将它变成 true了
if(thread.getFlag()){
System.out.println("主线程获取flag");
}
}
}
}
验证不可见性(仅仅加上一个延时)
个人见解流程分析:1 new volatileDemo();将成员变量flag = false;加载到内存中去。
2 开启子线程,由于延时一秒(放弃CPU的时间片),主内存中还是 flag = false; 并且让cpu去执行main线程。
3 main线程 将 主内存中 flag =false; 读取到main线程的 副本中去。
4 子线程将 flag = true; 并刷新到主内存中。
5 由于 while(true)速度比较快,只会读 main线程的副本,不会读主内存数据 。所以不会有打 印,"主线程获取flag"
JMM规定
- 所有的共享变量都存储于主内存中,这里说的变量不包含局部变量 ,因为局部变量是私有的,因此不存在竞争问题。
- 每一个线程还存在自己的工作内存,线程的工作内存,保留了共享变量的副本。
- 线程对变量的所有操作(读,取)都必须在工作内存中完成,而不能直接读写主内存中的变量。
- 不同线程间,也不能直接访问对方工作内存中的变量,线程间的变量的值的传递需要通过主内存中转完成。
- 总结 1.线程读取共享变量到自己的副本中
2.更改副本的值
3.刷新到主内存
2 不可见性解决方案
如何实现多线程间访问共享变量的可见性?
- 加锁
- 使用volatile关键字
- while(true)速度快调用本地线程内存,加延时即可访问主内存的共享变量
2.1 加锁方式解决
讲解:某一个线程进入到synchronized代码块中,执行过程如下:
- 线程获取锁
- 清空工作内存(本地内存)
- 从主内存拷贝共享变量最新值到工作内存中称为副本。
- 执行代码,将修改后的副本刷新会主内存中
- 线程释放锁。
2.2 使用volatile关键字
流程原理
2.3 while(true)速度快调用本地线程内存,加延时即可访问主内存的共享变量
3 volatile不保证原子性以及解决方案
3.1 案例 volatile 验证不是原子性
public class ThreadRunnable2 implements Runnable {
private volatile int a = 100;
@SneakyThrows @Override
public void run() {
while (true) {
if (a > 1) {
a--;
System.out.println(Thread.currentThread().getName() + "==" + a);
Thread.sleep(100);
}
}
}
public static void main(String[] args) {
ThreadRunnable2 thread = new ThreadRunnable2();
new Thread(thread).start();
new Thread(thread).start();
}
}
3.2 讲解count++和count-- 不是原子操作
3.3 解决volatile 原子性问题
加锁
使用原子类
AtomicBoolean | 可以用原子方式更新的 boolean 值。 |
AtomicInteger | 可以用原子方式更新的 int 值。 |
AtomicIntegerArray | 可以用原子方式更新其元素的 int 数组。 |
AtomicIntegerFieldUpdater<T> | 基于反射的实用工具,可以对指定类的指定 volatile int 字段进行原子更新 |
AtomicLong | 可以用原子方式更新的 long 值。 |
AtomicLongArray | 可以用原子方式更新其元素的 long 数组 |
AtomicLongFieldUpdater<T> | 基于反射的实用工具,可以对指定类的指定 volatile long 字段进行原子更新。 |
AtomicMarkableReference<V> | AtomicMarkableReference 维护带有标记位的对象引用,可以原子方式对其进行更新。 |
AtomicReference<V> | 可以用原子方式更新的对象引用。 |
AtomicReferenceArray<E> | 可以用原子方式更新其元素的对象引用数组。 |
AtomicReferenceFieldUpdater<T,V> | 基于反射的实用工具,可以对指定类的指定 volatile 字段进行原子更新 |
AtomicStampedReference<V> | AtomicStampedReference 维护带有整数"标志"的对象引用,可以用原子方式对其进行更新 |
public static void main(String[] args) {
//无参构造初始值为0 ,有参构造可以指定初始值
AtomicInteger atomicInteger = new AtomicInteger();
//获取当前值
int i = atomicInteger.get();
// 加 1操作 并返回前一个值
//例如:初始值是0 执行完getAndIncrement(),返回结果是 0 ,但是当前值是 1。
int andIncrement = atomicInteger.getAndIncrement();
// 减1操作 并返回前一个值
int andDecrement = atomicInteger.getAndDecrement();
// 加 20 并返回最终结果
//例如: 原值是5,返回结果将是25。
int i1 = atomicInteger.addAndGet(20);
// 设置新值 返回旧值
int andSet = atomicInteger.getAndSet(30);
}
4 volatile 禁止指令重排序
指令重排序:cpu为了提高我们的处理速度可能会对我们的代码指令重排序。但是在并发执行下,会有较小的概率出现和我们预期不符的结果。
案例(指令重排序)(执行了68万没有复现。就不打算执行了)
public class volatileDemo2 {
public static int a = 0, b = 0;
public static int i = 0, j = 0;
public static void main(String[] args) throws InterruptedException {
int count = 0;
while (true) {
count++;
Thread one = new Thread(() -> {
a = 1;
i = b;
}, "线程A");
Thread two = new Thread(() -> {
b = 1;
j = a;
}, "线程B");
one.start();
two.start();
one.join();
two.join();
String result = "第 " + count + "次" + " i= " + i + " , j= " + j;
System.out.println(result);
if (i == 0 && j == 0) {
break;
}
}
}
}
以上代码打印可的情况分析
解决办法
4 实战(懒汉式 双重检查+volatile )
public class volatileDemo3 {
//静态属性 ,volatile保证可见性和禁止指令重排
private volatile static volatileDemo3 instance = null;
//私有化构造器
private volatileDemo3() {
}
public static volatileDemo3 getInstance() {
//第一重检查
if (instance == null) {
synchronized (volatileDemo3.class) {
//第二重检查
if (instance == null) {
instance = new volatileDemo3();
}
}
}
return instance;
}
}
5 场景(只适合纯赋值 )
//纯赋值不会设计到原子操作 // flag = true; // 涉及到获取值和 向主内存刷新值,这都不是原子操作。 原理和count++一个道理。 flag = !flag;
public class volatileDemo4 implements Runnable {
private volatile boolean flag = false;
AtomicInteger atomicInteger = new AtomicInteger();
@SneakyThrows @Override
public void run() {
for (int i = 0; i < 1000; i++) {
//纯赋值不会设计到原子操作
// flag = true;
// 涉及到获取值和 向主内存刷新值,这都不是原子操作。 原理和count++一个道理。
flag = !flag;
atomicInteger.getAndIncrement();
}
}
public boolean getFlag(){
return flag;
}
public static void main(String[] args) throws InterruptedException {
volatileDemo4 thread = new volatileDemo4();
// 1 子线程开始执行
Thread thread1 = new Thread(thread);
Thread thread2 = new Thread(thread);
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(thread.getFlag()+"次数"+thread.atomicInteger);
}
}