文章目录
- JUC并发编程—— volatile 关键字详解
-
- 1、volatile 简介
- 2、可见性和非原子性验证
- 3、volatile与synchronized比较
JUC并发编程—— volatile 关键字详解 1、volatile 简介 并发编程三个特性:
1、原子性:一段操作一旦开始就会一直运行到底,中间不会被其它线程打断,这段操作可以是一个操作,也可以是多个操作。
2、可见性:当一个线程修改了共享变量的值,其它线程能立即感知到这种变化。
3、有序性:在本线程内观察,操作都是有序的;如果在一个线程中观察另外一个线程,所有的操作都是无序的。前半句是指“线程内表现为串行语义(WithIn Thread As-if-Serial Semantics)”,后半句是指“指令重排”现象和“工作内存和主内存同步延迟”现象。
volatile 是Java提供的一种轻量级的同步机制。
相比于synchronized(synchronized通常称为重量级锁),volatile更轻量级,因为它不会引起线程上下文的切换和调度。
volatile 特性:
- 保证了可见性,不保证原子性
- 禁止指令重排
指令重排是指在程序执行过程中, 为了性能考虑, 编译器和CPU可能会对指令重新排序.
- 当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中去;
- 这个写会操作会导致其他线程中的volatile变量缓存无效。
可见性验证假设A,B两个线程操作主存中的同一变量,当A线程修改了变量后,并把修改后的变量重新写入主存,此时B线程并不知道变量已被修改:
public class Demo01 {
private static int num = 0;
//共享变量
public static void main(String[] args) {//主线程 Anew Thread(()->{//副线程 B
while(num == 0){}
}).start();
try {
TimeUnit.SECONDS.sleep(3);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = 1;
// 修改num的值
System.out.println(num);
}
}
运行查看结果:
文章图片
当主线程A修改变量num=1后,副线程B并没有得到最新的num值,还是原来的num=0,所以副线程B陷入死循环。
当给变量num加上 volatile 关键字后:
private volatile static int num = 0;
再次运行查看结果:
文章图片
副线程B得到最新的num值,退出while循环。
当一个变量被volatile修饰后,表示着线程本地内存无效,当一个线程修改共享变量后他会立即被更新到主内存中,其他线程读取共享变量时,会直接从主内存中读取。
验证非原子性原子性是拒绝多线程操作的,不论是多核还是单核,具有原子性的量,同一时刻只能有一个线程来对它进行操作。简而言之,在整个操作过程中不会被线程调度器中断的操作,都可认为是原子性。例如 a=1是原子性操作,但是a++和a +=1就不是原子性操作。
a++可以分解为三个操作:
- 先获得 a 这个值
- 然后 a 加1
- 最后把 a 写回内存
package com.cheng.volatiletest;
import java.util.concurrent.TimeUnit;
public class Demo02 {
private static int num = 0;
public static void add(){
num++;
//非原子性操作
}public static void main(String[] args) {for (int i = 0;
i < 20;
i++) {//创建20个线程
new Thread(()->{
for (int j = 0;
j < 1000;
j++) {//每个线程执行100次add()方法
add();
}
}).start();
}
//如果当前存活线程大于两个
while (Thread.activeCount() > 2){// main,GC
Thread.yield();
//让出cpu使用权
}
System.out.println(Thread.currentThread().getName()+""+num);
}
}
运行查看结果:
【JUC并发编程|JUC并发编程—— volatile 关键字详解】
文章图片
因为add方法是个非原子性操作,所以线程在执行add方法的操作时,会被其他线程插入,导致执行出现问题。
使用 Synchronized 或 Lock 可以保证原子性,在执行add方法时,将不会被其他线程插队。
使用原子类除了使用 Synchronized 和 Lock ,JUC包下的原子类也可以保证原子性。
package com.cheng.volatiletest;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
public class Demo02 {
private static AtomicInteger num = new AtomicInteger();
//定义原子类AtomicIntegerpublic static void add(){
num.getAndIncrement();
//以原子的方式将当前值加 1
}public static void main(String[] args) {for (int i = 0;
i < 20;
i++) {//创建20个线程
new Thread(()->{
for (int j = 0;
j < 1000;
j++) {//每个线程执行100次add()方法
add();
}
}).start();
}
//如果当前存活线程大于两个
while (Thread.activeCount() > 2){// main,GC
Thread.yield();
//让出cpu使用权
}
System.out.println(Thread.currentThread().getName()+""+num);
}
}
文章图片
getAndIncrement() 源码:
文章图片
文章图片
我们取得了旧值,然后把要加的数传过去,调用getAndAddInt () 进行原子更新操作,实际最核心的方法是 compareAndSwapInt(),使用CAS进行更新。
3、volatile与synchronized比较 ● volatile关键字是线程同步的轻量级实现,所以volatile性能肯定比synchronized要好; volatile只能修饰变量,而synchronized可以修饰方法,代码块. 随着JDK新版本的发布,synchronized的执行效率也有较大的提升,在开发中使用sychronized的比率还是很大的。
● 多线程访问volatile变量不会发生阻塞,而synchronized可能会阻塞。
● volatile能保证数据的可见性,但是不能保证原子性; 而synchronized可以保证原子性,也可以保证可见性。
● 关键字volatile解决的是变量在多个线程之间的可见性; synchronized关键字解决多个线程之间访问公共资源的同步性。
推荐阅读
- rabbitmq从入门到精通|RabbitMQ入门 -- 阿里云服务器安装RabbitMQ
- java|Java中volatile关键字详解
- java学习|JUC并发编程汇总彻底搞懂JUC
- Quartz学习笔记(一) 初遇篇
- HTTP Client 学习笔记 (一) 初遇篇
- LeetCode|【刷题1】LeetCode 34. 在排序数组中查找元素的第一个和最后一个位置 java题解
- 牛逼!IDEA 护眼方案来了。。
- 感悟|Java后端学习体系(韩顺平)
- 笔记|初识Java Bean