文章目录
- 前言
- 一、简介
- volatile
- synchronized
- 二、名词解释
- 可见性
- 原子性
- 指令重排
- 临界区
- 对象锁
- 类锁
- 二、实战使用
- 1 Volatile可以解决的问题
- 2 volatile无法解决非原子性操作问题--synchronized
- 总结
前言
volatile与synchronized 都是java的关键字
- volatile一般修饰变量,被修饰的变量会及时将计算值刷新回主内存;
- synchronized 一般作用于 方法, 代码块等,一般分为 对象锁 和 类锁;
一、简介
volatile
- 能够保证数据可见性
- 禁止指令重排,在一定程度上保证了多线程情况下的执行顺序,从而保证结果执行正确
- 不能完全保证多线程情况下的结果正确: 非原子性操作无法保证
synchronized
- 被synchronized 修饰后,大体分为两种,一种是对象锁,一种是类锁
- synchronized 可以保证被修饰的部分,一个时间点只有一个线程执行
- 在多线程情况下,相当于将多线程转为了串行执行,不能完全达到多线程的性能,但是可以通过控制锁的粒度,尽量减少锁的时间;
- 对象锁,锁住的是实例对象,类锁,锁住的是字节码
二、名词解释
可见性
- 当写一个 volatile 变量时,JMM 会把该线程本地内存中的变量强制刷新到主内存中去;这个写操作会导致其他线程中的 volatile 变量缓存无效,无效后也就是强制让其他线程再次读取主内存数据,达到了数据被修改后能被其他线程立马感知到,即可见性;
原子性
- 没错,与事务中的原子性可以认为是等价的;
指令重排
- jvm为了优化代码运行的一种手段,编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序
- 举个例子 ABC 三步
int a = 1; // A操作
int b = 2; // B操作
int sum = a + b;// C 操作
System.out.println(sum);
多线程情况下, 可能是ABC 也可能是 BAC,这种指令重排不会影响结果,所以是允许的;
临界区
- 所谓“临界区”,指的是某一块代码区域,它同一时刻只能由一个线程执行。说人话就是: 被锁 锁住的代码部分;
对象锁
当synchronized 作用于普通方法,代码块,this 的时候,都是对象锁,每个对象实例一把锁,多个对象实例多个锁,多个锁之间没有影响, 那么多个对象实例之间的数据就不应该用对象锁,只适合单个对象;
类锁
当synchronized 作用于静态方法,this.getClass() 的时候,因为每个对象只有一份class,也就是唯一的,所以这种锁,无论你new 多少个对象实例,它们都公用一把锁
二、实战使用
1 Volatile可以解决的问题
public class Volatile可以解决的问题 {
private boolean flag = true;
// private volatile boolean flag = true;
private int num = 0;
public void write() {
flag = false;
System.out.println(Thread.currentThread().getName() + "开始执行.....");
}
public void read() {
System.out.println(Thread.currentThread().getName() + "开始执行.....");
while (flag) {
num++;
}
System.out.println(Thread.currentThread().getName() + "结束.....");
System.out.println("跳出循环" + num);
}
public static void main(String[] args) throws InterruptedException {
Volatile可以解决的问题 handle = new Volatile可以解决的问题();
new Thread(handle::read).start();
Thread.sleep(2000);
new Thread(handle::write).start();
}
}
两个线程运行 第一个线程判断 当 flag 为ture ,就一直执行
第二个线程改变flag 的值,时期变为flase;
如果不使用volatile修饰,那么flag 虽然会被改变,但是由于没有强制刷新会主内存,那么其实第一个线程也感知不到
所以仍然会继续num++ ,不会停止;
解决问题的关键在于,volatile可以强制将线程内部的计算结果,刷新会主内存,并且使其他线程的值失效,不得不再次去主内存获取
就是如上代码,.不用volatile修饰的时候,就会导致即使改变了flag 的值,但是第一个线程仍然无法感知到,所以就一直运行,不会结束
2 volatile无法解决非原子性操作问题–synchronized
@Data
public class 多线程问题 implements Runnable {
public static int num = 0;
private synchronized void add() {
for (int i = 0; i < 500; i++) {
num++;
}
}
// public volatile static int num = 0;
// private void add() {
// for (int i = 0; i < 500; i++) {
// num++;
// }
// }
@Override
public void run() {
add();
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
多线程问题 pro = new 多线程问题();
Thread thread = new Thread(pro);
thread.setName("第一个线程");
Thread thread1 = new Thread(pro);
thread1.setName("第二个线程");
thread1.start();
thread.start();
while (Thread.activeCount() > 1) {
Thread.yield();
}
System.out.println(num);
}
}
}
出现问题的现象是,计算结果无法保证,计算错误; i++ 非原子操作
所以需要用类似 synchronized 的锁,解决这类问题
总结
类似于 synchronized 的属于悲观锁,相对性的就是乐观锁;
乐观锁多用于“读多写少“的环境,避免频繁加锁影响性能;而悲观锁多用于”写多读少“的环境,避免频繁失败和重试影响性能。