本章节主要介绍ReentrantLock的可重入,可打断,锁超时, 公平性 等相关的知识点, 并且举了两个例子来演示ReentrantLock的用法。
可重入 定义 代码 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 package com.joshua.reentrant8;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.ReentrantDemo_1" )public class ReentrantDemo_1 { public static ReentrantLock lock = new ReentrantLock(); public static void main (String[] args) { lock.lock(); try { log.debug("entered main" ); m1(); } finally { lock.unlock(); } } private static void m1 () { lock.lock(); try { log.debug("entered m1" ); m2(); } finally { lock.unlock(); } } private static void m2 () { lock.lock(); try { log.debug("entered m2" ); } finally { lock.unlock(); } } }
测试 确实可重入,并且finally也是按倒序先执行m2的, 再执行m1的finnaly 最后main的finally释放锁。
可打断 定义 代码 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 package com.joshua.reentrant8;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.ReentrantDemo_2" )public class ReentrantDemo_2 { public static ReentrantLock lock = new ReentrantLock(); public static void main (String[] args) { Thread t1 = new Thread(() -> { try { log.debug("尝试获取锁" ); lock.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); log.debug("没有获取到锁,返回" ); return ; } try { log.debug("获取到锁" ); } finally { lock.unlock(); } }, "t1" ); t1.start(); } }
上面的代码是t1线程没有被其他线程打断,执行能正常输出:
1 2 09 :39 :09.375 [t1] DEBUG c.ReentrantDemo_2 - 尝试获取锁09 :39 :09.376 [t1] DEBUG c.ReentrantDemo_2 - 获取到锁
改造代码 添加打断的逻辑:
改造一: 加了打断
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 package com.joshua.reentrant8;import com.joshua.util.Sleeper;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.ReentrantDemo_2" )public class ReentrantDemo_2 { public static ReentrantLock lock = new ReentrantLock(); public static void main (String[] args) { Thread t1 = new Thread(() -> { try { log.debug("尝试获取锁" ); lock.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); log.debug("没有获取到锁,返回" ); return ; } try { log.debug("获取到锁" ); } finally { lock.unlock(); } }, "t1" ); lock.lock(); t1.start(); Sleeper.sleep(1_000 ); log.debug("即将打断t1" ); t1.interrupt(); } }
改造二: lock.lockInterruptibly(); 换成lock.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 package com.joshua.reentrant8;import com.joshua.util.Sleeper;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.ReentrantDemo_2" )public class ReentrantDemo_2 { public static ReentrantLock lock = new ReentrantLock(); public static void main (String[] args) { Thread t1 = new Thread(() -> { log.debug("尝试获取锁" ); lock.lock(); try { log.debug("获取到锁" ); } finally { lock.unlock(); } }, "t1" ); lock.lock(); t1.start(); Sleeper.sleep(1_000 ); log.debug("即将打断t1" ); t1.interrupt(); } }
测试 改造一 代码执行结果(可打断锁被打断,抛异常):
改造二 代码执行结果(产生死锁):
对比改造一和改造二的两点总结:
lock.lockInterruptibly()和lock.lock(); 前者可打断后者不可打断。
可打断锁会允许打断并抛异常,不可打断则容易进入死锁。
锁超时 lock.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 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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 package com.joshua.reentrant8;import com.joshua.util.Sleeper;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.TimeUnit;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.ReentrantDemo_3" )public class ReentrantDemo_3 { public static ReentrantLock lock = new ReentrantLock(); public static void main (String[] args) { t0(); } private static void t3 () { Thread t1 = new Thread(() -> { log.debug("尝试获取锁" ); try { if (!lock.tryLock(1 , TimeUnit.SECONDS)) { log.debug("没获取到" ); return ; } } catch (InterruptedException e) { e.printStackTrace(); } try { log.debug("拿到啦~~" ); } finally { lock.unlock(); } }, "t1" ); lock.lock(); log.debug("主线程获取了" ); t1.start(); lock.unlock(); } private static void t2 () { Thread t1 = new Thread(() -> { log.debug("尝试获取锁" ); try { if (!lock.tryLock(1 , TimeUnit.SECONDS)) { log.debug("没获取到" ); return ; } } catch (InterruptedException e) { e.printStackTrace(); } try { log.debug("拿到啦~~" ); } finally { lock.unlock(); } }, "t1" ); lock.lock(); log.debug("主线程获取了" ); t1.start(); } private static void t1 () { Thread t1 = new Thread(() -> { log.debug("尝试获取锁" ); if (!lock.tryLock()) { log.debug("没获取到" ); return ; } try { log.debug("拿到啦~~" ); } finally { lock.unlock(); } }, "t1" ); lock.lock(); log.debug("主线程获取了" ); t1.start(); } private static void t0 () { Thread t1 = new Thread(() -> { log.debug("尝试获取锁" ); if (!lock.tryLock()) { log.debug("没获取到" ); } try { log.debug("拿到啦~~" ); } finally { lock.unlock(); } }, "t1" ); t1.start(); } }
测试 t0测试结果: 说明lock.tryLock()是即时生效 的。 如果获取到,返回true, 反之false。
t1测试结果:因为主线程先上了锁 且一直没有释放, 所以没获取到
t2测试结果: !lock.tryLock(1, TimeUnit.SECONDS)表明先等1秒 , 如果1秒之内锁被其他线程释放, 则能获取到, 则返回true, 否则false。
t3测试结果:主线程释放了锁lock.unlock();
//Sleeper.sleep(2_000); 当然如果将t3的注释行打开, 那还是获取不到, 因为释放锁的时间超过了等待时间。
ReentrantLock解决哲学家就餐问题(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 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 86 87 package com.joshua.reentrant8;import com.joshua.util.Sleeper;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.ReentrantDemo_4" )public class ReentrantDemo_4 { public static ReentrantLock lock = new ReentrantLock(); public static void main (String[] args) { Chopstick c1 = new Chopstick("1" ); Chopstick c2 = new Chopstick("2" ); Chopstick c3 = new Chopstick("3" ); Chopstick c4 = new Chopstick("4" ); Chopstick c5 = new Chopstick("5" ); new Philosopher("苏格拉底" , c1, c2).start(); new Philosopher("柏拉图" , c2, c3).start(); new Philosopher("亚里士多德" , c3, c4).start(); new Philosopher("赫拉克利特" , c4, c5).start(); new Philosopher("阿基米德" , c5, c1).start(); } } @Slf 4j(topic = "c.Philosopher" )class Philosopher extends Thread { Chopstick left; Chopstick right; public Philosopher (String name, Chopstick left, Chopstick right) { super (name); this .left = left; this .right = right; } @Override public void run () { while (true ) { if (left.tryLock()) { try { if (right.tryLock()) { try { eat(); } finally { right.unlock(); } } } finally { left.unlock(); } } } } private void eat () { log.debug("双筷在手,开始干饭~~~~" ); Sleeper.sleep(100 ); } } @Slf 4j(topic = "c.Chopstick" )class Chopstick extends ReentrantLock { private String name; public Chopstick (String name) { this .name = name; } @Override public String toString () { return "Chopstick{" + "name='" + name + '\'' + '}' ; } }
结果
总结 哲学家就餐问题的根本在于大家各持有一只筷子(锁), 等待邻座释放(锁)。那么RenentrantLock的锁超时就可以控制获取不到筷子时将其释放掉。 该功能就避免了死锁。 而且这种方式优于之前改造加锁顺序new Philosopher("阿基米德", c5, c1).start();的方式。因为不会造成某个线程获取锁机会明显过大(小)的现象。
公平性 ReentrantLock 默认时非公平的, 可以通过调用有参构造修改为公平锁。 公平锁的意思是 会按照进入等待队列的顺序获取到锁资格。
源码掠影 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public ReentrantLock () { sync = new NonfairSync(); } public ReentrantLock (boolean fair) { sync = fair ? new FairSync() : new NonfairSync(); }
代码(不是总能复现仅供参考) 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 package com.joshua.reentrant8;import com.joshua.util.Sleeper;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.ReentrantDemo_5" )public class ReentrantDemo_5 { public static void main (String[] args) { ReentrantLock lock = new ReentrantLock(); lock.lock(); for (int i = 0 ; i < 5000 ; i++) { new Thread(() -> { lock.lock(); try { System.out.println(Sleeper.getThreadName()+"running..." ); }finally { lock.unlock(); } }, "t" +i).start(); } Sleeper.sleep(1_000 ); new Thread(() -> { System.out.println(Sleeper.getThreadName()+"start~~~~~~~~~~~~~~~~~~~~~~~" ); lock.lock(); try { System.out.println(Sleeper.getThreadName()+"running~~~~~~~~~~~~~~~~~~~~~~~" ); }finally { lock.unlock(); } }, "强行插入" ).start(); lock.unlock(); } }
ReentrantLock lock = new ReentrantLock(fairness); 如果是false , 则强行插入强行插入running~~~~~~~~~~~~~~~~~~~~~~~
可能在中间输出, 但是改成true 之后, 则一定在最后输出。不是总能复现仅供参考,后续源码会解释
多条件 使用 代码(对比wait-notify例子) 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 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 package com.joshua.reentrant8;import com.joshua.util.Sleeper;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.ReentrantDemo_6" )public class ReentrantDemo_6 { public static final Object room = new Object(); private static boolean hasCigar = false ; private static boolean foodDelivered = false ; static ReentrantLock lock = new ReentrantLock(); static Condition con1 = lock.newCondition(); static Condition con2 = lock.newCondition(); public static void main (String[] args) { new Thread(() -> { lock.lock(); try { log.debug("有烟没? [{}]" , hasCigar); while (!hasCigar) { log.debug("没外卖, 先歇会" ); try { con1.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("有烟没? [{}]" , hasCigar); if (hasCigar) { log.debug("可以开始干活了" ); } else { log.debug("没干成活" ); } } finally { lock.unlock(); } }, "小男" ).start(); new Thread(() -> { lock.lock(); try { log.debug("外卖送到没? [{}]" , foodDelivered); if (!foodDelivered) { log.debug("没外卖, 先歇会" ); try { con2.await(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("外卖送到没? [{}]" , foodDelivered); if (foodDelivered) { log.debug("可以开始干活了" ); } else { log.debug("没干成活" ); } } finally { lock.unlock(); } }, "小女" ).start(); Sleeper.sleep(1_000 ); new Thread(() -> { lock.lock(); try { foodDelivered = true ; log.debug("外卖到了哦" ); con2.signal(); } finally { lock.unlock(); } }, "送外卖的" ).start(); Sleeper.sleep(1_000 ); new Thread(() -> { lock.lock(); try { hasCigar = true ; log.debug("烟到了哦" ); con1.signal(); } finally { lock.unlock(); } }, "送烟的" ).start(); } }
测试结果
Synchronized和ReentrantLock对比 同步模式之顺序控制 假设现在有这样一个场景, 两个线程t1和t2分别输出“吃了嘛您?” 和 “吃啦, 您呢?”, 需要先执行t1,再执行t2。那么为了控制这种先后次序,就有人为干预,
方法一 wait-notify 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 package com.joshua.reentrant8;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.ReentrantDemo_1" )public class ReentrantDemo_7 { public static Object obj = new Object(); static boolean t1Runned = false ; public static void main (String[] args) { Thread t1 = new Thread(() -> { synchronized (obj) { log.debug("吃了嘛您嘞?" ); t1Runned = true ; obj.notify(); } }, "t1" ); Thread t2 = new Thread(() -> { synchronized (obj) { while (!t1Runned) { try { obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } log.debug("吃啦, 您呢" ); }, "t2" ); t1.start(); t2.start(); } }
方法一 结果
方法二 ReentrantLock 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 package com.joshua.reentrant8;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.ReentrantDemo_1" )public class ReentrantDemo_8 { public static ReentrantLock lock = new ReentrantLock(); static boolean t1Runned = false ; static Condition con = lock.newCondition(); public static void main (String[] args) { Thread t1 = new Thread(() -> { lock.lock(); try { log.debug("吃了嘛您嘞?" ); t1Runned = true ; con.signal(); } finally { lock.unlock(); } }, "t1" ); Thread t2 = new Thread(() -> { lock.lock(); try { while (!t1Runned) { try { con.await(); } catch (InterruptedException e) { e.printStackTrace(); } } } finally { lock.unlock(); } log.debug("吃啦, 您呢" ); }, "t2" ); t1.start(); t2.start(); } }
方法二 结果
方法三 park-unpark 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 package com.joshua.reentrant8;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.LockSupport;@Slf 4j(topic = "c.ReentrantDemo_1" )public class ReentrantDemo_9 { public static void main (String[] args) { Thread t2 = new Thread(() -> { LockSupport.park(); log.debug("吃啦, 您呢" ); }, "t2" ); Thread t1 = new Thread(() -> { log.debug("吃了嘛您嘞?" ); LockSupport.unpark(t2); }, "t1" ); t1.start(); t2.start(); } }
方法三 结果
交替输出需求 需求 :
Wait-Notify 实现 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 package com.joshua.reentrant8;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.LockSupport;@Slf 4j(topic = "c.ReentrantDemo_10" )public class ReentrantDemo_10 { public static void main (String[] args) { WaitNotify wn = new WaitNotify(1 , 5 ); new Thread(() -> { wn.print("a" ,1 ,2 ); }, "t1" ).start(); new Thread(() -> { wn.print("b" ,2 ,3 ); }, "t2" ).start(); new Thread(() -> { wn.print("c" ,3 ,1 ); }, "t3" ).start(); } } class WaitNotify { private int flag; private int loopNumber; public WaitNotify (int flag, int loopNumber) { this .flag = flag; this .loopNumber = loopNumber; } public void print (String str, int waitFlag, int nextFlag) { for (int i = 0 ; i < loopNumber; i++) { synchronized (this ) { while (flag != waitFlag) { try { this .wait(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.print(str); flag = nextFlag; this .notifyAll(); } } } }
await-signal实现 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 package com.joshua.reentrant8;import com.joshua.util.Sleeper;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.ReentrantDemo_11" )public class ReentrantDemo_11 { public static void main (String[] args) { AwaitSignal awaitSignal = new AwaitSignal(5 ); Condition a = awaitSignal.newCondition(); Condition b = awaitSignal.newCondition(); Condition c = awaitSignal.newCondition(); new Thread(() -> { awaitSignal.print("a" , a, b); }, "t1" ).start(); new Thread(() -> { awaitSignal.print("b" , b, c); }, "t2" ).start(); new Thread(() -> { awaitSignal.print("c" , c, a); }, "t3" ).start(); Sleeper.sleep(1_000 ); awaitSignal.lock(); try { log.debug("开始" ); a.signal(); } finally { awaitSignal.unlock(); } } } class AwaitSignal extends ReentrantLock { private int loopNumber; public AwaitSignal (int loopNumber) { this .loopNumber = loopNumber; } public void print (String str, Condition current, Condition next) { for (int i = 0 ; i < loopNumber; i++) { lock(); try { current.await(); System.out.print(str); next.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { unlock(); } } } }
注意⚠️: 体会为什么在try后第一行就current.await();
如果主线程唤醒的代码从a.signal();改成b.signal(); 则结果会变成bca连续五次:
park-unpark实现 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 package com.joshua.reentrant8;import com.joshua.util.Sleeper;import lombok.extern.slf4j.Slf4j;import java.util.concurrent.locks.Condition;import java.util.concurrent.locks.LockSupport;import java.util.concurrent.locks.ReentrantLock;@Slf 4j(topic = "c.ReentrantDemo_12" )public class ReentrantDemo_12 { static Thread t1; static Thread t2; static Thread t3; public static void main (String[] args) { ParkUnpark pup = new ParkUnpark(5 ); t1= new Thread(() -> { pup.print("a" ,t2); }, "t1" ); t2= new Thread(() -> { pup.print("b" ,t3); }, "t2" ); t3= new Thread(() -> { pup.print("c" ,t1); }, "t3" ); t1.start(); t2.start(); t3.start(); log.debug("开始" ); LockSupport.unpark(t1); } } class ParkUnpark { private int loopNumber; public ParkUnpark (int loopNumber) { this .loopNumber = loopNumber; } public void print (String str,Thread next) { for (int i = 0 ; i < loopNumber; i++) { LockSupport.park(); System.out.print(str); LockSupport.unpark(next); } } }