相对于 synchronized 具备如下特定:
(1)可中断
(2)可以设置超市时间
(3)可以设置为公平锁
(4)支持多个条件变量
与 synchronized 一样,都支持可重入
基本语法
一、可重入(P121)
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁。
如果是不可重入,那么第二次获得锁时,自己也会被锁挡住。
@Slf4j(topic = "c.Test22") public class Test22 { private static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { lock.lock(); try { log.debug("enter main"); m1(); } finally { lock.unlock(); } } public static void m1(){ lock.lock(); try { log.debug("enter m1"); m2(); } finally { lock.unlock(); } } public static void m2(){ lock.lock(); try { log.debug("enter m2"); } finally { lock.unlock(); } } }
二、可打断
@Slf4j(topic = "c.Test22") public class Test22 { private static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { try { //如果没有竞争那么此方法就会获取lock对象锁 //如果有竞争就进入阻塞队列,可以被其它线程用interruput方法打断 log.debug("尝试获取锁"); lock.lockInterruptibly(); } catch (InterruptedException e) { e.printStackTrace(); log.debug("没有获得锁,返回"); return; } try { log.debug("获取到锁"); } finally { lock.unlock(); } },"t1"); lock.lock(); t1.start(); Thread.sleep(1000); log.debug("打断 t1"); t1.interrupt(); } }
注意如果是 不可中断(lock()方法) 模式,那么即使使用了 interrupt 也不会让等待中断
三、锁超时
@Slf4j(topic = "c.Test22") public class Test22 { private static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { log.debug("尝试获取锁"); // 立刻尝试获取锁 boolean tryLock = lock.tryLock(); if (!tryLock){ log.debug("获取不到锁"); return; } try { log.debug("获得到锁"); } finally { lock.unlock(); } },"t1"); log.debug("获取锁"); lock.lock(); t1.start(); } }
@Slf4j(topic = "c.Test22") public class Test22 { private static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(() -> { log.debug("尝试获取锁"); try { // 2s后尝试获取锁 boolean tryLock = lock.tryLock(2, TimeUnit.SECONDS); if (!tryLock){ log.debug("获取不到锁"); return; } } catch (InterruptedException e) { e.printStackTrace(); log.debug("获取不到锁"); return; } try { log.debug("获得到锁"); } finally { lock.unlock(); } },"t1"); log.debug("获取锁"); lock.lock(); t1.start(); } }
使用 tryLock 解决哲学家就餐问题@Slf4j(topic = "c.Test23") public class Test23 {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(); } } @Slf4j(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(); // 释放自己手里的筷子 } } } } Random random = new Random(); private void eat() { log.debug("eating..."); Sleeper.sleep(0.5); } } class Chopstick extends ReentrantLock { String name; public Chopstick(String name) { this.name = name; } @Override public String toString() { return "筷子{" + name + '}'; } }
四、公平锁
ReentrantLock 默认是不公平的
// true:公平 // false(默认):不公平 ReentrantLock lock = new ReentrantLock(true);
公平锁一般没有必要,会降低并发度,后面分析原理时会讲解。
五、条件变量
synchronized 中也有条件变量,就是我们讲原理的 waitSet 休息室,当条件不满足时进入等待。
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比:
(1)synchronized 是那些不满足条件的线程都在一间休息室等消息
(2)而 ReentrantLock 支持多间休息室,唤醒时也是按休息室来唤醒的
使用要点:(1)await 前需要获得锁
(2)await 执行后,会释放锁,进入 conditionObject 等待
(3)await 的线程被唤醒(或打断、或超时)去重新竞争 lock 锁
(4)竞争 lock 锁成功后,从 await 后继续执行
@Slf4j(topic = "c.Test22") public class Test22 { private static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) throws InterruptedException { // 创建一个新的条件变量(休息室) Condition condition1 = lock.newCondition(); Condition condition2 = lock.newCondition(); lock.lock(); // 进入休息室等待 condition1.await(); condition1.signal(); condition1.signalAll(); } }
@Slf4j(topic = "c.Test24") public class Test24 { 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) { new Thread(() -> { ROOM.lock(); try { log.debug("有烟没?[{}]", hasCigarette); while (!hasCigarette) { log.debug("没烟,先歇会!"); try { waitCigaretteSet.await(); } catch (InterruptedException e) { e.printStackTrace(); } } 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("可以开始干活了"); } finally { ROOM.unlock(); } }, "小女").start(); sleep(1); new Thread(() -> { ROOM.lock(); try { hasTakeout = true; waitTakeoutSet.signal(); } finally { ROOM.unlock(); } }, "送外卖的").start(); sleep(1); new Thread(() -> { ROOM.lock(); try { hasCigarette = true; waitCigaretteSet.signal(); } finally { ROOM.unlock(); } }, "送烟的").start(); } }
六、同步模式之顺序控制
1. 固定运行顺序
1.1 wait notify 版
@Slf4j(topic = "c.Test25") public class Test25 { static final Object lock = new Object(); // 表示 t2 是否运行过 static boolean t2runned = false; public static void main(String[] args) { Thread t1 = new Thread(() -> { synchronized (lock) { while (!t2runned) { try { lock.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } log.debug("1"); } }, "t1"); Thread t2 = new Thread(() -> { synchronized (lock) { log.debug("2"); t2runned = true; lock.notify(); } }, "t2"); t1.start(); t2.start(); } }
1.2 Park Unpark 版
@Slf4j(topic = "c.Test26") public class Test26 { public static void main(String[] args) { Thread t1 = new Thread(() -> { LockSupport.park(); log.debug("1"); }, "t1"); t1.start(); new Thread(() -> { log.debug("2"); LockSupport.unpark(t1); },"t2").start(); } }
park 和 unpark 方法比较灵活,他俩谁先调用,谁后调用无所谓。并且是以线程为单位进行『暂停』和『恢复』, 不需要『同步对象』和『运行标记』
2. 交替输出
线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现
2.1 wait notify 版
@Slf4j(topic = "c.Test27") public class Test27 { public static void main(String[] args) { WaitNotify wn = new WaitNotify(1, 5); new Thread(() -> { wn.print("a", 1, 2); }).start(); new Thread(() -> { wn.print("b", 2, 3); }).start(); new Thread(() -> { wn.print("c", 3, 1); }).start(); } } /* 输出内容 等待标记 下一个标记 a 1 2 b 2 3 c 3 1 */ class WaitNotify { // 打印 a 1 2 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(); } } } // 等待标记 private int flag; // 2 // 循环次数 private int loopNumber; public WaitNotify(int flag, int loopNumber) { this.flag = flag; this.loopNumber = loopNumber; } }
2.2 Lock 条件变量版
public class Test30 { public static void main(String[] args) throws InterruptedException { AwaitSignal awaitSignal = new AwaitSignal(5); Condition a = awaitSignal.newCondition(); Condition b = awaitSignal.newCondition(); Condition c = awaitSignal.newCondition(); new Thread(() -> { awaitSignal.print("a", a, b); }).start(); new Thread(() -> { awaitSignal.print("b", b, c); }).start(); new Thread(() -> { awaitSignal.print("c", c, a); }).start(); Thread.sleep(1000); awaitSignal.lock(); try { System.out.println("开始..."); a.signal(); } finally { awaitSignal.unlock(); } } } class AwaitSignal extends ReentrantLock{ private int loopNumber; public AwaitSignal(int loopNumber) { this.loopNumber = loopNumber; } // 参数1 打印内容, 参数2 进入哪一间休息室, 参数3 下一间休息室 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(); } } } }
2.3 Park Unpark 版
@Slf4j(topic = "c.Test31") public class Test31 { static Thread t1; static Thread t2; static Thread t3; public static void main(String[] args) { ParkUnpark pu = new ParkUnpark(5); t1 = new Thread(() -> { pu.print("a", t2); }); t2 = new Thread(() -> { pu.print("b", t3); }); t3 = new Thread(() -> { pu.print("c", t1); }); t1.start(); t2.start(); t3.start(); LockSupport.unpark(t1); } } class ParkUnpark { public void print(String str, Thread next) { for (int i = 0; i < loopNumber; i++) { LockSupport.park(); System.out.print(str); LockSupport.unpark(next); } } private int loopNumber; public ParkUnpark(int loopNumber) { this.loopNumber = loopNumber; } }