【Java 多线程】【Synchronized 原理&死锁】

本文详细解析了Java中的Synchronized原理,包括锁的升级过程(无锁、偏向锁、轻量级锁和重量级锁),以及死锁的概念、产生原因和解决策略。重点讨论了锁的优化技术,如锁消除和锁粗化,以及避免死锁的关键点。
摘要由CSDN通过智能技术生成

目录

1、Synchronized 原理

1.1 加锁(锁的升级)

1.1.1 无锁

1.1.2 偏向锁

1.1.3 自旋锁 

1.1.4 重量级锁 

1.2 锁的优化

 1.2.1 锁消除

 1.2.2 锁粗化

 2、死锁

2.1 死锁是什么

 2.2、死锁产生的原因(4个必要条件)

2.3 容易出现死锁的情况

2.4.  解决死锁


1、Synchronized 原理

结合锁策略,总结出 Synchroized 具有的特性(JDK1.8)

  1. 开始是 乐观锁 ,如果出现 锁冲突,转换为 悲观锁
  2. 开始是 轻量级锁 锁被持有的时间较长,转换为 重量级锁
  3. 实现轻量级锁 用到 自旋锁
  4. 不公平锁 ,锁竞争
  5. 可重入锁
  6. 不是读写锁

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;

  1. 线程 B 持有X,线程 C持有Y
  2. 线程A需要同时持有X,Y才能执行,B,C不会单一释放,那A就会持续等待,死锁

3.  多线程竞争资源访问顺序

线程A 和 线程 B,共享资源 X Y;

  1. 线程A 先持有 X,再持有 Y
  2. 线程B 先持有 Y,再持有 X
  3. 线程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
}
//这就不会产生 环路等待,顺序执行

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值