4.13 ReentrantLock

相对于 synchronized 它具备如下特点

  1. 可中断
  2. 可以设置超时时间
  3. 可以设置为公平锁
  4. 支持多个条件变量,即对与不满足条件的线程可以放到不同的集合中等待

与 synchronized 一样,都支持可重入

基本语法

// 获取锁
reentrantLock.lock();
try {
// 临界区
} finally {
// 释放锁
reentrantLock.unlock();
}

可重入

可重入是指同一个线程如果首次获得了这把锁,那么因为它是这把锁的拥有者,因此有权利再次获取这把锁

如果是不可重入锁,那么第二次获得锁时,自己也会被锁挡住

可打断

lock 与 lockInterruptibly比较区别在于:
lock 优先考虑获取锁,待获取锁成功后,才响应中断。
lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。

锁超时

try lock用的是保护性暂停模式

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import java.sql.Time;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock; import static cn.itcast.n2.util.Sleeper.sleep; @Slf4j(topic = "c.Test22")
public class Test22 {
private static ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
log.debug("尝试获得锁");
try {
if (! lock.tryLock(2, TimeUnit.SECONDS)) {
log.debug("获取不到锁");
return;
}
} catch (InterruptedException e) {
e.printStackTrace();
log.debug("获取不到锁");
return;
}
try {
log.debug("获得到锁");
} finally {
lock.unlock();
}
}, "t1"); lock.lock();
log.debug("获得到锁");
t1.start();
sleep(1);
log.debug("释放了锁");
lock.unlock();
} }

使用 tryLock 解决哲学家就餐问题,给左右手加入reentrantlock,用trylock方法尝试获得锁,再释放

package cn.itcast.test;

import cn.itcast.n2.util.Sleeper;
import lombok.extern.slf4j.Slf4j; import java.util.Random;
import java.util.concurrent.locks.ReentrantLock; @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 默认是不公平的

公平锁一般没有必要,会降低并发度,后面分析原理时会讲解

条件变量

synchronized 中也有条件变量,就是我们讲原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待

ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比

  1. synchronized 是那些不满足条件的线程都在一间休息室等消息
  2. 而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤 醒

使用要点: Test24.java

  1. await 前需要获得锁
  2. await 执行后,会释放锁,进入 conditionObject 等待
  3. await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁,执行唤醒的线程爷必须先获得锁
  4. 竞争 lock 锁成功后,从 await 后继续执行
package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; import static cn.itcast.n2.util.Sleeper.sleep; @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. 固定运行顺序

比如,必须先 2 后 1 打印

1.1 wait notify 版

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@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 版

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

@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();
}
}

交替输出

线程 1 输出 a 5 次,线程 2 输出 b 5 次,线程 3 输出 c 5 次。现在要求输出 abcabcabcabcabc 怎么实现

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

@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;
}
}
package cn.itcast.test;

import sun.rmi.runtime.Log;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock; 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();
}
}
}
}

各自线程进入各自的休息室 ,然后再接受唤醒开启下一间休息室

package cn.itcast.test;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.LockSupport;

@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;
}
}

本章小结

本章我们需要重点掌握的是

  1. 分析多线程访问共享资源时,哪些代码片段属于临界区
  2. 使用 synchronized 互斥解决临界区的线程安全问题
    1. 掌握 synchronized 锁对象语法
    2. 掌握 synchronzied 加载成员方法和静态方法语法
    3. 掌握 wait/notify 同步方法
  3. 使用 lock 互斥解决临界区的线程安全问题 掌握 lock 的使用细节:可打断、锁超时、公平锁、条件变量
  4. 学会分析变量的线程安全性、掌握常见线程安全类的使用
  5. 了解线程活跃性问题:死锁、活锁、饥饿
  6. 应用方面
    1. 互斥:使用 synchronized 或 Lock 达到共享资源互斥效果,实现原子性效果,保证线程安全。
    2. 同步:使用 wait/notify 或 Lock 的条件变量来达到线程间通信效果。
  7. 原理方面
    1. monitor、synchronized 、wait/notify 原理
    2. synchronized 进阶原理
    3. park & unpark 原理
  8. 模式方面
    1. 同步模式之保护性暂停
    2. 异步模式之生产者消费者
    3. 同步模式之顺序控制

并发编程(ReentrantLock&&同步模式之顺序控制)的更多相关文章

  1. Java并发编程:同步容器

    Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...

  2. 【转】Java并发编程:同步容器

    为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch).今天我们就来讨论下同步容器. ...

  3. 8、Java并发编程:同步容器

    Java并发编程:同步容器 为了方便编写出线程安全的程序,Java里面提供了一些线程安全类和并发工具,比如:同步容器.并发容器.阻塞队列.Synchronizer(比如CountDownLatch). ...

  4. 【并发编程】Future模式添加Callback及Promise 模式

    Future Future是Java5增加的类,它用来描述一个异步计算的结果.你可以使用 isDone 方法检查计算是否完成,或者使用 get 方法阻塞住调用线程,直到计算完成返回结果.你也可以使用  ...

  5. Python并发编程-线程同步(线程安全)

    Python并发编程-线程同步(线程安全) 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 线程同步,线程间协调,通过某种技术,让一个线程访问某些数据时,其它线程不能访问这些数据,直 ...

  6. 3.并发编程-ReentrantLock 细节说明

    并发编程-ReentrantLock 细节说明 ---title: 并发编程-ReentrantLock 细节说明date: 2018-07-05 09:06:57categories: - 并发编程 ...

  7. Java并发编程之同步

    1.synchronized 关键字 synchronized 锁什么?锁对象. 可能锁对象包括: this, 临界资源对象,Class 类对象. 1.1 同步方法 synchronized T me ...

  8. Java并发编程:同步锁、读写锁

    之前我们说过线程安全问题可以用锁机制来解决,即线程必要要先获得锁,之后才能进行其他操作.其实在 Java 的 API 中有这样一些锁类可以提供给我们使用,与其他对象作为锁相比,它们具有更强大的功能. ...

  9. Java 并发编程 生产者消费者模式

    本文部分摘自<Java 并发编程的艺术> 模式概述 在线程的世界里,生产者就是生产数据的线程,消费者就是消费数据的数据.生产者和消费者彼此之间不直接通信,而是通过阻塞队列进行通信,所以生产 ...

随机推荐

  1. 知名金融媒体采访行业大咖,多方推动BGV茁壮成长

    近来,多家知名金融媒体如纽约金融时报.伦敦金融时报等采访NGK官方代表洛索斯夫,以及美国当地行业大咖马库斯等人. 受访的NGK官方代币洛索斯夫回答道,近期官方将会推出NGK的书籍<NGK公链底层 ...

  2. Masterboxan INC 下半年将聚焦超高净值和家族全权委托客户

    "投资是一个没有终点的奋斗.我们不能简单的预测市场,而是应对市场做出正确的反应.这需要我们不断反思.总结.提升,找到自己的投资哲学,然后用一生的时间去坚守."Masterboxan ...

  3. C++算法代码——和为给定数

    题目来自:http://218.5.5.242:9018/JudgeOnline/problem.php?cid=1376&pid=0 题目描述 给出若干个整数,询问其中是否有一对数的和等于给 ...

  4. 字节码增强技术-Byte Buddy

    本文转载自字节码增强技术-Byte Buddy 为什么需要在运行时生成代码? Java 是一个强类型语言系统,要求变量和对象都有一个确定的类型,不兼容类型赋值都会造成转换异常,通常情况下这种错误都会被 ...

  5. .Net -- NLog日志框架配置与使用

    NLog是适用于各种.NET平台(包括.NET标准)的灵活,免费的日志记录平台,NLog可将日志写入多个目标,比如Database.File.Console.Mail.下面介绍下NLog的基本使用方法 ...

  6. 381. O(1) 时间插入、删除和获取随机元素 - 允许重复

    381. O(1) 时间插入.删除和获取随机元素 - 允许重复 LeetCode_381 题目详情 题解分析 代码实现 package com.walegarrett.interview; impor ...

  7. Golang+chromedp+goquery 简单爬取动态数据

    目录 Golang+chromedp+goquery 简单爬取动态数据 Golang的安装 下载golang软件 解压golang 配置golang 重新导入配置 chromedp框架的使用 实际的代 ...

  8. [数据结构与算法-13]ST表

    ST表 主要用来快速查询静态数据区间最大值 思路 数组\(A[i][j]\)存储数列\(\{a_i\}\)中区间\(i \in [i, i+2^j)\)的最大值 查询时只需要查询\(max\{A[i] ...

  9. Spring的IOC常用注解(含源码)

    一.容器中注入组件 1,包扫描 + 组件标注注解 源码:Demo01_ComponentScan a)组件标注 @Controller @Service @Repository @Component ...

  10. Shiro反序列化<=1.2.4 复现

    Apache Shiro是一个Java安全框架,执行身份验证.授权.密码和会话管理.    shiro默认使用了CookieRememberMeManager,其处理cookie的流程是:得到reme ...