1. JMM
请你谈谈对Volatile的理解
Volatile是Java虚拟机提供轻量级的同步机制。
- 保证可见性
- 不保证原子性
- 禁止指令重排
什么是JMM
JVM:Java内存模型,不存在的东西,他是一个概念、约定。
关于JMM的一些同步的约定:
- 线程解锁前,必须把共享变量
立刻
刷回主存。 - 线程枷锁前,必须读取主存中的最新值到工作内存中!
- 加锁和解锁是同一把锁。
线程:工作内存、主内存
八种操作:
内存交互操作
内存交互操作有8种,虚拟机实现必须保证每一个操作都是原子的,不可在分的(对于double和long类型的变量来说,load、store、read和write操作在某些平台上允许例外)
- lock(锁定):作用于主内存的变量,把一个变量标识为线程独占状态
- unlock (解锁):作用于主内存的变量,它把一个处于锁定状态的变量释放出来,释放后的变量才可以被其他线程锁定
- read (读取):作用于主内存变量,它把一个变量的值从主内存传输到线程的工作内存中,以便随后的load动作使用
- load(载入):作用于工作内存的变量,它把read操作从主存中变量放入工作内存中
- use (使用):作用于工作内存中的变量,它把工作内存中的变量传输给执行引擎,每当虚拟机遇到一个需要使用到变量的值,就会使用到这个指令
- assign (赋值):作用于工作内存中的变量,它把一个从执行引擎中接受到的值放入工作内存的变量副本中
- store (存储):作用于主内存中的变量,它把一个从工作内存中一个变量的值传送到主内存中,以便后续的write使用
- write (写入):作用于主内存中的变量,它把store操作从工作内存中得到的变量的值放入主内存的变量中
JMM对这八种指令的使用,制定了如下规则:
- 不允许read和load、store和write操作之一单独出现。即使用了read必须load,使用了store必须write
- 不允许线程丢弃他最近的assign操作,即工作变量的数据改变了之后,必须告知主存
- 不允许一个线程将没有assign的数据从工作内存同步回主内存
- 一个新的变量必须在主内存中诞生,不允许工作内存直接使用一个未被初始化的变量。就是怼变量实施use、store操- 作之前,必须经过assign和load操作
- 一个变量同一时间只有一个线程能对其进行lock。多次lock后,必须执行相同次数的unlock才能解锁
- 如果对一个变量进行lock操作,会清空所有工作内存中此变量的值,在执行引擎使用这个变量前,必须重新load或assign操作初始化变量的值
- 如果一个变量没有被lock,就不能对其进行unlock操作。也不能unlock一个被其他线程锁住的变量
- 对一个变量进行unlock操作之前,必须把此变量同步回主内存
实践
public class Demo {
private static int num = 0;
public static void main(String[] args) throws InterruptedException { // main
new Thread(()->{ // 线程对主内存的变化不知道
while (num ==0){
}
}).start();
TimeUnit.SECONDS.sleep(1);
num = 1;
System.out.println(num);
}
}
问题:程序不知道主内存的值被修改过了。
2. Volatile
保证可见性
增加volatile增加可见性
private volatile static int num = 0;
不保证原子性
原子性:不可分割
线程A在执行任务的时候,不能被打扰的,也不能被分割。要么同时成功,要么同时失败。
public class Demo02 {
private volatile static int num = 0;
public static void add(){
num ++ ;
}
public static void main(String[] args) {
// 理论上num结果应该为20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
while(Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "" +num);
}
}
如果不加 lock 和 synchronized,怎么样保证原子性。
使用原子类Atomic,解决原子性问题
public class Demo02 {
private volatile static AtomicInteger num = new AtomicInteger();
public static void add(){
//num ++ ; // 不是一个原子性操作
num.getAndIncrement(); // AtomicInteger + 1 ,CAS
}
public static void main(String[] args) {
// 理论上num结果应该为20000
for (int i = 0; i < 20; i++) {
new Thread(()->{
for (int i1 = 0; i1 < 1000; i1++) {
add();
}
}).start();
}
while(Thread.activeCount() > 2){
Thread.yield();
}
System.out.println(Thread.currentThread().getName() + "" +num);
}
}
什么是指令重排
你写的程序,计算机并不是按照你写的那样去执行的。
源代码 --> 编译器优化重排–>指令并行也可能会重排–>内存系统也会重排-> 执行
处理器在进行指令重排的时候,考虑:数据之间的依赖性!
Volatile可以避免指令重排:内存屏障。CPU指令:
- 保证特定的操作的执行顺序!
- 可以保证某些变量的内存可见性(利用这些特性Volatile实现了可见性)
3. 单例模式
饿汉式
public class Hungry {
// 可能浪费空间
private byte[] data1 = new byte[1024*1024];
private byte[] data2 = new byte[1024*1024];
private byte[] data3 = new byte[1024*1024];
private byte[] data4 = new byte[1024*1024];
private Hungry(){
}
private final static Hungry HUNGRY = new Hungry();
private static Hungry getInstance(){
return HUNGRY;
}
}
DCL懒汉式
// 懒汉式
public class LazyMan {
private static boolean mango = false;
private LazyMan(){
synchronized (LazyMan.class){
if(mango == false){
mango = true;
}else{
throw new RuntimeException("不要试图使用破坏");
}
if(LazyMan!=null){
throw new RuntimeException("不要试图使用破坏");
}
}
}
private volatile static LazyMan LazyMan;
public static LazyMan getInstance(){
// 双重检测锁模式,懒汉式单例,DCL懒汉式加锁
if(LazyMan == null){
synchronized (LazyMan.class){
if(LazyMan == null){
LazyMan = new LazyMan(); // 不是原子性操作
}
}
}
return LazyMan;
}
/**
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*/
// 多线程
// public static void main(String[] args) {
// for (int i = 0; i < 10; i++) {
// new Thread(()->{
// LazyMan.getInstance();
// }).start();
// }
// }
// 反射
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// LazyMan instance = LazyMan.getInstance();
Field mango = LazyMan.class.getDeclaredField("mango");
mango.setAccessible(true);
Constructor<LazyMan> declaredConstructors = LazyMan.class.getDeclaredConstructor();
declaredConstructors.setAccessible(true);
LazyMan instance2 = declaredConstructors.newInstance();
mango.set(instance2,false);
LazyMan instance1 = declaredConstructors.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
静态内部类
public class Holder {
private Holder(){
}
public static class InnerClass{
private static final Holder HOLDER= new Holder();
}
public static Holder getInstance(){
return InnerClass.HOLDER;
}
}
单利不安全
使用枚举
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
EnumSingle instance2 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle instance3 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
System.out.println(instance3);
}
}
枚举类型的最终反编译源码是有参构造:
4. 深入学习CAS
什么是CAS
Unsafe
public class CASDemo {
// CAS compareAndSet:比较并交换!
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
// 期望、更新
// public final boolean compareAndSet(int expect, int update)
// 如果我期望的值达到了,那么久更新,否则,就不更新 CAS 是CPU的并发愿语!
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
}
}
CAS:比较当前工作内存中的值和主内存中的值,如果这个值是期望的,那么则执行操作!如果不是就一直循环!
CAS缺点:
- 循环会耗时
- 一次性只能保证一个共享变量的原子性
- ABA问题
CAS:ABA问题(狸猫换太子)
5. ABA问题
ABA问题代码:
public class CASDemo {
// CAS compareAndSet:比较并交换!
public static void main(String[] args) {
AtomicInteger atomicInteger = new AtomicInteger(2020);
// 对于我们平时写的SQL:乐观锁!
// 期望、更新
// public final boolean compareAndSet(int expect, int update)
// 如果我期望的值达到了,那么久更新,否则,就不更新 CAS 是CPU的并发愿语!
// =======捣乱的线程========
System.out.println(atomicInteger.compareAndSet(2020, 2021));
System.out.println(atomicInteger.get());
System.out.println(atomicInteger.compareAndSet(2021, 2020));
System.out.println(atomicInteger.get());
// =======期望的线程========
System.out.println(atomicInteger.compareAndSet(2020, 213123));
System.out.println(atomicInteger.get());
}
}
5. 原子引用
注意:
解决ABA问题,引入原子引用!
public class CASDemo {
// CAS compareAndSet:比较并交换!
public static void main(String[] args) {
// AtomicInteger atomicInteger = new AtomicInteger(2020);
AtomicStampedReference<Integer> atomicInteger = new AtomicStampedReference<>(1, 1);
// 对于我们平时写的SQL:乐观锁!
new Thread(
()->{
int stamp = atomicInteger.getStamp(); // 获得版本号
System.out.println("a1=>"+stamp);
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(1, 2, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
System.out.println("a2=>"+atomicInteger.getStamp());
System.out.println(atomicInteger.compareAndSet(2, 1, atomicInteger.getStamp(), atomicInteger.getStamp() + 1));
System.out.println("a3=>"+atomicInteger.getStamp());
},"A"
).start();
new Thread(
()->{
int stamp = atomicInteger.getStamp(); // 获得版本号
System.out.println("b=>"+stamp);
try {
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(atomicInteger.compareAndSet(1, 4, stamp, stamp + 1));
System.out.println("b2=>"+atomicInteger.getStamp());
},"B"
).start();
}
}
6. 各种锁
6.1 公平锁、非公平锁
非公平锁:非常不公平,可以插锁(默认非公平)
public ReentrantLock() {
sync = new NonfairSync();
}
公平锁:非常公平,不能够插队,必须先来后到!
public ReentrantLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
}
6.2 可重入锁(递归锁)
拿到了外面的锁,就可以拿到里面的锁(自动获得)。
Synchronized
public class demo01 {
public static void main(String[] args) {
Phone phone = new Phone();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone{
public synchronized void sms(){
System.out.println(Thread.currentThread().getName()+"sms");
call(); // 这列也有锁
}
public synchronized void call(){
System.out.println(Thread.currentThread().getName()+"call");
}
}
lock
public class Demo02 {
public static void main(String[] args) {
Phone2 phone = new Phone2();
new Thread(()->{
phone.sms();
},"A").start();
new Thread(()->{
phone.sms();
},"B").start();
}
}
class Phone2{
Lock lock = new ReentrantLock();
public void sms(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"sms");
call(); // 这列也有锁
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
public void call(){
lock.lock();
try {
System.out.println(Thread.currentThread().getName()+"call");
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
7. 自旋锁
自旋锁:
public class spinLockDemo {
AtomicReference<Thread> atomicReference = new AtomicReference<>();
// 加锁
public void MyLock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "==> mylock");
// 自旋锁
while (atomicReference.compareAndSet(null,thread)){
}
}
// 解锁
public void MyUnLock(){
Thread thread = Thread.currentThread();
System.out.println(thread.getName() + "==> mylock");
// 自旋锁
atomicReference.compareAndSet(thread,null);
}
}
使用自己封装的自旋锁:
public class TestSpinLock {
public static void main(String[] args) throws InterruptedException {
spinLockDemo lock = new spinLockDemo();
new Thread(()->{
lock.MyLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.MyUnLock();
}
},"T1").start();
TimeUnit.SECONDS.sleep(1);
new Thread(()->{
lock.MyLock();
try {
TimeUnit.SECONDS.sleep(3);
} catch (Exception e) {
e.printStackTrace();
} finally {
lock.MyUnLock();
}
},"T2").start();
}
}
8. 死锁
什么是死锁
死锁测试,怎么排除死锁:
public class DeadLock {
public static void main(String[] args) {
String lockA = "lockA";
String lockB = "lockB";
new Thread(new MyThread(lockA,lockB),"T1").start();
new Thread(new MyThread(lockB,lockA),"T2").start();
}
}
class MyThread implements Runnable{
private String LockA;
private String LockB;
public MyThread(String lockA, String lockB) {
LockA = lockA;
LockB = lockB;
}
@SneakyThrows
@Override
public void run() {
synchronized (LockA){
System.out.println(Thread.currentThread().getName() + "lock:" + LockA +"=>get"+LockB);
TimeUnit.SECONDS.sleep(2);
synchronized (LockB){
System.out.println(Thread.currentThread().getName() + "lock:" + LockB+"=>get"+LockA);
}
}
}
}
解决问题