ReentrantLock
相对于 synchronized 它具备如下特点
可中断、可以设置超时时间、可以设置为公平锁(防止线程饥饿)、支持多个条件变量
与 synchronized 一样,都支持可重入
基本语法
// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}
可重入
可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁,如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住
@Slf4j(topic = "c.TestReentrantLock")
public class TestReentrantLock {
/*// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}*/
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) {
lock.lock();
try {
log.debug("进入main");
m1();
}finally {
lock.unlock();
}
}
public static void m1(){
lock.lock();
try {
log.debug("进入m1");
m2();
}finally {
lock.unlock();
}
}
public static void m2(){
lock.lock();
try {
log.debug("进入m2");
}finally {
lock.unlock();
}
}
}
15:12:05 [main] c.TestReentrantLock - 进入main
15:12:05 [main] c.TestReentrantLock - 进入m1
15:12:05 [main] c.TestReentrantLock - 进入m2
可打断
@Slf4j(topic = "c.Test20")
public class Test20 {
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1= new Thread(()->{
try {
//如果有竞争会进入锁队列,可以用其他线程用interrupt方法打断
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();
}
}
15:20:07 [t1] c.Test20 - 尝试获取锁
15:20:08 [main] c.Test20 - 打断t1
15:20:08 [t1] c.Test20 - 没有获得锁,返回
锁超时
lock.trylock()里面可以带参数,来指示它等待锁的时间,没有参数的话就立即失败
@Slf4j(topic = "c.Test20")
public class Test20 {
private static ReentrantLock lock=new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1= new Thread(()->{
log.debug("尝试获取锁");
try {
if(!lock.tryLock(2, TimeUnit.SECONDS)){
log.debug("获取不到锁");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("获取不到锁");
}
try {
log.debug("获取到锁");
}finally {
lock.unlock();
}
},"t1");
lock.lock();
log.debug("获取到锁");
t1.start();
Thread.sleep(1000);
lock.unlock();
log.debug("释放了锁");
}
}
15:26:41 [main] c.Test20 - 获取到锁
15:26:41 [t1] c.Test20 - 尝试获取锁
15:26:42 [main] c.Test20 - 释放了锁
15:26:42 [t1] c.Test20 - 获取到锁
锁超时解决哲学家就餐(奈斯)
@Slf4j(topic = "c.PhilosophersEat")
public class PhilosophersEat {
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();
}
}
class Chopstick extends ReentrantLock {
String name;
public Chopstick(String name) {
this.name = name;
}
@Override
public String toString() {
return "筷子{" + name + '}';
}
}
@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;
}
private void eat() throws InterruptedException {
log.debug("eating...");
Thread.sleep(1000);
}
@Override
public void run() {
while (true) {
// 获得左手筷子
if(left.tryLock()) {
try {
// 获得右手筷子
if (right.tryLock()) {
try {
eat();
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
right.unlock();
}
}
} finally {
//如果获取右手筷子的时候失败了,会放下左手筷子
left.unlock();
}
}
}
}
}
公平锁(先入先得)
ReentrantLock默认时不公平的,但是可以修改构造方法变成公平锁
公平锁一般没有必要,会降低并发度
条件变量
synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待
ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比
synchronized 是那些不满足条件的线程都在一间休息室等消息而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
使用要点:
await 前需要获得锁
await 执行后,会释放锁,进入 conditionObject 等待
await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
竞争 lock 锁成功后,从 await 后继续执行
之前那个送烟送外卖得进一步例子
@Slf4j(topic = "c.Test21")
public class Test21 {
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 InterruptedException {
new Thread(() -> {
ROOM.lock();
try {
log.debug("有烟没?[{}]",hasCigarette);
while (!hasCigarette){
log.debug("没有烟,先歇会儿");
waitCigaretteSet.await();
}
log.debug("有烟吗?[{}]",hasCigarette);
if(hasCigarette){
log.debug("可以开始干活了");
}else{
log.debug("没干成活");
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} 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("可以开始干活了");
} else {
log.debug("没干成活...");
}
}finally {
ROOM.unlock();
}
}, "小女").start();
sleep(1000);
new Thread(() -> {
ROOM.lock();
try {
log.debug("烟送到了");
hasCigarette=true;
waitCigaretteSet.signal();
}finally {
ROOM.unlock();
}
}, "送烟的").start();
new Thread(() -> {
ROOM.lock();
try {
log.debug("外卖送到了");
hasTakeout=true;
waitTakeoutSet.signal();
}finally {
ROOM.unlock();
}
}, "送外卖的").start();
}
}
15:58:29 [小南] c.Test21 - 有烟没?[false]
15:58:29 [小南] c.Test21 - 没有烟,先歇会儿
15:58:29 [小女] c.Test21 - 外卖送到没?[false]
15:58:29 [小女] c.Test21 - 没外卖,先歇会!
15:58:30 [送烟的] c.Test21 - 烟送到了
15:58:30 [送外卖的] c.Test21 - 外卖送到了
15:58:30 [小南] c.Test21 - 有烟吗?[true]
15:58:30 [小南] c.Test21 - 可以开始干活了
15:58:30 [小女] c.Test21 - 外卖送到没?[true]
15:58:30 [小女] c.Test21 - 可以开始干活了
同步模式之顺序控制
1.固定顺序
比如保证t2先运行再运行t1
wait-notify法
@Slf4j(topic = "c.Test22")
public class Test22 {
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) {
throw new RuntimeException(e);
}
}
log.debug("1");
}
},"t1");
Thread t2= new Thread(()->{
synchronized (lock){
log.debug("2");
t2runned=true;
lock.notify();
}
},"t2");
t1.start();
t2.start();
}
}
ReentrantLock中的await-signal法,大同小异纯当练手
@Slf4j(topic = "c.Test22")
public class Test22 {
static ReentrantLock lock=new ReentrantLock();
//表示t2是否运行过
static boolean t2runned=false;
static Condition condition=lock.newCondition();
public static void main(String[] args) {
Thread t1= new Thread(()->{
lock.lock();
try {
while(!t2runned){
condition.await();
}
} catch (InterruptedException e) {
throw new RuntimeException(e);
} finally {
lock.unlock();
}
log.debug("1");
},"t1");
Thread t2= new Thread(()->{
lock.lock();
try {
log.debug("2");
t2runned=true;
condition.signal();
}finally {
lock.unlock();
}
},"t2");
t1.start();
t2.start();
}
}
park-unpark法,这个写起来要简单一些
@Slf4j(topic = "c.Test23")
public class Test23 {
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();
}
}
2.交替输出
线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现
wait-notify实现,通过整数标记来控制
@Slf4j(topic = "c.Test24")
public class Test24 {
public static void main(String[] args) {
WaitNotify wn=new WaitNotify(1,5);
new Thread(()->{
try {
wn.print("a",1,2);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(()->{
try {
wn.print("b",2,3);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
new Thread(()->{
try {
wn.print("c",3,1);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}).start();
}
}
class WaitNotify{
public void print(String str,int waitFlag,int nextFlag) throws InterruptedException {
for (int i=0;i<loopNumber;i++){
synchronized (this){
while(flag!=waitFlag){
this.wait();
}
System.out.print(str);
flag=nextFlag;
this.notifyAll();
}
}
}
//等待标记
private int flag;
//循环次数
private int loopNumber;
public WaitNotify(int flag,int loopNumber){
this.flag=flag;
this.loopNumber=loopNumber;
}
}
Lock 条件变量实现
@Slf4j(topic ="c.Test25")
public class Test25 {
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(()->{
try {
awaitSignal.print("a",a,b);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"1").start();
new Thread(()->{
try {
awaitSignal.print("b",b,c);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"2").start();
new Thread(()->{
try {
awaitSignal.print("c",c,a);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
},"3").start();
Thread.sleep(1000);
awaitSignal.lock();
try {
System.out.println("开始:");
//先唤醒线程1来打印a
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) throws InterruptedException {
for(int i=0;i<loopNumber;i++){
lock();
try {
current.await();
System.out.print(str);
next.signal();
}finally {
unlock();
}
}
}
}
Park Unpark 实现
@Slf4j(topic = "c.Test26")
public class Test26 {
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);
},"t1");
t2=new Thread(()->{
pu.print("b",t3);
},"t2");
t3=new Thread(()->{
pu.print("c",t1);
},"t3");
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;
}
}
小结
本章我们需要重点掌握的是
分析多线程访问共享资源时,哪些代码片段属于临界区
使用 synchronized 互斥解决临界区的线程安全问题
掌握 synchronized 锁对象语法
掌握 synchronzied 加载成员方法和静态方法语法
掌握 wait/notify 同步方法
使用 lock 互斥解决临界区的线程安全问题
掌握 lock 的使用细节:可打断、锁超时、公平锁、条件变量
学会分析变量的线程安全性、掌握常见线程安全类的使用
了解线程活跃性问题:死锁、活锁、饥饿
应用方面
互斥:使用 synchronized 或 Lock 达到共享资源互斥效果
同步:使用 wait/notify 或 Lock 的条件变量来达到线程间通信效果
原理方面
monitor、synchronized 、wait/notify 原理
synchronized 进阶原理
park & unpark 原理
模式方面
同步模式之保护性暂停
异步模式之生产者消费者
同步模式之顺序控制