目录
1、Synchronized 原理
结合锁策略,总结出 Synchroized 具有的特性(JDK1.8)
- 开始是 乐观锁 ,如果出现 锁冲突,转换为 悲观锁
- 开始是 轻量级锁, 锁被持有的时间较长,转换为 重量级锁;
- 实现轻量级锁 用到 自旋锁
- 是 不公平锁 ,锁竞争
- 是 可重入锁
- 不是读写锁
1.1 加锁(锁的升级)
Synchroized 对对象加锁时,根据情况,JVM将Synchroized 锁分为:
无锁、偏向锁、轻量级锁、重量级锁 状态,依次升级,不能降级
1.1.1 无锁
无锁:没有加任何锁,不会出现锁竞争
1.1.2 偏向锁
偏向锁:存在锁竞争,尝试加锁状态,但不是真正的加锁,而是打上个偏向锁的标记,记录这个锁属于哪个线程
- 如果没有其他的线程来竞争,就不用实际加锁
- 后续有其他的线程来竞争,取消原来的偏向锁状态,实际加上锁,进入轻量锁
偏向锁本质是:延迟加锁,非必要,不加锁,避免加锁的开销
偏向锁标记:区分是否真正需要加锁,有其他的线程来打标记,JVM就会通知赶紧加上锁
1.1.3 自旋锁
自旋锁:遇到锁竞争,就是自旋锁(轻量级锁)
- 轻量级锁 是通过 CAS来实现
- 自旋锁一直处于自旋状态,比较浪费CPU资源,因此达到一定时间/次数,就不自旋了
1.1.4 重量级锁
重量级锁:锁竞争更加激烈,多条线程进入自旋,就会升级为重量级锁
- 自旋线程多了,CPU的消耗会增大
- 在内核进行阻塞等待,线程放弃CPU持续等待,由内核进行后续调度
1.2 锁的优化
1.2.1 锁消除
编译器+JVM 判断锁是否可消除. 如果可以, 就直接消除;
编译阶段进行的优化,检测当前代码是否是多线程执行/是否有必要加锁,非必要不加锁,无必要,在编译阶段就将锁自动去掉
synchronized 的滥用,比如 StringBuffer 是线程安全的,关键方法都会加上 synchrinized;
单线程使用了 StringBuffer ,不涉及到线程安全,加锁就不会被执行;
1.2.2 锁粗化
一段逻辑中如果出现多次加锁解锁, 编译器 + JVM 会自动进行锁的粗化.
同一个执行中,每个操作都进行了加锁,就会简化为这个执行加上一个锁就行了
2、死锁
2.1 死锁是什么
死锁:多个进程或线程 在互相持有对方需要的资源而无法继续执行的一种状态;
比如:两个工人,只有一把锤子和钉子,一个人拿着锤,一个人拿着钉子,两人都需要对方持有的,但都不放下自己持有的,进入僵持状态;
通常放生在多线程同时请求资源时,一个线程请求被阻塞,其他线程需要这个线程持有的资源并等待该线程释放资源,也进入阻塞,形成了阻塞;
Object locker1 = new Object();
Object locker2 = new Object();
//线程1
synchronized(locker1){ //持有 锁1
synchronized(locker2)//等待锁2
}
//线程2
synchronized(locker2){ //持有 锁2
synchronized(locker1)//等待锁1
}
//这就造成了死锁
2.2、死锁产生的原因(4个必要条件)
1. 互斥条件:每个资源只能被一个线程或进程占用;(锁基本特点)
2. 请求和保持条件:线程对已占有的资源保持不放,同时有请求获取其他资源;
(吃着碗里的惦记着锅里的)(代码的特点)
3. 不剥夺条件:线程已获得的资源在未使用完不被其他线程强制剥夺,只能自己释放
(锁基本特点)
4. 循环等待条件:若干线程组成环,都在等待上一个线程占有的资源(代码的特点)
2.3 容易出现死锁的情况
1. 一个线程 ,一把锁,可重入锁没事,不可重入死锁
同一个线程,对同一把锁,加锁两次,可重入没有事,不可重入就会产生锁竞争,等待锁,自己竞争自己,自等待;
2. 线程多资源同时访问
线程A 、 线程 B、线程 C,共享资源 X Y;
- 线程 B 持有X,线程 C持有Y
- 线程A需要同时持有X,Y才能执行,B,C不会单一释放,那A就会持续等待,死锁
3. 多线程竞争资源访问顺序
线程A 和 线程 B,共享资源 X Y;
- 线程A 先持有 X,再持有 Y
- 线程B 先持有 Y,再持有 X
- 线程A 等待B释放Y,线程B 等待A释放X,循环等待,死锁状态;
2.4. 解决死锁
最有效解决死锁问题,就是破除循环等待,进行锁排序
对多线程的加锁顺序 加以限制
Object locker1 = new Object();
Object locker2 = new Object();
//线程1
synchronized(locker1){ //持有 锁1
synchronized(locker2)//持有 锁2
}
//线程2
synchronized(locker1){ //等待 锁1
synchronized(locker2)//等待锁1
}
//这就不会产生 环路等待,顺序执行