ReentrantLock
,相对于 Synchronized
有以下特点
可中断
可以设置超时时间
可以设置为公平锁
支持多个变量条件
与 Synchronized
一样都支持可重入。
基本语法 1 2 3 4 5 6 7 8 reentrantLock.lock (); try { }finally { reentrantLock.unlock(); }
可重入 lock() 可重入是指同一个线程如果首次获得了这把锁,那么他就是锁的拥有者,因此有权利再次获取这把锁。
如果是不可重入,那么,第二次获得锁时,自己也会被锁住。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.redisc;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.Run" )public class Run { private static ReentrantLock reentrantLock = new ReentrantLock(); public static void main (String[] args) throws Exception { reentrantLock.lock(); try { log.debug("主方法" ); m1(); } finally { reentrantLock.unlock(); } } public static void m1 () { reentrantLock.lock(); try { log.debug("m1" ); m2(); } finally { reentrantLock.unlock(); } } public static void m2 () { reentrantLock.lock(); try { log.debug("m2" ); } finally { reentrantLock.unlock(); } } }
输出
1 2 3 10:49 :53.026 [main] DEBUG c .Run - 主方法 10:49 :53.028 [main] DEBUG c .Run - m1 10:49 :53.028 [main] DEBUG c .Run - m2
重复加锁了 3
次。
可打断 lockInterruptibly() 可打断主要是防止死锁出现。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 package com.redisc; import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf4j(topic = "c.Run" ) public class Run { private static ReentrantLock reentrantLock = new ReentrantLock(); public static void main(String[] args){ Thread t1 = new Thread(() -> { try { // 如果没有竞争那么此方法就会获取 lock 对象锁 // 如果有竞争进入阻塞队列,那么可以被其他线程用 interrupt 方法打断 reentrantLock.lockInterruptibly(); }catch (InterruptedException e){ e.printStackTrace(); return ; } try { log.debug("获取到锁" ); }finally { reentrantLock.unlock(); } },"t1" ) ; t1 .start () ; } }
但是,我们让主线程先把锁加上。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 package com.redisc; import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf4j(topic = "c.Run" ) public class Run { private static ReentrantLock reentrantLock = new ReentrantLock(); public static void main(String[] args){ Thread t1 = new Thread(() -> { try { // 如果没有竞争那么此方法就会获取 lock 对象锁 // 如果有竞争进入阻塞队列,那么可以被其他线程用 interrupt 方法打断 reentrantLock.lockInterruptibly(); }catch (InterruptedException e){ e.printStackTrace(); return ; } try { log.debug("获取到锁" ); }finally { reentrantLock.unlock(); } },"t1" ) ; reentrantLock .lock () ; t1 .start () ; } }
没有输出。进行打断
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 package com.redisc; import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf4j(topic = "c.Run" ) public class Run { private static ReentrantLock reentrantLock = new ReentrantLock(); public static void main(String[] args) { Thread t1 = new Thread(() -> { try { // 如果没有竞争那么此方法就会获取 lock 对象锁 // 如果有竞争进入阻塞队列,那么可以被其他线程用 interrupt 方法打断 reentrantLock.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); log.debug("没有获得锁" ); return ; } try { log.debug("获取到锁" ); } finally { reentrantLock.unlock(); } }, "t1" ) ; reentrantLock .lock () ; t1 .start () ; t1 .interrupt () ; } }
输出
1 2 3 4 5 6 java.lang .InterruptedException at java.util .concurrent .locks .AbstractQueuedSynchronizer .acquireInterruptibly(AbstractQueuedSynchronizer.java :1220 ) at java.util .concurrent .locks .ReentrantLock .lockInterruptibly(ReentrantLock.java :335 ) at com.redisc .Run .lambda$main $0 (Run.java :17 ) at java.lang .Thread .run(Thread.java :748 ) 11 :35 :30.715 [t1] DEBUG c.Run - 没有获得锁
锁超时 tryLock() 无参数,是立刻尝试获得锁。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 package com.redisc; import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf4j(topic = "c.Run" ) public class Run { private static ReentrantLock reentrantLock = new ReentrantLock(); public static void main(String[] args) { Thread t1 = new Thread(() -> { // 如果成功,则会获得 // 如果失败,则下次继续等待获得锁 if (!reentrantLock.tryLock()) { log.debug("获取不到锁" ); return ; } try { log.debug("获取到锁" ); } finally { reentrantLock.unlock(); } }, "t1" ) ; t1 .start () ; } }
进行主线程获得锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 package com.redisc; import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf4j(topic = "c.Run" ) public class Run { private static ReentrantLock reentrantLock = new ReentrantLock(); public static void main(String[] args) { Thread t1 = new Thread(() -> { // 如果成功,则会获得 // 如果失败,则下次继续等待获得锁 if (!reentrantLock.tryLock()) { log.debug("获取不到锁" ); return ; } try { log.debug("获取到锁" ); } finally { reentrantLock.unlock(); } }, "t1" ) ; reentrantLock .lock () ; t1 .start () ; } }
输出
1 12:04 :18.223 [t1] DEBUG c .Run - 获取不到锁
参数超时机制,有参数,是在参数时间内尝试获得锁
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 package com.redisc; import lombok.extern.slf4j.Slf4j;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReentrantLock;@Slf4j(topic = "c.Run" ) public class Run { private static ReentrantLock reentrantLock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { // 如果成功,则会获得 // 如果失败,则下次继续等待获得锁 try { if (!reentrantLock.tryLock(2 , TimeUnit.SECONDS)) { log.debug("获取不到锁" ); return ; } } catch (InterruptedException e) { e.printStackTrace(); } try { log.debug("获取到锁" ); } finally { reentrantLock.unlock(); } }, "t1" ) ; log .debug ("获取锁" ) ; reentrantLock .lock () ; t1 .start () ; Thread .sleep (1000 ) ; reentrantLock .unlock () ; } }
输出
1 2 12:06 :26.150 [main] DEBUG c .Run - 获取锁 12:06 :27.154 [t1] DEBUG c .Run - 获取到锁
公平锁 ReentrantLock
默认情况和 Synchronized
都是不公平锁。
不公平指的是,当锁释放后,所有的线程又处于同一地位来争抢锁,导致有的线程可能一直抢不到。
而 ReentrantLock
通过构造参数传入 True
来设计成公平锁。
公平锁会降低并发,一般用 trylock
就好了。
条件变量
Synchronized 也有条件变量,就是 waitset 中的休息室。当条件不满足的时候,进入 waitset 等待
ReentrantLock 的条件变量比 Synchronized 强大之处,在于它是支持多个条件变量的
Synchronized 是那些不满足条件的线程都在一间休息室等消息
ReentrantLock 支持多间休息室
使用流程
await 前获得锁
await 执行后,会释放锁,进入 conditionObject 等待
await 的线程被唤醒「signal、signalAll」(或打断、超时)取重新竞争 lock 锁
竞争 lock 锁成功后,从 await 后继续执行
对 java | wait && notify 正确姿势 | 把 if 换成 while 解决虚假唤醒 | 进行优化
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 package com.redisc; import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;@Slf4j(topic = "c.Run" ) public class Run { static final Object room = new Object(); static boolean hasCigarette = false ; static boolean hasTakeout = false ; static ReentrantLock ROOM = new ReentrantLock(); static Condition waitCigaretteSet = ROOM.newCondition(); static Condition waitTakeoutSet = ROOM.newCondition(); public static void main(String[] args) throws Exception { new Thread(() -> { ROOM.lock(); try { log.debug("有烟没1?[{}]" , hasCigarette); while (!hasCigarette) { log.debug("没烟,先歇会" ); try { waitCigaretteSet.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有烟没2?[{}]" , hasCigarette); if (hasCigarette) { log.debug("可以干活了" ); } } finally { ROOM.unlock(); } }, "小南" ) .start () ; new Thread (() -> { ROOM.lock(); try { log.debug("有外卖没?[{}]" , hasTakeout); while (!hasTakeout) { log.debug("没外卖,先歇会" ); try { waitTakeoutSet.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有外卖没?[{}]" , hasTakeout); if (hasTakeout) { log.debug("可以干活了" ); } } finally { ROOM.unlock(); } }, "小女" ) .start () ; for (int i = 0 ; i < 5 ; i++) { new Thread (() -> { synchronized (room) { log.debug("可以干活了" ); } }, "其他人" ) .start () ; } Thread .sleep (1000 ) ; new Thread (() -> { ROOM.lock(); try { hasCigarette = true ; log.debug("烟到了" ); waitCigaretteSet.signal(); } finally { ROOM.unlock(); } }, "送烟的" ) .start () ; } }
输出
1 2 3 4 5 6 7 8 9 10 11 12 12:39 :59.230 [其他人] DEBUG c .Run - 可以干活了 12:39 :59.230 [小南] DEBUG c .Run - 有烟没1?[false] 12:39 :59.233 [小南] DEBUG c .Run - 没烟,先歇会 12:39 :59.233 [其他人] DEBUG c .Run - 可以干活了 12:39 :59.233 [其他人] DEBUG c .Run - 可以干活了 12:39 :59.233 [小女] DEBUG c .Run - 有外卖没?[false] 12:39 :59.233 [其他人] DEBUG c .Run - 可以干活了 12:39 :59.233 [小女] DEBUG c .Run - 没外卖,先歇会 12:39 :59.233 [其他人] DEBUG c .Run - 可以干活了 12:40 :00.230 [送烟的] DEBUG c .Run - 烟到了 12:40 :00.231 [小南] DEBUG c .Run - 有烟没2?[true] 12:40 :00.231 [小南] DEBUG c .Run - 可以干活了