synchronized和reentrantlock及其应用场景?
synchronized 是什么
synchronized 是 Java 提供的 内置锁机制。
核心一句话:
保证同一时间只有一个线程执行某段代码。
例如
同时调用三个线程:线程A,线程B,线程C
有synchronized的存在使得三个线程可以
A执行完
B再执行
C最后执行
避免了线程安全问题(A B C 同时执行)
synchronized 的本质
synchronized 是锁,更是对象监视器锁(Monitor Lock)
用synchronized锁住了obj类,当有线程进入到obj类中,就会获取到obj的锁。如果别人已经拿走了锁,就必须进行等待。
synchronized 的三种用法
- 锁普通方法
java
public synchronized void method(){
}
锁的是:this对象
- 锁代码块
java
synchronized(obj){
}
锁的是:obj对象
- 锁静态方法
java
public static synchronized void method(){
}
锁的是:Class对象
ReentrantLock 是什么
ReentrantLock 是 Java并发包提供的一种锁。
相比synchronized更高级
使用方法:
java
Lock lock = new ReentrantLock();
lock.lock();
try{
// 临界区
}finally{
lock.unlock();
}
流程:
加锁
执行
释放锁
为什么叫 Reentrant(可重入)
可重入表示:
同一个线程可以多次获得同一把锁。
举个例子:
methodA()
↓
methodB()
两个方法都加锁:lock
同一个线程进入 methodA 后:还能进入 methodB
不会死锁。
注意:
synchronized 也是可重入锁
所以:
两者都是可重入锁
ReentrantLock 比 synchronized好在哪?
1 可以手动控制锁
synchronized
自动加锁 自动释放
ReentrantLock
手动 lock() 手动 unlock()
2 可以尝试获取锁
java
lock.tryLock()
说明:如果拿不到锁,就直接返回。
而synchronized
只能等待。
3 可以设置公平锁
java
new ReentrantLock(true)
公平锁:
先来先得
synchronized:
不支持公平锁
4 可以中断等待线程
java
lock.lockInterruptibly()
线程可以:被中断
synchronized 不支持。
真实应用场景
简单同步
用synchronized
比如:
计数器 简单资源保护
java
public synchronized void add(){
count++;
}
复杂锁控制
用ReentrantLock
比如:
需要尝试获取锁
需要超时
需要公平锁
java
if(lock.tryLock()){
// 执行
}
高并发组件
用ReentrantLock
比如:
线程池
并发容器
阻塞队列
总结表
| 特性 | synchronized | ReentrantLock |
|---|---|---|
| 类型 | JVM关键字 | Java类 |
| 加锁方式 | 自动 | 手动 |
| 释放锁 | 自动 | 必须unlock |
| 可重入 | 支持 | 支持 |
| 公平锁 | 不支持 | 支持 |
| tryLock | 不支持 | 支持 |
| 中断锁 | 不支持 | 支持 |
| 复杂控制 | 弱 | 强 |
syncronized锁升级的过程
先理解为什么需要锁升级
因为在早期JDK版本,syncronized性能很差,因为他使用的是操作系统级别的互斥锁(Mutex),一旦线程竞争锁,就会发生
线程阻塞
线程唤醒
所以在JDK1.6版本之后,出现了优化:
锁会根据竞争情况逐渐升级。
这就是锁升级的由来。
锁升级的整体过程
升级过程:
无锁
↓
偏向锁
↓
轻量级锁
↓
重量级锁
一句话理解:竞争越激烈,锁越重。
第一阶段无锁状态
第二阶段偏向锁
当第一个线程访问到
java
synchronized(obj){
}
JVM会认为只有一个线程会访问,于是进入偏向锁
偏向锁的含义:
锁偏向某个线程
对象头会记录:
线程ID
之后如果线程A再进入:
不需要加锁
不需要CAS
直接执行
性能接近于:无锁
第三阶段:轻量级锁
此时如果另一个线程来了 :线程B
线程B尝试获取锁
此时JVM发现:锁已经偏向线程A
于是:偏向锁失效
升级为:轻量级锁
轻量级锁的核心思想是:
自旋
线程不会立即阻塞,而是:
循环尝试获取锁
例如:
while(锁没释放){
再试一次
}
这样可以避免:
线程阻塞
线程唤醒
性能比重量级锁好很多。
第四阶段:重量级锁
如果线程越来越多:
线程A
线程B
线程C
线程D
此时自旋会消耗大量CPU。
JVM就会判断
竞争过于激烈,将锁升级成重量级锁
特点:
线程会被阻塞
进入:
操作系统的Mutex锁
流程变为:
线程A执行
线程B阻塞
线程C阻塞
等线程A释放锁后:
操作系统唤醒其他线程
锁升级流程图
线程访问同步代码
│
▼
无锁
│
▼
偏向锁
(单线程访问)
│
▼
轻量级锁
(少量竞争)
│
▼
重量级锁
(激烈竞争)
存在的意义
总结:锁升级,就是为了
减少线程阻塞带来的性能开销。
发明的产物