Java锁
1. 对象头
1.1 简介
以32位的 JVM 为例,每个Java对象的对象头都包含了如下信息
Mark Word: 锁的信息,hashcode,垃圾回收器标志
Klass Word: 指针,包含当前对象的Class对象的地址,类对象来确定该对象是什么类型
Object Header ( 64 bits)
Mark Word( 32 bits) Klass Word( 32 bits)
Object Header ( 96 bits)
Mark Word( 32 bits) Klass Word( 32 bits) Array Length ( 32 bits)
1.2 Mark Word
- 01/00/11: 代表是否加锁
- age: 垃圾回收器标记
2. Monitor监视器
2.1 monitor
1 . Owner: 保存当前获取到java锁的,线程指针
2 . EntryList: 保存被java锁阻塞的,线程指针
3 . WaitSet: 保存被java锁等待的,线程指针
- 被Synchronized修饰的java对象, 重量级锁,不公平锁
2.2 竞争步骤
java对象被synchronized修饰后 当它获取到对象锁的时候,该对象就会被关联到monitor,java对象的Mark Word就会变为 prt_to_heavyweight_monitor , 即是用来保存重量级锁的地址,同时mark word中的01转变为10
1 . thread-1 通过synchronized获取到一个obj对象
1.1 obj对象头信息( hashcode,age等) 变为prt_to_heavyweight_monitor( 30 bit) ( monitor指针)
1.2 obj对象头的锁状态变为 10 (重量级锁)
1.3 根据monitor指针,找到monitor,将Owner设置为thread-1
2 . thread-2 过来后,检查obj锁对象头
2.1 发现该obj对象头的Mark Word的锁状态已经是重量级锁
2.2 根据Mark Word中锁的地址检查到当Owner已经有其他线程了
2.3 thread-2进入到EntryList,进行Block
3 . thread-1 执行完临界区代码后,
3.1 monitor的Owner进行清空
3.2 将owner中的当前线程的owner和obj对象头中的monitor地址再次交换
3.3 monitor唤醒EntryList中其他线程
3.4 其他在 EntryList 中等待的线程, 再次竞争对象锁,再次设置monitor的Owner
a. synchronized( obj) ,就会有一个monitor监管该对象
b. 同步代码块如果发生异常时候,也会将锁释放
c. synchronized( obj) , 必须关联到同一个obj,不然就不会指向同一个monitor
3. 常见锁
3.1 轻量级锁
锁对象虽被多个线程都来获取,但访问时间错开,不存在竞争 轻量级锁对使用者 是透明的, 语法:syncronized 当存在其他线程竞争的时候,自动升级为重量级锁
3.2 锁重入
锁重入: 一个线程在调用一个方法的时候,在方法调用链中,多次使用同一个对象来加锁
- 线程在自己的工作内存内,创建栈帧,并在活动栈帧创建一个 《锁记录》 的结构
- 锁记录: lock record address: 加锁的信息,用来保存当前线程ID等信息, 同时后续会保存对像锁的Mark Word
Object Reference: 用来保存锁对象的地址
00: 表示轻量级锁, 01代表无锁
- 锁记录对象:是在JVM层面的,对用户无感知
- Object Body: 该锁对象的成员变量
- 尝试cas交换Object中的 Mark Word和栈帧中的锁记录
- 情况一:锁膨胀,若其他线程持有该obj对象的轻量级锁,表明有竞争,进入锁膨胀过程,加重量级锁
- 情况二:锁重入,若本线程再次synchronized锁,再添加一个Lock Record作为重入计数
- 两种情况区分: 根据obj中保存线程的lock record地址来进行判断
- null: 表示重入了几次
- 退出synchronized代码块时,若为null的锁记录,表示有重入,这时清除锁记录(null清除)
- 退出synchronized代码块时,锁记录不为null,cas将Mark Word的值恢复给对象头
同时obj头变为01无锁状态
- 成功则代表解锁成功; 失败说明轻量级锁进入了锁膨胀
3.3 锁膨胀
在尝试轻量级加锁时,cas无法成功 可能因为:其他线程为此对象加上了轻量级锁(有竞争),这时进行锁膨胀,锁变为重量级锁 轻量级锁没有阻塞机制,重量级锁有阻塞机制
- thread-0轻量级锁加锁成功
- 当thread-1进行轻量级加锁时,thread-0已经为该对象加了轻量级锁,对应的java object是00
- thread-1轻量级加锁失败,进入了锁膨胀流程
- 为Object对象申请monitor锁,并让Object的mark word 指向重量级锁地址, 同时变为10( 重量级锁)
- 然后自己进入monitor的EntryList 进行 Block
- 当Thread-0 退出同步块时,使用cas将Mark Word的值恢复给对象头,失败进入重量级解锁流程
- 按照Monitor地址找到Monitor,设置Owner为null,唤醒EntryList中BLOCKED线程
3.4 自旋优化-重量级锁
- 一个线程的重量级锁被其他线程持有时,该线程并不会直接进入阻塞
- 先本身自旋,同时查看锁资源在自旋优化期间是否能够释放 《避免阻塞时候的上下文切换》
- 若当前线程自旋成功( 即此时持有锁的线程已经退出了同步块,释放了锁) ,这时线程就避免了阻塞
- 若自旋失败,则进入EntryList中
智能自旋:
- 自适应的: Java6之后,对象刚刚的一次自旋成功,就认为自旋成功的概率大,就会多自旋几次
反之,就少自旋几次甚至不自旋
- java7之后不能控制是否开启自旋功能
- 自旋会占用cpu时间,单核自旋就是浪费,多核自旋才有意义
4. 锁消除
- Java的 JIT, 即时编译器,对热点代码进行优化
- 逃逸分析: JVM 是根据锁对象是否可以发生逃逸分析来判断
- JVM默认开启锁消除机制
- Java中锁消除默认是打开的,会根据代码中锁关联的对象是否能够逃逸决定是否优化
- 关闭锁消除: java -XX: -EliminateLocks -jar demo.jar
4.1 消除
package com. nike. erick. d01 ;
public class Demo07 {
public static void main ( String [ ] args) {
lockMethod ( ) ;
nonLockMethod ( ) ;
}
private static void lockMethod ( ) {
long startTime = System . currentTimeMillis ( ) ;
Integer number = 0 ;
for ( int i = 0 ; i < 10000000 ; i++ ) {
synchronized ( new Object ( ) ) {
number++ ;
}
}
System . out. println ( "Lock Method Times: " + ( System . currentTimeMillis ( ) - startTime) ) ;
}
private static void nonLockMethod ( ) {
long startTime = System . currentTimeMillis ( ) ;
Integer number = 0 ;
for ( int i = 0 ; i < 10000000 ; i++ ) {
number++ ;
}
System . out. println ( "Non Lock Method Times: " + ( System . currentTimeMillis ( ) - startTime) ) ;
}
}
4.2 逃逸分析
private static void lockMethod ( ) {
boolean flag = true ;
Object lock = new Object ( ) ;
new Thread ( ( ) -> {
synchronized ( lock) {
System . out. println ( "逃逸代码块" ) ;
}
} ) . start ( ) ;
long start = System . currentTimeMillis ( ) ;
for ( int i = 0 ; i < 100000000 ; i++ ) {
synchronized ( lock) {
flag = ! flag;
}
}
System . out. println ( "加锁:" + ( System . currentTimeMillis ( ) - start) ) ;
}
5. 锁粒度细化
5.1 单锁
一间屋子两个功能:睡觉,学习,互不影响(不同的方法不会访问同一个资源) 如果用一个屋子(一个对象锁)的话,并发度很低
package com. dreamer. multithread. day04 ;
import java. util. concurrent. TimeUnit ;
public class Demo02 {
public static void main ( String [ ] args) throws InterruptedException {
long start = System . currentTimeMillis ( ) ;
BigRoom room = new BigRoom ( ) ;
Thread firstSleep = new Thread ( ( ) -> room. sleep ( ) ) ;
Thread secondSleep = new Thread ( ( ) -> room. sleep ( ) ) ;
Thread firstWork = new Thread ( ( ) -> room. work ( ) ) ;
Thread secondWork = new Thread ( ( ) -> room. work ( ) ) ;
firstSleep. start ( ) ;
secondSleep. start ( ) ;
firstWork. start ( ) ;
secondWork. start ( ) ;
firstSleep. join ( ) ;
secondSleep. join ( ) ;
firstWork. join ( ) ;
secondWork. join ( ) ;
System . out. println ( "total time:" + ( System . currentTimeMillis ( ) - start) ) ;
}
}
class BigRoom {
public void sleep ( ) {
synchronized ( this ) {
try {
TimeUnit . SECONDS . sleep ( 2 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
public void work ( ) {
synchronized ( this ) {
try {
TimeUnit . SECONDS . sleep ( 2 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
}
5.2 多锁
一个对象中,如果不同种类方法只会被同一种线程调用,则可以进行锁粒度细化 如果多把锁,被一个方法同时使用了,可能造成死锁
class BigRoom {
private Object sleepLock = new Object ( ) ;
private Object workLock = new Object ( ) ;
public void sleep ( ) {
synchronized ( sleepLock) {
try {
TimeUnit . SECONDS . sleep ( 2 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
public void work ( ) {
synchronized ( workLock) {
try {
TimeUnit . SECONDS . sleep ( 2 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
}
6. 活跃性
6.1 死锁
- 线程一:持有a锁,等待b锁
- 线程二:持有b锁,等待a锁
- 互相等待引发的死锁问题
- 哲学家就餐问题
- 定位死锁: 可以借助jconsole来定位死锁
- 解决方法: 都按照相同顺序加锁就可以,但可能引发饥饿问题
package com. dreamer. multithread. day04 ;
import java. util. concurrent. TimeUnit ;
public class Demo02 {
public static void main ( String [ ] args) {
BigRoom room = new BigRoom ( ) ;
new Thread ( ( ) -> room. sleepAndWork ( ) ) . start ( ) ;
new Thread ( ( ) -> room. workAndSleep ( ) ) . start ( ) ;
}
}
class BigRoom {
private final Object sleepLock = new Object ( ) ;
private final Object workLock = new Object ( ) ;
public void sleepAndWork ( ) {
synchronized ( sleepLock) {
consumeTime ( ) ;
synchronized ( workLock) {
System . out. println ( "睡醒---工作啦" ) ;
}
}
}
public void workAndSleep ( ) {
synchronized ( workLock) {
consumeTime ( ) ;
synchronized ( sleepLock) {
System . out. println ( "工作后--要睡觉啦¬" ) ;
}
}
}
private void consumeTime ( ) {
try {
TimeUnit . SECONDS . sleep ( 2 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
}
6.2 饥饿锁
6.3 活锁
两个线程中互相改变对方结束的条件,导致两个线程一直运行下去 可能会结束,但是二者会交替进行
package com. dreamer. multithread. day04 ;
public class Demo04 {
private static int counter = 10 ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> {
while ( counter < 20 ) {
counter++ ;
System . out. println ( " ++ 操作:" + counter) ;
}
} ) . start ( ) ;
new Thread ( ( ) -> {
while ( counter > 0 ) {
counter-- ;
System . out. println ( " -- 操作:" + counter) ;
}
} ) . start ( ) ;
}
}
Wait/Notify
当线程执行任务的时候,发现条件不满足,则进行wait 条件满足后,通过notify来再次执行任务
1. 基本使用
1.1 API
wait( ) notify( ) notifyAll( )
Object类的方法,必须成为锁的owner时候才能使用
public final void wait( ) throws InterruptedException
public final native void wait( long timeoutMillis) throws InterruptedException
public final native void notify( )
public final native void notifyAll( )
1.2 原理
Owner线程发现条件不满足,调用wait,即进入WaitSet变为WAITING状态 wait会释放当前锁资源
1 . BLOCK和WAITING的线程都处于阻塞状态,不占用cpu
2 . BLOCK线程会在Owner线程释放锁时唤醒
3 . WAITING线程会在Owner线程调用notify时唤醒,但唤醒后只是进入EntryList重新竞争锁
1.3 Demo
package com. nike. erick. d02 ;
import java. util. concurrent. TimeUnit ;
public class Demo01 {
private static Object lock = new Object ( ) ;
public static void main ( String [ ] args) throws InterruptedException {
Thread firstThread = new Thread ( ( ) -> {
synchronized ( lock) {
try {
System . out. println ( "first thread coming" ) ;
lock. wait ( ) ;
System . out. println ( "first thread ending...." ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
} ) ;
Thread secondThread = new Thread ( ( ) -> {
synchronized ( lock) {
try {
System . out. println ( "second thread coming" ) ;
lock. wait ( ) ;
System . out. println ( "second thread ending...." ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
}
} ) ;
firstThread. start ( ) ;
secondThread. start ( ) ;
TimeUnit . SECONDS . sleep ( 2 ) ;
synchronized ( lock) {
lock. notifyAll ( ) ;
}
}
}
package com. nike. erick. d02 ;
public class Demo02 {
private static Object lock = new Object ( ) ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> {
try {
lock. wait ( ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
} ) . start ( ) ;
}
}
1.4 wait VS sleep
1 . Wait 是Object的方法 Sleep 是Thread 的静态方法
2 . Wait 必须和synchronized结合使用 Sleep 不需要
3 . Wait 会放弃当前线程的锁资源 Sleep 不会释放锁(如果工作时候带锁)
4 . 都会让出cpu资源,状态都是Timed-Waiting
2.wait/notify正确使用
循环wait: 防止虚假唤醒的问题,确保线程一定是执行完毕任务后才会结束
synchronized ( lock) {
while ( 条件不成立) {
lock. wait ( ) ;
}
executeBusiness ( ) ;
} ;
synchronized ( lock) {
lock. notifyAll ( ) ;
}
2.1. 单个线程wait
package com. erick. multithread. d1 ;
import java. util. concurrent. TimeUnit ;
public class Demo02 {
private static final Object lock = new Object ( ) ;
private static boolean hasCigarette = false ;
public static void main ( String [ ] args) throws InterruptedException {
new Thread ( ( ) -> {
synchronized ( lock) {
while ( ! hasCigarette) {
System . out. println ( "烟没到,休息会儿" ) ;
try {
lock. wait ( ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
}
System . out. println ( "烟来了,开始干活" ) ;
}
} , "t1" ) . start ( ) ;
for ( int i = 0 ; i < 5 ; i++ ) {
new Thread ( ( ) -> {
synchronized ( lock) {
System . out. println ( "其他人开始干活" ) ;
}
} ) . start ( ) ;
}
TimeUnit . SECONDS . sleep ( 2 ) ;
synchronized ( lock) {
hasCigarette = true ;
System . out. println ( "烟到了" ) ;
lock. notify ( ) ;
}
}
}
2.2 多个线程wait
java 11 中,谁先进入WaitSet, notify先唤醒谁
package com. erick. multithread. d1 ;
import java. util. concurrent. TimeUnit ;
public class Demo03 {
private static final Object lock = new Object ( ) ;
private static boolean hasCigarette = false ;
private static boolean hasDinner = false ;
public static void main ( String [ ] args) throws InterruptedException {
new Thread ( ( ) -> {
synchronized ( lock) {
while ( ! hasCigarette) {
System . out. println ( "烟没到,休息会儿" ) ;
try {
lock. wait ( ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
}
System . out. println ( "烟来了,开始干活" ) ;
}
} , "t1" ) . start ( ) ;
new Thread ( ( ) -> {
synchronized ( lock) {
while ( ! hasDinner) {
System . out. println ( "外卖没到,休息会儿" ) ;
try {
lock. wait ( ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
}
System . out. println ( "外卖来了,开始干活" ) ;
}
} , "t2" ) . start ( ) ;
for ( int i = 0 ; i < 5 ; i++ ) {
new Thread ( ( ) -> {
synchronized ( lock) {
System . out. println ( "其他人开始干活" ) ;
}
} ) . start ( ) ;
}
TimeUnit . SECONDS . sleep ( 2 ) ;
synchronized ( lock) {
System . out. println ( "烟来了" ) ;
hasCigarette = true ;
lock. notifyAll ( ) ;
}
}
}
3. 保护性暂停模式
一个线程等待另一个线程的一个执行结果 Guarded Suspension Design Pattern
- 一个结果需要从一个线程传递到另一个线程,让两个线程关联同一个GuardedObject
- JDK中, join的实现,Future的实现,就是采用Guarded Suspension
- 同步模式
3.1 无限等待
package com. erick. multithread. d1 ;
import java. util. concurrent. TimeUnit ;
public class Demo04 {
private static GuardedResponse response = new GuardedResponse ( ) ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> System . out. println ( response. obtainResult ( ) ) ) . start ( ) ;
new Thread ( ( ) -> response. populateResult ( ) ) . start ( ) ;
}
}
class GuardedResponse {
private Object result;
public synchronized Object obtainResult ( ) {
while ( null == result) {
try {
System . out. println ( "暂未获取到资源" ) ;
this . wait ( ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
}
System . out. println ( "已经获取到资源" ) ;
return result;
}
public synchronized void populateResult ( ) {
result = heavyWork ( ) ;
this . notifyAll ( ) ;
}
private Object heavyWork ( ) {
try {
TimeUnit . SECONDS . sleep ( 3 ) ;
return new Object ( ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
}
}
3.2 超时等待
package com. nike. erick. d04 ;
import java. util. ArrayList ;
import java. util. concurrent. TimeUnit ;
public class Demo06 {
private static GuardResponse guardResponse = new GuardResponse ( ) ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> System . out. println ( guardResponse. getResponse ( 2000 ) ) ) . start ( ) ;
new Thread ( ( ) -> {
try {
TimeUnit . SECONDS . sleep ( 1 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
guardResponse. setResponse ( new ArrayList < > ( ) ) ;
} ) . start ( ) ;
}
}
class GuardResponse {
private Object response;
public Object getResponse ( long timeoutMills) {
synchronized ( this ) {
long startTime = System . currentTimeMillis ( ) ;
long passedTime = 0 ;
while ( response == null ) {
long leftTime = timeoutMills - passedTime;
if ( leftTime <= 0 ) {
break ;
}
try {
this . wait ( leftTime) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
passedTime = System . currentTimeMillis ( ) - startTime;
}
return response;
}
}
public void setResponse ( Object response) {
synchronized ( this ) {
this . response = response;
this . notify ( ) ;
}
}
}
3.3 Join原理
4. 生产者消费者
如果有结果从一类线程不断的传递到其他类线程,可以使用消息队列(生产者消费者) 多个生产者及多个消费者, 阻塞队列, 异步消费 消息队列,先入先得,有容量限制,满时不再添加消息,空时不再消费消息 JDK中各种阻塞队列 ,就是用的这种方式
package com. erick. multithread. d1 ;
import java. util. LinkedList ;
public class Demo05 {
public static void main ( String [ ] args) {
MessageBroker < String > messageBroker = new MessageBroker < > ( 5 ) ;
for ( int i = 0 ; i < 10 ; i++ ) {
new Thread ( ( ) -> messageBroker. sendMessage ( "hello:" + Thread . currentThread ( ) . getName ( ) ) ) . start ( ) ;
}
for ( int i = 0 ; i < 2 ; i++ ) {
new Thread ( ( ) -> messageBroker. consumeMessage ( ) ) . start ( ) ;
}
}
}
class MessageBroker < T > {
private int capacity;
private LinkedList < T > blockingQueue = new LinkedList ( ) ;
public MessageBroker ( int capacity) {
this . capacity = capacity;
}
public synchronized void sendMessage ( T message) {
while ( blockingQueue. size ( ) >= capacity) {
try {
System . out. println ( "队列已满,请稍后再发送消息" ) ;
this . wait ( ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
}
blockingQueue. addFirst ( message) ;
System . out. println ( "添加成功:" + Thread . currentThread ( ) . getName ( ) ) ;
this . notifyAll ( ) ;
}
public synchronized T consumeMessage ( ) {
while ( blockingQueue. size ( ) <= 0 ) {
try {
System . out. println ( "当前暂无消息,请稍后再消费" ) ;
this . wait ( ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
}
T message = blockingQueue. removeLast ( ) ;
this . notifyAll ( ) ;
return message;
}
}
5. Park/Unpark
5.1. 基本使用
先park,再unpark park后是waiting状态,会释放锁
java.util.concurrent.locks.LockSupport
public static void park( )
public static void unpark( Thread thread)
package com. dreamer. multithread. day04 ;
import java. util. concurrent. TimeUnit ;
import java. util. concurrent. locks. LockSupport ;
public class Demo01 {
public static void main ( String [ ] args) throws InterruptedException {
Thread slaveThread = new Thread ( "slave-thread" ) {
@Override
public void run ( ) {
System . out. println ( "prepare for PARK...." ) ;
LockSupport . park ( ) ;
System . out. println ( "PARK ended" ) ;
}
} ;
slaveThread. start ( ) ;
TimeUnit . SECONDS . sleep ( 2 ) ;
LockSupport . unpark ( slaveThread) ;
}
}
5.2. 先unpark后park
package com. dreamer. multithread. day04 ;
import java. util. concurrent. locks. LockSupport ;
public class Demo01 {
public static void main ( String [ ] args) {
Thread slaveThread = new Thread ( "slave-thread" ) {
@Override
public void run ( ) {
LockSupport . unpark ( Thread . currentThread ( ) ) ;
System . out. println ( "prepare for PARK...." ) ;
LockSupport . park ( ) ;
System . out. println ( "PARK ended" ) ;
}
} ;
slaveThread. start ( ) ;
}
}
5.3 wait/park
wait/notify是Object的方法 park/unpark是LockSupport
wait/notify 必须和synchronized结合使用 park/unpark不必
wait/notify 顺序不能颠倒 park/unpark可以颠倒
wait/notify 只能随机唤醒一个或者全部唤醒 park/unpark可以指定一个线程唤醒
ReentryLock
1. 基本特性
1.1 可重入锁
一个线程已经获取锁,因为该线程是该锁主人。第二次获取该锁时,依然可以获取到该锁 不可重入:第二次获取就会造成死锁,部分锁就是这种情况
package com. erick. multithread. d2 ;
import java. util. concurrent. locks. ReentrantLock ;
public class Demo01 {
public static void main ( String [ ] args) {
FirstTest firstTest = new FirstTest ( ) ;
firstTest. firstMethod ( ) ;
}
}
class FirstTest {
private ReentrantLock lock = new ReentrantLock ( ) ;
public void firstMethod ( ) {
try {
lock. lock ( ) ;
System . out. println ( "first method coming" ) ;
secondMethod ( ) ;
} finally {
lock. unlock ( ) ;
}
}
public void secondMethod ( ) {
try {
lock. lock ( ) ;
System . out. println ( "second method coming" ) ;
} finally {
lock. unlock ( ) ;
}
}
}
1.2 可打断锁
- 没有其他线程争夺锁,则正常执行
- 有竞争时,线程就会进入EntryList,但是可以被打断
- 其他线程先获取锁,执行一段时间后,等待获取锁的线程 《打断等待》
无竞争
package com. erick. multithread. d2 ;
import java. util. concurrent. locks. ReentrantLock ;
public class Demo02 {
private static ReentrantLock lock = new ReentrantLock ( ) ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> {
try {
lock. lockInterruptibly ( ) ;
} catch ( InterruptedException e) {
System . out. println ( "锁被打断了" ) ;
throw new RuntimeException ( e) ;
}
try {
doBusiness ( ) ;
} finally {
lock. unlock ( ) ;
}
} ) . start ( ) ;
}
private static void doBusiness ( ) {
System . out. println ( "do business logic" ) ;
}
}
有竞争
package com. erick. multithread. d2 ;
import java. util. concurrent. TimeUnit ;
import java. util. concurrent. locks. ReentrantLock ;
public class Demo03 {
private static ReentrantLock lock = new ReentrantLock ( ) ;
public static void main ( String [ ] args) {
Thread firstThread = new Thread ( ( ) -> {
try {
System . out. println ( "first-thread 开始等待锁" ) ;
lock. lockInterruptibly ( ) ;
} catch ( InterruptedException e) {
System . out. println ( "first-thread 锁被打断" ) ;
throw new RuntimeException ( e) ;
}
try {
doBusiness ( ) ;
} finally {
lock. unlock ( ) ;
}
} ) ;
Thread secondThread = new Thread ( new Runnable ( ) {
@Override
public void run ( ) {
try {
lock. lock ( ) ;
sleep ( 2 ) ;
firstThread. interrupt ( ) ;
System . out. println ( "second-thread完成业务" ) ;
} finally {
lock. unlock ( ) ;
}
}
} ) ;
secondThread. start ( ) ;
sleep ( 1 ) ;
firstThread. start ( ) ;
}
private static void doBusiness ( ) {
System . out. println ( "do business logic" ) ;
}
private static void sleep ( int seconds) {
try {
TimeUnit . SECONDS . sleep ( seconds) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
}
}
1.3 公平锁
- 当一个线程持有锁的时候,其他线程进入锁的 EntryList
- 当线程释放锁的时候,其他线程一拥而上,而不是按照进入的顺序先到先得
- 默认是非公平锁,传参为true,公平锁
package com. erick. multithread. d2 ;
import java. util. concurrent. TimeUnit ;
import java. util. concurrent. locks. ReentrantLock ;
public class Demo04 {
private static ReentrantLock lock = new ReentrantLock ( true ) ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> {
try {
lock. lock ( ) ;
sleep ( 5 ) ;
} finally {
lock. unlock ( ) ;
}
} ) . start ( ) ;
sleep ( 1 ) ;
for ( int i = 0 ; i < 3 ; i++ ) {
new Thread ( ( ) -> {
try {
lock. lock ( ) ;
System . out. println ( Thread . currentThread ( ) . getName ( ) + " running" ) ;
} finally {
lock. unlock ( ) ;
}
} ) . start ( ) ;
sleep ( 1 ) ;
}
}
private static void sleep ( int seconds) {
try {
TimeUnit . SECONDS . sleep ( seconds) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
}
}
1.4 超时锁
避免死锁: 主动方式来避免一个线程一直等待锁资源,带来的死锁问题
不带超时
package com. erick. multithread. d2 ;
import java. util. concurrent. locks. ReentrantLock ;
public class Demo05 {
private static ReentrantLock lock = new ReentrantLock ( ) ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> {
boolean hasLock = lock. tryLock ( ) ;
if ( ! hasLock) {
System . out. println ( "没有获取到锁,放弃" ) ;
return ;
}
try {
executeBusiness ( ) ;
} finally {
lock. unlock ( ) ;
}
} ) . start ( ) ;
}
private static void executeBusiness ( ) {
System . out. println ( "do business logic" ) ;
}
}
package com. erick. multithread. d2 ;
import java. util. concurrent. TimeUnit ;
import java. util. concurrent. locks. ReentrantLock ;
public class Demo06 {
private static ReentrantLock lock = new ReentrantLock ( ) ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> {
try {
lock. lock ( ) ;
sleep ( 5 ) ;
} finally {
lock. unlock ( ) ;
}
} ) . start ( ) ;
new Thread ( ( ) -> {
boolean hasLock = lock. tryLock ( ) ;
if ( ! hasLock) {
System . out. println ( "没有获取到锁,放弃等待" ) ;
return ;
}
try {
executeBusiness ( ) ;
} finally {
lock. unlock ( ) ;
}
} ) . start ( ) ;
}
private static void executeBusiness ( ) {
System . out. println ( "do business logic" ) ;
}
private static void sleep ( int seconds) {
try {
TimeUnit . SECONDS . sleep ( seconds) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
}
}
带超时
等待过程中,如果获取到了锁,则执行正常的业务流程 等待一段时间后,如果没有获取到锁,则中断获取锁的竞争 等待过程中,依然可以被打断
ReentrantLock public boolean tryLock( long timeout, TimeUnit unit)
throws InterruptedException
package com. nike. erick. d03 ;
import java. util. concurrent. TimeUnit ;
import java. util. concurrent. locks. ReentrantLock ;
public class Demo06 {
private static ReentrantLock reentrantLock = new ReentrantLock ( ) ;
public static void main ( String [ ] args) throws InterruptedException {
Thread firstThread = new Thread ( ( ) -> {
reentrantLock. lock ( ) ;
try {
TimeUnit . SECONDS . sleep ( 2 ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
} finally {
reentrantLock. unlock ( ) ;
}
} ) ;
Thread secondThread = new Thread ( ( ) -> {
try {
boolean hasLock = reentrantLock. tryLock ( 3 , TimeUnit . SECONDS ) ;
if ( ! hasLock) {
System . out. println ( "没有获取到锁" ) ;
return ;
}
try {
System . out. println ( "Execution Business..." ) ;
} finally {
reentrantLock. unlock ( ) ;
}
} catch ( InterruptedException e) {
System . out. println ( "等待锁过程中被打断了。。。" ) ;
e. printStackTrace ( ) ;
}
} ) ;
firstThread. start ( ) ;
TimeUnit . SECONDS . sleep ( 1 ) ;
secondThread. start ( ) ;
secondThread. interrupt ( ) ;
}
}
2. 条件变量
多WaitSet, 可以和wait/notify更好的结合 ReentrantLock: 可以将等待的不同队列分类,然后根据队列来进行唤醒
ReentrantLock public Condition newCondition( )
Condition public final void await( ) throws InterruptedException
Condition public final void signal( )
public final void signalAll( )
package com. nike. erick. d03 ;
import java. util. concurrent. TimeUnit ;
import java. util. concurrent. locks. Condition ;
import java. util. concurrent. locks. ReentrantLock ;
public class Demo07 {
private static ReentrantLock reentrantLock = new ReentrantLock ( true ) ;
private static Condition boyRoom = reentrantLock. newCondition ( ) ;
private static Condition girlRoom = reentrantLock. newCondition ( ) ;
private static boolean hasCigarette = false ;
private static boolean hasDinner = false ;
public static void main ( String [ ] args) throws InterruptedException {
for ( int i = 0 ; i < 5 ; i++ ) {
int boyNo = i;
new Thread ( ( ) -> {
reentrantLock. lock ( ) ;
while ( true ) {
if ( ! hasCigarette) {
try {
System . out. println ( "没有烟,女孩等会-" + boyNo) ;
boyRoom. await ( ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
} else {
break ;
}
try {
System . out. println ( "男孩打仗-" + boyNo) ;
} finally {
reentrantLock. unlock ( ) ;
}
}
} ) . start ( ) ;
}
for ( int i = 0 ; i < 5 ; i++ ) {
int girlNo = i;
new Thread ( ( ) -> {
reentrantLock. lock ( ) ;
while ( true ) {
if ( ! hasDinner) {
try {
System . out. println ( "没有外卖,女孩等会-" + girlNo) ;
girlRoom. await ( ) ;
} catch ( InterruptedException e) {
e. printStackTrace ( ) ;
}
} else {
break ;
}
}
try {
System . out. println ( "女孩做鞋-" + girlNo) ;
} finally {
reentrantLock. unlock ( ) ;
}
} ) . start ( ) ;
}
TimeUnit . SECONDS . sleep ( 1 ) ;
new Thread ( ( ) -> {
reentrantLock. lock ( ) ;
try {
hasCigarette = true ;
boyRoom. signalAll ( ) ;
} finally {
reentrantLock. unlock ( ) ;
}
} ) . start ( ) ;
TimeUnit . SECONDS . sleep ( 1 ) ;
new Thread ( ( ) -> {
reentrantLock. lock ( ) ;
try {
hasDinner = true ;
girlRoom. signalAll ( ) ;
} finally {
reentrantLock. unlock ( ) ;
}
} ) . start ( ) ;
}
}
3. ReentrantLock vs Synchronized
可重入性: Synchronized 和 ReentrantLock都支持
可打断性: Synchronized锁不能被打断 ReentrantLock可以被打断,防止死锁
超时性: Synchronized锁获取时候会一直等待 ReentrantLock支持超时等待
公平性: Synchronized的EntryList是不公平 ReentrantLock( true) 公平锁
条件变量: Synchronized的WaitSet只有一个 ReentrantLock支持不同的WaitSet
固定顺序输出
1. 先2后1
1.1 synchronized + wait + notify
package com. erick. multithread. d3 ;
public class Demo01 {
private static Object lock = new Object ( ) ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> {
synchronized ( lock) {
try {
lock. wait ( ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
System . out. println ( "first-thread running" ) ;
}
} ) . start ( ) ;
new Thread ( ( ) -> {
synchronized ( lock) {
System . out. println ( "second-thread running" ) ;
lock. notifyAll ( ) ;
}
} ) . start ( ) ;
}
}
1.2 reentrylock + await + signal
package com. erick. multithread. d3 ;
import java. sql. Time ;
import java. util. concurrent. TimeUnit ;
import java. util. concurrent. locks. Condition ;
import java. util. concurrent. locks. ReentrantLock ;
public class Demo02 {
private static ReentrantLock lock = new ReentrantLock ( ) ;
private static Condition room = lock. newCondition ( ) ;
public static void main ( String [ ] args) throws InterruptedException {
new Thread ( ( ) -> {
try {
lock. lock ( ) ;
room. await ( ) ;
System . out. println ( "线程一执行完毕" ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
} finally {
lock. unlock ( ) ;
}
} ) . start ( ) ;
TimeUnit . SECONDS . sleep ( 1 ) ;
new Thread ( ( ) -> {
try {
lock. lock ( ) ;
System . out. println ( "线程二执行完毕" ) ;
room. signal ( ) ;
} finally {
lock. unlock ( ) ;
}
} ) . start ( ) ;
}
}
1.3 park + unpark
package com. erick. multithread. d3 ;
import java. util. concurrent. locks. LockSupport ;
public class Demo03 {
public static void main ( String [ ] args) {
Thread firstThread = new Thread ( ( ) -> {
LockSupport . park ( ) ;
System . out. println ( "线程一运行完毕" ) ;
} ) ;
firstThread. start ( ) ;
new Thread ( ( ) -> {
System . out. println ( "线程二运行完毕" ) ;
LockSupport . unpark ( firstThread) ;
} ) . start ( ) ;
}
}
2. 交替输出
2.1 synchronized + wait + notify
package com. erick. multithread. d3 ;
import java. util. Objects ;
public class Demo04 {
private static Object lock = new Object ( ) ;
private static String baseChar = "a" ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> printCharacter ( "a" , "b" ) ) . start ( ) ;
new Thread ( ( ) -> printCharacter ( "b" , "c" ) ) . start ( ) ;
new Thread ( ( ) -> printCharacter ( "c" , "d" ) ) . start ( ) ;
new Thread ( ( ) -> printCharacter ( "d" , "e" ) ) . start ( ) ;
new Thread ( ( ) -> printCharacter ( "e" , "a" ) ) . start ( ) ;
}
private static void printCharacter ( String srcChar, String targetChar) {
while ( true ) {
synchronized ( lock) {
while ( ! Objects . equals ( baseChar, srcChar) ) {
try {
lock. wait ( ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
}
System . out. println ( Thread . currentThread ( ) . getName ( ) + ":" + baseChar) ;
baseChar = targetChar;
lock. notifyAll ( ) ;
}
}
}
}
2.2 reentrylock + await + signal
package com. erick. multithread. d3 ;
import java. util. concurrent. locks. Condition ;
import java. util. concurrent. locks. ReentrantLock ;
public class Demo05 {
private static String baseChar = "a" ;
private static ReentrantLock lock = new ReentrantLock ( ) ;
private static Condition firstRoom = lock. newCondition ( ) ;
private static Condition secondRoom = lock. newCondition ( ) ;
private static Condition thirdRoom = lock. newCondition ( ) ;
private static Condition fourthRoom = lock. newCondition ( ) ;
private static Condition fifthRoom = lock. newCondition ( ) ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> printCharacter ( "a" , "b" , firstRoom, secondRoom) ) . start ( ) ;
new Thread ( ( ) -> printCharacter ( "b" , "c" , secondRoom, thirdRoom) ) . start ( ) ;
new Thread ( ( ) -> printCharacter ( "c" , "d" , thirdRoom, fourthRoom) ) . start ( ) ;
new Thread ( ( ) -> printCharacter ( "d" , "e" , fourthRoom, fifthRoom) ) . start ( ) ;
new Thread ( ( ) -> printCharacter ( "e" , "a" , fifthRoom, firstRoom) ) . start ( ) ;
}
private static void printCharacter ( String printChar, String targetChar, Condition waitRoom, Condition signalRoom) {
while ( true ) {
try {
lock. lock ( ) ;
if ( baseChar != printChar) {
waitRoom. await ( ) ;
}
System . out. println ( Thread . currentThread ( ) . getName ( ) + ":" + printChar) ;
baseChar = targetChar;
signalRoom. signal ( ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
} finally {
lock. unlock ( ) ;
}
}
}
}
2.3 park + unpark
package com. erick. multithread. d3 ;
import java. util. concurrent. locks. LockSupport ;
public class Demo06 {
private static String baseChar = "a" ;
private static Thread firstThread;
private static Thread secondThread;
private static Thread thirdThread;
private static Thread fourthThread;
private static Thread fifthThread;
public static void main ( String [ ] args) {
firstThread = new Thread ( ( ) -> printCharacter ( "a" , "b" , secondThread) ) ;
secondThread = new Thread ( ( ) -> printCharacter ( "b" , "c" , thirdThread) ) ;
thirdThread = new Thread ( ( ) -> printCharacter ( "c" , "d" , fourthThread) ) ;
fourthThread = new Thread ( ( ) -> printCharacter ( "d" , "e" , fifthThread) ) ;
fifthThread = new Thread ( ( ) -> printCharacter ( "e" , "a" , firstThread) ) ;
firstThread. start ( ) ;
secondThread. start ( ) ;
thirdThread. start ( ) ;
fourthThread. start ( ) ;
fifthThread. start ( ) ;
}
private static void printCharacter ( String printChar, String targetChar, Thread nextThread) {
while ( true ) {
if ( baseChar != printChar) {
LockSupport . park ( ) ;
}
System . out. println ( Thread . currentThread ( ) . getName ( ) + ":" + printChar) ;
baseChar = targetChar;
LockSupport . unpark ( nextThread) ;
}
}
}
3. 交替输出奇偶数
3.1 synchronized + wait + notify
package com. erick. multithread. d3 ;
public class Demo07 {
private static Object lock = new Object ( ) ;
private static int number = 0 ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> printNum ( ) , "t1" ) . start ( ) ;
new Thread ( ( ) -> printNum ( ) , "t2" ) . start ( ) ;
}
private static void printNum ( ) {
for ( int i = 0 ; i < 10 ; i++ ) {
synchronized ( lock) {
System . out. println ( Thread . currentThread ( ) . getName ( ) + ":" + number) ;
number++ ;
lock. notify ( ) ;
try {
lock. wait ( ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
}
}
}
}
3.2 reentrylock + await + signal
package com. erick. multithread. d3 ;
import java. util. concurrent. locks. Condition ;
import java. util. concurrent. locks. ReentrantLock ;
public class Demo08 {
private static int number = 0 ;
private static ReentrantLock lock = new ReentrantLock ( ) ;
private static Condition room = lock. newCondition ( ) ;
public static void main ( String [ ] args) {
new Thread ( ( ) -> printNum ( ) , "t1" ) . start ( ) ;
new Thread ( ( ) -> printNum ( ) , "t2" ) . start ( ) ;
}
private static void printNum ( ) {
for ( int i = 0 ; i < 10 ; i++ ) {
try {
lock. lock ( ) ;
System . out. println ( Thread . currentThread ( ) . getName ( ) + ":" + number) ;
number++ ;
room. signal ( ) ;
try {
room. await ( ) ;
} catch ( InterruptedException e) {
throw new RuntimeException ( e) ;
}
} finally {
lock. unlock ( ) ;
}
}
}
}
3.3 park + unpark
package com. erick. multithread. d3 ;
import java. util. concurrent. locks. LockSupport ;
public class Demo09 {
private static int number = 0 ;
private static Thread firstThread;
private static Thread secondThread;
public static void main ( String [ ] args) {
firstThread = new Thread ( ( ) -> printNum ( secondThread) ) ;
secondThread = new Thread ( ( ) -> printNum ( firstThread) ) ;
firstThread. start ( ) ;
secondThread. start ( ) ;
LockSupport . unpark ( firstThread) ;
}
private static void printNum ( Thread nextThread) {
for ( int i = 0 ; i < 10 ; i++ ) {
LockSupport . park ( ) ;
System . out. println ( Thread . currentThread ( ) . getName ( ) + ":" + number) ;
number++ ;
LockSupport . unpark ( nextThread) ;
}
}
}