6 Callable&Future 接口
6.0 创建线程的多种方式
程序中,不显示自己创建线程,都是使用线程池来创建
因此,前三种明白其作用,三者之间的区别即可
6.0.1 继承 Thread 类
class MyThread extends Thread{
//重写run()方法
public void run(){
for(int i = 0;i < 10; i++){
System.out.println(Thread.currentThread().getName() + i);
}
}
}
6.0.2 实现Runnable 接口
// normal
class MyRunnable implements Runnable {
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
}
}
@Test
public void threadTest() {
Thread myThread = new Thread(new MyRunnable());
myThread.start;
}
// lambda
@Test
public void threadTest() {
new Thread(() -> {
for (int processCount = 0; processCount < 30; processCount++) {
System.out.println(processCount);
}
}).start();
}
6.0.3 实现 Callable 接口
6.0.4 通过线程池
6.1 Callable 接口(让线程返回值)
目前我们学习了有两种创建线程的方法-一种是通过创建 Thread 类,另一种是通过使用 Runnable 创建线程。但是,Runnable 缺少的一项功能是,当线程终止时(即 run() 完成时),我们无法使线程返回结果。为了支持此功能,Java 中提供了 Callable 接口
Runnable 接口和 Callable 接口对比
- 是否有返回值:Callable 接口有返回值
- 是否抛出异常:Callable 接口可以抛出异常
- 接口实现方法名称:
- Runnable 接口的实现方法为
run()
- Callable 接口的实现方法为
call()
- Runnable 接口的实现方法为
- 线程创建方法
- Runnable:
- Runnable 接口的实现类直接传给Thread的构造函数,即可创建线程
- Callable:
- Thread 的构造函数没有Callable的参数,因此不可以直接创建线程
- 找到一个中间类 FutureTask, 它是Runnable的实现类,可以直接传给Thread 的构造函数;同时 FutureTask 的构造函数可以传递 Callable 类型
- 借由 FutureTask,就可以创建实现Callable接口的线程
```java
class MyThread2 implements Callable
{ @Override public Integer call() throws Exception { return 200; } }
- Runnable:
public class Demo {
public static void main (String[] args) {
// 普通方式
FutureTask
// lambda 方式
FutureTask<Integer> futureTask2 = new FutureTask<>(()->{
return 200;
});
new Thread(futureTask2, "t1").start();
System.out.println(futureTask2.get()); // 获取t2线程的执行返回结果
}
}
<a name="cc9a69af"></a>
## 6.2 Future 接口(接收线程返回值)
作用
- 使用Callable接口接口可以返回线程的执行结果,但是主线程现在没有方法获取到这个返回的结果,需要在主线程创建一个对象,让线程把执行结果放到这个对象里
- 因此,需要使用实现了 Future 接口的对象。**将 Future 作为保存线程执行结果的对象**
- 当Callable接口返回了值时,就保存在这个对象里
重要api<br />Future 基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写 5 种方法,这里列出了重要的方法
- `public boolean cancel (boolean mayInterrupt)`:
- 用于停止任务
- 如果尚未启动,它将停止任务。如果已启动,则仅在 mayInterrupt 为 true 时才会中断任务
- `public Object **get** () 抛出 InterruptedException,ExecutionException`:
- 用于获取任务的结果。
- 如果任务完成,它将立即返回结果,**否则将等待任务完成(主线程阻塞),然后返回结果**。
- `public boolean isDone ()`:
- 主线程调用这个,来判断实现这个自定义线程是否已经完成执行任务
- 如果任务完成,则返回 true,否则返回 false
可以看到 Callable 和 Future 做两件事
- Callable 与 Runnable 类似,因为它封装了要在另一个线程上运行的任务,而 Future 用于存储从另一个线程获得的结果
**要创建线程,需要 Callable/Runnable。为了获得结果,需要 future**
- 因此,需要一个类,实现了上面两个接口
<a name="xBbdB"></a>
## 6.3 FutureTask (结合 Callable 和 Future)
是 Callable 和 Future 的结合,**完成了让线程返回值,接收返回值返回到主线程整个流程**,是让该功能**真实可用**的一个类<br />介绍
- Java 库具有具体的 FutureTask 类型,**该类型实现 Runnable 和 Future,并方便地将两种功能组合在一起**。
- 可以**通过为其构造函数提供 Callable 来创建FutureTask**。然后,将 FutureTask 对象提供给 Thread 的构造函数以创建Thread 对象。因此,间接地使用 Callable 创建线程。
核心原理(重点)
- 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成
- 当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态
- 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
- 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法
- 一旦计算完成,就不能再重新开始或取消计算
- get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常
- get 只计算一次,因此 get 方法放到最后
重点知识点
- FutureTask的任务**只会运行一次**,后续可以通过 `get()` 方法**多次获取这同一个结果**
- 主线程获取FutureTask的任务时,使用 get() 方法,**如果没有完成,会阻塞主线程**
<a name="9FAHJ"></a>
## 6.4 使用 Callable 和 Future -> FutureTask
CallableDemo 案例
```java
/**
* CallableDemo 案列
*/
public class CallableDemo {
/**
* 实现 runnable 接口
*/
static class MyThread1 implements Runnable{
/**
* run 方法
*/
@Override
public void run() {
try {
System.out.println(Thread.currentThread().getName() + "线程进入了 run 方法");
}catch (Exception e){
e.printStackTrace();
}
}
}
/**
* 实现 callable 接口
*/
static class MyThread2 implements Callable{
/**
* call 方法
* @return
* @throws Exception
*/
@Override
public Long call() throws Exception {
try {
System.out.println(Thread.currentThread().getName() + "线程进入了 call方法,开始准备睡觉");
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName() + "睡醒了");
}catch (Exception e){
e.printStackTrace();
}
return System.currentTimeMillis();
}
}
public static void main(String[] args) throws Exception{
//声明 runable
Runnable runable = new MyThread1();
//声明 callable
Callable callable = new MyThread2();
//future-callable
FutureTask<Long> futureTask2 = new FutureTask(callable);
//线程二
new Thread(futureTask2, "线程二").start();
for (int i = 0; i < 10; i++) {
Long result1 = futureTask2.get();
System.out.println(result1);
}
//线程一
new Thread(runable,"线程一").start();
}
}
6.5 小结(重点)
- 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成, 当主线程将来需要时,就可以通过 Future对象获得后台作业的计算结果或者执行状态
- 一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果
- 仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
-
7 JUC 三大辅助类
JUC 中提供了三种常用的辅助类,通过这些辅助类可以很好的解决线程数量过多时 Lock 锁的频繁操作。这三种辅助类为:
CountDownLatch: 减少计数
- CyclicBarrier: 循环栅栏
-
7.1 减少计数 CountDownLatch
CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法之后的语句。
CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻塞
- 其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
- 当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行
场景: 6 个同学陆续离开教室后值班同学才可以关门。
CountDownLatchDemo
/**
* CountDownLatchDemo
*/
public class CountDownLatchDemo {
/**
* 6 个同学陆续离开教室后值班同学才可以关门
*/
public static void main(String[] args) throws Exception{
//定义一个数值为 6 的计数器
CountDownLatch countDownLatch = new CountDownLatch(6);
//创建 6 个同学
for (int i = 1; i <= 6; i++) {
new Thread(() ->{
try{
if(Thread.currentThread().getName().equals("同学 6")){
Thread.sleep(2000);
}
System.out.println(Thread.currentThread().getName() + "离开了");
//计数器减一,不会阻塞
countDownLatch.countDown();
}catch (Exception e){
e.printStackTrace();
}
}, "同学" + i).start();
}
//主线程 await 休息
System.out.println("主线程睡觉");
countDownLatch.await();
//全部离开后自动唤醒主线程
System.out.println("全部离开了,现在的计数器为" + countDownLatch.getCount());
}
}
7.2 循环栅栏 CyclicBarrier
CyclicBarrier 看英文单词可以看出大概就是循环阻塞的意思,在使用中 CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句。
与 CountDownLatch 的对比
- 不相同
- 计数方式不同,CountDownLatch 是倒数,CyclicBarrier 是正数
- 阻塞的线程不同,CountDownLatch 阻塞的是主线程,CyclicBarrier 阻塞的是自定义线程
- 目标到达后执行的方式不同,CountDownLatch 目标到达后,是继续执行主线程后续的代码,CyclicBarrier 目标到达后,是开启其预先定义好的线程去执行
- 整体设计思想不同,CountDownLatch 是只有做好了xx,整体才能继续执行,CyclicBarrier 是吩咐去做xx,都做好了再去做xx,但主线程从头到尾不过问。
可以将 CyclicBarrier 理解为加 1 操作
CyclicBarrierDemo
- 场景: 集齐 7 颗龙珠就可以召唤神龙
- 开启7个线程去收集七颗龙珠
- 都收集到后,开启另外一个线程去召唤神龙
召唤出神龙后,各个线程再将龙珠释放 ```java public class CyclicBarrierDemo {
private static Integer NUMBER = 7;
public static void main (String[] args) throws Exception {
//定义循环栅栏
CyclicBarrier cyclicBarrier = new CyclicBarrier(NUMBER, () ->{
System.out.println("集齐" + NUMBER + "颗龙珠,现在召唤神龙!!!!!!!!!");
});
//定义 7 个线程分别去收集龙珠
for (int i = 1; i <= 7; i++) {
new Thread(()->{
try {
System.out.println("收集到了 " + Thread.currentThread().getName());
cyclicBarrier.await();
System.out.println(Thread.currentThread().getName() + "释放");
}catch (Exception e){
e.printStackTrace();
}
}, "龙珠" + i + "号").start();
}
System.out.println("继续执行主线程");
}
}
```java
D:\Java\jdk1.8.0_40\bin\java.exe "-javaagent:D:\JetBrains\IntelliJ IDEA 2021.1\lib\idea_rt.jar=53317:D:\JetBrains\IntelliJ IDEA 2021.1\bin" -Dfile.encoding=UTF-8 -classpath D:\Java\jdk1.8.0_40\jre\lib\charsets.jar;D:\Java\jdk1.8.0_40\jre\lib\deploy.jar;D:\Java\jdk1.8.0_40\jre\lib\ext\access-bridge-64.jar;D:\Java\jdk1.8.0_40\jre\lib\ext\cldrdata.jar;D:\Java\jdk1.8.0_40\jre\lib\ext\dnsns.jar;D:\Java\jdk1.8.0_40\jre\lib\ext\jaccess.jar;D:\Java\jdk1.8.0_40\jre\lib\ext\jfxrt.jar;D:\Java\jdk1.8.0_40\jre\lib\ext\localedata.jar;D:\Java\jdk1.8.0_40\jre\lib\ext\nashorn.jar;D:\Java\jdk1.8.0_40\jre\lib\ext\sunec.jar;D:\Java\jdk1.8.0_40\jre\lib\ext\sunjce_provider.jar;D:\Java\jdk1.8.0_40\jre\lib\ext\sunmscapi.jar;D:\Java\jdk1.8.0_40\jre\lib\ext\sunpkcs11.jar;D:\Java\jdk1.8.0_40\jre\lib\ext\zipfs.jar;D:\Java\jdk1.8.0_40\jre\lib\javaws.jar;D:\Java\jdk1.8.0_40\jre\lib\jce.jar;D:\Java\jdk1.8.0_40\jre\lib\jfr.jar;D:\Java\jdk1.8.0_40\jre\lib\jfxswt.jar;D:\Java\jdk1.8.0_40\jre\lib\jsse.jar;D:\Java\jdk1.8.0_40\jre\lib\management-agent.jar;D:\Java\jdk1.8.0_40\jre\lib\plugin.jar;D:\Java\jdk1.8.0_40\jre\lib\resources.jar;D:\Java\jdk1.8.0_40\jre\lib\rt.jar;D:\projects\es-tool-frame\target\classes;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\logging\log4j\log4j-api\2.11.2\log4j-api-2.11.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\logging\log4j\log4j-core\2.11.2\log4j-core-2.11.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\slf4j\slf4j-api\1.7.29\slf4j-api-1.7.29.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\slf4j\slf4j-log4j12\1.7.29\slf4j-log4j12-1.7.29.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\log4j\log4j\1.2.17\log4j-1.2.17.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-logging\commons-logging\1.2\commons-logging-1.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-cli\commons-cli\1.2\commons-cli-1.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\elasticsearch\client\elasticsearch-rest-high-level-client\5.6.8\elasticsearch-rest-high-level-client-5.6.8.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\elasticsearch\plugin\parent-join-client\5.6.8\parent-join-client-5.6.8.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\elasticsearch\client\elasticsearch-rest-client\5.6.8\elasticsearch-rest-client-5.6.8.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\httpcomponents\httpasyncclient\4.1.2\httpasyncclient-4.1.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\elasticsearch\client\transport\5.6.8\transport-5.6.8.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\elasticsearch\plugin\transport-netty3-client\5.6.8\transport-netty3-client-5.6.8.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\netty\netty\3.10.6.Final\netty-3.10.6.Final.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\elasticsearch\plugin\reindex-client\5.6.8\reindex-client-5.6.8.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\elasticsearch\plugin\lang-mustache-client\5.6.8\lang-mustache-client-5.6.8.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\github\spullara\mustache\java\compiler\0.9.3\compiler-0.9.3.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\elasticsearch\plugin\percolator-client\5.6.8\percolator-client-5.6.8.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\elasticsearch\elasticsearch\5.6.8\elasticsearch-5.6.8.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-core\6.6.1\lucene-core-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-analyzers-common\6.6.1\lucene-analyzers-common-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-backward-codecs\6.6.1\lucene-backward-codecs-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-grouping\6.6.1\lucene-grouping-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-highlighter\6.6.1\lucene-highlighter-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-join\6.6.1\lucene-join-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-memory\6.6.1\lucene-memory-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-misc\6.6.1\lucene-misc-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-queries\6.6.1\lucene-queries-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-queryparser\6.6.1\lucene-queryparser-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-sandbox\6.6.1\lucene-sandbox-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-spatial\6.6.1\lucene-spatial-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-spatial-extras\6.6.1\lucene-spatial-extras-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-spatial3d\6.6.1\lucene-spatial3d-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\lucene\lucene-suggest\6.6.1\lucene-suggest-6.6.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\elasticsearch\securesm\1.2\securesm-1.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\net\sf\jopt-simple\jopt-simple\5.0.2\jopt-simple-5.0.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\carrotsearch\hppc\0.7.1\hppc-0.7.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\joda-time\joda-time\2.9.5\joda-time-2.9.5.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\yaml\snakeyaml\1.15\snakeyaml-1.15.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\fasterxml\jackson\dataformat\jackson-dataformat-smile\2.8.6\jackson-dataformat-smile-2.8.6.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\fasterxml\jackson\dataformat\jackson-dataformat-yaml\2.8.6\jackson-dataformat-yaml-2.8.6.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\fasterxml\jackson\dataformat\jackson-dataformat-cbor\2.8.6\jackson-dataformat-cbor-2.8.6.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\tdunning\t-digest\3.0\t-digest-3.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\hdrhistogram\HdrHistogram\2.1.9\HdrHistogram-2.1.9.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\elasticsearch\jna\4.4.0-1\jna-4.4.0-1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\elasticsearch\plugin\transport-netty4-client\5.6.8\transport-netty4-client-5.6.8.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\netty\netty-buffer\4.1.13.Final\netty-buffer-4.1.13.Final.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\netty\netty-codec\4.1.13.Final\netty-codec-4.1.13.Final.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\netty\netty-codec-http\4.1.13.Final\netty-codec-http-4.1.13.Final.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\netty\netty-common\4.1.13.Final\netty-common-4.1.13.Final.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\netty\netty-handler\4.1.13.Final\netty-handler-4.1.13.Final.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\netty\netty-resolver\4.1.13.Final\netty-resolver-4.1.13.Final.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\netty\netty-transport\4.1.13.Final\netty-transport-4.1.13.Final.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\locationtech\spatial4j\spatial4j\0.6\spatial4j-0.6.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\vividsolutions\jts\1.13\jts-1.13.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\datastory\commons3\ds-commons3-util\1.1.1\ds-commons3-util-1.1.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\protostuff\protostuff-core\1.6.0\protostuff-core-1.6.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\protostuff\protostuff-api\1.6.0\protostuff-api-1.6.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\protostuff\protostuff-runtime\1.6.0\protostuff-runtime-1.6.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\protostuff\protostuff-collectionschema\1.6.0\protostuff-collectionschema-1.6.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\commons\commons-csv\1.5\commons-csv-1.5.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\commons\commons-collections4\4.1\commons-collections4-4.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\commons\commons-lang3\3.7\commons-lang3-3.7.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-codec\commons-codec\1.11\commons-codec-1.11.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\xiaoleilu\hutool\2.15.3\hutool-2.15.3.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\quartz-scheduler\quartz\2.3.0\quartz-2.3.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\mchange\c3p0\0.9.5.2\c3p0-0.9.5.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\mchange\mchange-commons-java\0.2.11\mchange-commons-java-0.2.11.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\zaxxer\HikariCP-java6\2.3.13\HikariCP-java6-2.3.13.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\commons\commons-email\1.3.1\commons-email-1.3.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\javax\activation\activation\1.1.1\activation-1.1.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\datastory\commons3\ds-commons3-es\1.2.27-SNAPSHOT\ds-commons3-es-1.2.27-SNAPSHOT.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\searchbox\jest\5.3.3\jest-5.3.3.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\io\searchbox\jest-common\5.3.3\jest-common-5.3.3.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\httpcomponents\httpcore-nio\4.4.6\httpcore-nio-4.4.6.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\httpcomponents\httpcore\4.4.5\httpcore-4.4.5.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\httpcomponents\httpclient\4.5.3\httpclient-4.5.3.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\google\guava\guava\20.0\guava-20.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\alibaba\fastjson\1.2.17\fastjson-1.2.17.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-io\commons-io\1.3.2\commons-io-1.3.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\projectlombok\lombok\1.18.10\lombok-1.18.10.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\poi\poi\3.15\poi-3.15.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\poi\poi-ooxml\3.15\poi-ooxml-3.15.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\poi\poi-ooxml-schemas\3.15\poi-ooxml-schemas-3.15.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\xmlbeans\xmlbeans\2.6.0\xmlbeans-2.6.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\stax\stax-api\1.0.1\stax-api-1.0.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\github\virtuald\curvesapi\1.04\curvesapi-1.04.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\jcraft\jsch\0.1.54\jsch-0.1.54.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\fasterxml\jackson\core\jackson-core\2.9.10\jackson-core-2.9.10.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\fasterxml\jackson\core\jackson-databind\2.9.10\jackson-databind-2.9.10.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\fasterxml\jackson\core\jackson-annotations\2.9.10\jackson-annotations-2.9.10.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\google\code\gson\gson\2.3.1\gson-2.3.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-beanutils\commons-beanutils\1.9.3\commons-beanutils-1.9.3.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-collections\commons-collections\3.2.2\commons-collections-3.2.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\jayway\jsonpath\json-path\2.4.0\json-path-2.4.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\net\minidev\json-smart\2.3\json-smart-2.3.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\net\minidev\accessors-smart\1.2\accessors-smart-1.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\ow2\asm\asm\5.0.4\asm-5.0.4.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\yeezhao\commons\yz-commons-util\2.1.0.2.47\yz-commons-util-2.1.0.2.47.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\hbase\hbase-client\0.98.4.2.2.6.0-2800-hadoop2\hbase-client-0.98.4.2.2.6.0-2800-hadoop2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\hbase\hbase-common\0.98.4.2.2.6.0-2800-hadoop2\hbase-common-0.98.4.2.2.6.0-2800-hadoop2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\hbase\hbase-protocol\0.98.4.2.2.6.0-2800-hadoop2\hbase-protocol-0.98.4.2.2.6.0-2800-hadoop2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-lang\commons-lang\2.6\commons-lang-2.6.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\google\protobuf\protobuf-java\2.5.0\protobuf-java-2.5.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\zookeeper\zookeeper\3.4.6.2.2.6.0-2800\zookeeper-3.4.6.2.2.6.0-2800.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\cloudera\htrace\htrace-core\2.04\htrace-core-2.04.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\mortbay\jetty\jetty-util\6.1.26\jetty-util-6.1.26.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\codehaus\jackson\jackson-mapper-asl\1.9.13\jackson-mapper-asl-1.9.13.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\codehaus\jackson\jackson-core-asl\1.9.13\jackson-core-asl-1.9.13.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\hadoop\hadoop-common\2.6.0.2.2.6.0-2800\hadoop-common-2.6.0.2.2.6.0-2800.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\commons\commons-math3\3.1.1\commons-math3-3.1.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\xmlenc\xmlenc\0.52\xmlenc-0.52.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-httpclient\commons-httpclient\3.1\commons-httpclient-3.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-net\commons-net\3.1\commons-net-3.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\mortbay\jetty\jetty\6.1.26.hwx\jetty-6.1.26.hwx.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\sun\jersey\jersey-core\1.9\jersey-core-1.9.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\sun\jersey\jersey-json\1.9\jersey-json-1.9.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\codehaus\jettison\jettison\1.1\jettison-1.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\sun\xml\bind\jaxb-impl\2.2.3-1\jaxb-impl-2.2.3-1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\codehaus\jackson\jackson-jaxrs\1.8.3\jackson-jaxrs-1.8.3.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\codehaus\jackson\jackson-xc\1.8.3\jackson-xc-1.8.3.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-el\commons-el\1.0\commons-el-1.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\net\java\dev\jets3t\jets3t\0.9.0\jets3t-0.9.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\jamesmurty\utils\java-xmlbuilder\0.4\java-xmlbuilder-0.4.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\microsoft\azure\azure-storage\2.0.0\azure-storage-2.0.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-configuration\commons-configuration\1.6\commons-configuration-1.6.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-digester\commons-digester\1.8\commons-digester-1.8.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-beanutils\commons-beanutils-core\1.8.0\commons-beanutils-core-1.8.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\avro\avro\1.7.4\avro-1.7.4.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\thoughtworks\paranamer\paranamer\2.3\paranamer-2.3.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\xerial\snappy\snappy-java\1.0.4.1\snappy-java-1.0.4.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\curator\curator-client\2.6.0\curator-client-2.6.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\curator\curator-recipes\2.6.0\curator-recipes-2.6.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\google\code\findbugs\jsr305\1.3.9\jsr305-1.3.9.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\htrace\htrace-core\3.0.4\htrace-core-3.0.4.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\commons\commons-compress\1.4.1\commons-compress-1.4.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\tukaani\xz\1.0\xz-1.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\hadoop\hadoop-auth\2.6.0.2.2.6.0-2800\hadoop-auth-2.6.0.2.2.6.0-2800.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\directory\server\apacheds-kerberos-codec\2.0.0-M15\apacheds-kerberos-codec-2.0.0-M15.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\directory\server\apacheds-i18n\2.0.0-M15\apacheds-i18n-2.0.0-M15.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\directory\api\api-asn1-api\1.0.0-M20\api-asn1-api-1.0.0-M20.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\directory\api\api-util\1.0.0-M20\api-util-1.0.0-M20.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\curator\curator-framework\2.6.0\curator-framework-2.6.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\hadoop\hadoop-mapreduce-client-core\2.6.0.2.2.6.0-2800\hadoop-mapreduce-client-core-2.6.0.2.2.6.0-2800.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\hadoop\hadoop-yarn-common\2.6.0.2.2.6.0-2800\hadoop-yarn-common-2.6.0.2.2.6.0-2800.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\hadoop\hadoop-yarn-api\2.6.0.2.2.6.0-2800\hadoop-yarn-api-2.6.0.2.2.6.0-2800.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\javax\xml\bind\jaxb-api\2.2.2\jaxb-api-2.2.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\javax\xml\stream\stax-api\1.0-2\stax-api-1.0-2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\javax\servlet\servlet-api\2.5\servlet-api-2.5.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\sun\jersey\jersey-client\1.9\jersey-client-1.9.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\google\inject\guice\3.0\guice-3.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\javax\inject\javax.inject\1\javax.inject-1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\aopalliance\aopalliance\1.0\aopalliance-1.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\sun\jersey\jersey-server\1.9\jersey-server-1.9.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\asm\asm\3.1\asm-3.1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\sun\jersey\contribs\jersey-guice\1.9\jersey-guice-1.9.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\google\inject\extensions\guice-servlet\3.0\guice-servlet-3.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\apache\hadoop\hadoop-annotations\2.6.0.2.2.6.0-2800\hadoop-annotations-2.6.0.2.2.6.0-2800.jar;D:\Java\jdk1.8.0_40\lib\tools.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\github\stephenc\findbugs\findbugs-annotations\1.3.9-1\findbugs-annotations-1.3.9-1.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\junit\junit\4.11\junit-4.11.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\org\hamcrest\hamcrest-core\1.3\hamcrest-core-1.3.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\javax\mail\mail\1.4.5\mail-1.4.5.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\commons-dbutils\commons-dbutils\1.4\commons-dbutils-1.4.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\c3p0\c3p0\0.9.1.2\c3p0-0.9.1.2.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\mysql\mysql-connector-java\5.1.39\mysql-connector-java-5.1.39.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\yeezhao\commons\yz-commons-sybase-jconn4d\16.0\yz-commons-sybase-jconn4d-16.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\yeezhao\commons\yz-commons-ojdbc6\0.1.0-11r\yz-commons-ojdbc6-0.1.0-11r.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\squareup\okhttp3\okhttp\3.14.0\okhttp-3.14.0.jar;D:\apache-maven-3.6.1-bin\apache-maven-3.6.1\repository\com\squareup\okio\okio\1.17.2\okio-1.17.2.jar cn.haniel.utils.LockDemo
收集到了 龙珠1号
收集到了 龙珠7号
收集到了 龙珠6号
收集到了 龙珠5号
继续执行主线程 -- 注意:主线程并不受阻塞
收集到了 龙珠2号
收集到了 龙珠4号
收集到了 龙珠3号
集齐7颗龙珠,现在召唤神龙!!!!!!!!!
龙珠3号释放
龙珠6号释放
龙珠5号释放
龙珠1号释放
龙珠4号释放
龙珠2号释放
龙珠7号释放
Process finished with exit code 0
7.3 信号灯 Semaphore
Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。
作用
- 高并发时,限制指定资源的并行访问量
使用方式
- acquire 方法获得许可证
- release 方法释放许可
SemaphoreDemo
- 场景: 抢车位, 6 部汽车 3 个停车位
- 会先有三个汽车抢到车位(获取到许可证),其他汽车阻塞线程等待许可证
- 等许可证被释放后,其他线程就可以获取到 ```java /**
- Semaphore 案列
/
public class SemaphoreDemo { /*
- 抢车位, 10 部汽车 1 个停车位
- @param args
*/
public static void main(String[] args) throws Exception{
//定义 3 个停车位
Semaphore semaphore = new Semaphore(3);
//模拟 6 辆汽车停车
for (int i = 1; i <= 6; i++) {
} } }Thread.sleep(100);
//停车
new Thread(() ->{
try {
System.out.println(Thread.currentThread().getName() + "找车位 ing");
semaphore.acquire();
System.out.println(Thread.currentThread().getName() + "汽车停车成功!");
Thread.sleep(10000);
}catch (Exception e){
e.printStackTrace();
}finally {
System.out.println(Thread.currentThread().getName() + "溜了溜了");
semaphore.release();
}
}, "汽车" + i).start();
Process finished with exit code 0
<a name="dcDEs"></a>
# 8 读写锁
<a name="iKxlN"></a>
## 8.1 读写锁介绍
读写锁 `ReentrantReadWriteLock` 的概念
- **读读共享**:一个资源可以被**多个读线程**读取
- **写独占**: 一个资源可以被**一个写线程**操作
- **读写互斥(不同线程)**:一个资源**不可以同时**存在**读写**线程
- **锁降级(同线程)**:一个线程可以**先获取到写锁,后不释放写锁就直接拿到读锁**,也即同一个线程可以先写,然后直接去读取;但反之不可以,也即不可以先读取,然后直接去写,需要释放读锁,然后竞争写锁
读写锁内部表示两个锁
- 一个是**读操作相关的锁**,称为**共享锁**,存在死锁情况
- 数据库行锁就会存在读锁的死锁,具体待查
- 一个是**写相关的锁**,称为**排他锁**,存在死锁情况
线程进入读锁的前提条件:
- 没有其他线程的写锁
- 没有写请求, 或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁)
线程进入写锁的前提条件:
- 没有其他线程的读锁
- 没有其 他线程的写锁
<a name="uo"></a>
## 8.2 读写锁的演进
1. 资源不加锁
- 多线程争夺资源,混乱
2. 添加普通锁(synchronized 或 ReentrantLock)
- 读写都是独占锁,效率低下
- 读读不能共享
- 读写锁(ReentrantReadWriteLock)
- 读读可以共享,提升性能
- 缺点
- **锁饥饿**:大量/长时间的读操作,因为读写互斥,会导致写操作持续等待
- 解决方法
- 将锁设置成公平锁
而读写锁有以下三个重要的特性:
1. 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
1. 重进入:读锁和写锁都支持线程重进入。
1. 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
<a name="ppLXw"></a>
## 8.3 读写锁中的锁降级
锁降级简介
- 同一个线程中,将持有的写入锁降级为读锁
锁降级过程说明(jdk8)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1774803/1626619650043-92c31ceb-d7f3-447b-8e5d-2963eacdd80e.png#crop=0&crop=0&crop=1&crop=1&height=96&id=ocFVy&margin=%5Bobject%20Object%5D&name=image.png&originHeight=247&originWidth=1934&originalType=binary&ratio=1&rotation=0&showTitle=false&size=215543&status=done&style=none&title=&width=753)
- 先获取写锁,然后在同一个线程中,持有写锁的情况下,可以直接获取到读锁(通常认为这不是持有两个锁,而是写锁降级读锁)
- 操作完成后,先释放写锁,然后释放读锁
示例
- 锁降级可以
- 反过来则不行,会因为读锁没有释放而无法获取写锁而阻塞
```java
public class ReentrantReadWriteLockDemo {
public static void main(String[] args) {
ReentrantReadWriteLock rwLock = new ReentrantReadWriteLock();
// 读锁
ReentrantReadWriteLock.ReadLock readLock = rwLock.readLock();
// 写锁
ReentrantReadWriteLock.WriteLock writeLock = rwLock.writeLock();
// 锁降级
// 1. 获取写锁
writeLock.lock();
System.out.println("写操作");
// 2. 获取读锁
readLock.lock();
System.out.println("读操作");
// 3. 释放写锁
writeLock.unlock();
// 4. 释放读锁
readLock.unlock();
// 反过来则不行,会因为读锁没有释放而无法获取写锁而阻塞
// 1. 获取读锁
readLock.lock();
System.out.println("读操作--2");
// 2. 获取写锁
writeLock.lock();
// 3. 释放读锁
readLock.unlock();
System.out.println("写操作--2");
// 4. 释放写锁
writeLock.unlock();
}
}
8.2 ReentrantReadWriteLock
ReentrantReadWriteLock 类的整体结构
public class ReentrantReadWriteLock implements ReadWriteLock, java.io.Serializable {
/** 读锁 */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** 写锁 */
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
/** 使用默认(非公平)的排序属性创建一个新的ReentrantReadWriteLock */
public ReentrantReadWriteLock() {
this(false);
}
/** 使用给定的公平策略创建一个新的 ReentrantReadWriteLock */
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
/** 返回用于写入操作的锁 */
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
/** 返回用于读取操作的锁 */
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
abstract static class Sync extends AbstractQueuedSynchronizer {}
static final class NonfairSync extends Sync {}
static final class FairSync extends Sync {}
public static class ReadLock implements Lock, java.io.Serializable {}
public static class WriteLock implements Lock, java.io.Serializable {}
}
可以看到,ReentrantReadWriteLock 实现了 ReadWriteLock 接口,ReadWriteLock 接口定义了获取读锁和写锁的规范,具体需要实现类去实现;
同时其还实现了 Serializable 接口,表示可以进行序列化,在源代码中可以看到 ReentrantReadWriteLock 实现了自己的序列化逻辑
8.3 入门案例
场景: 使用 ReentrantReadWriteLock 对一个 hashmap 进行读和写操作
8.3.1 实现案例
不加读写锁时的问题
- 资源类没有被锁保护起来,map在写的过程,就会有读线程插进来,反过来也一样,数据不安全
加了读写锁
- 写锁是独占锁,写线程拿到写锁时,其他线程都不能获取这个读写锁,因此写线程会完整执行
读锁是共享锁,读线程拿到读锁时,其他的读线程也可以拿到读锁,因此多个读线程会交替执行 ```java class ResourceClass { /**
- 资源类的资源,并发得写入、读取
为了写入的可以立马被读取到,加上 volatile */ public volatile Map
map = new HashMap<>(); /**
可重入读写锁 */ private ReadWriteLock rwLock = new ReentrantReadWriteLock();
/**
资源类的写操作 */ public void put(String key, Object value) { try {
// 加写锁
rwLock.writeLock().lock();
System.out.println(Thread.currentThread().getName() + " 正在写操作 " + key);
TimeUnit.SECONDS.sleep(1);
map.put(key, value);
System.out.println(Thread.currentThread().getName() + " 写完了 " + key);
} catch (Exception e) {
e.printStackTrace();
} finally {
// 释放写锁
rwLock.writeLock().unlock();
} }
/**
- 资源类的读操作
*/
public Object get(String key) {
Object result = null;
try {
} catch (Exception e) {// 加读锁
rwLock.readLock().lock();
System.out.println(Thread.currentThread().getName() + " 正在读取 " + key);
TimeUnit.SECONDS.sleep(1);
result = map.get(key);
System.out.println(Thread.currentThread().getName() + " 取完了 " + key);
} finally {e.printStackTrace();
} return result; } }// 释放读锁
rwLock.readLock().unlock();
public class ReentrantReadWriteLockDemo { public static void main(String[] args) { ResourceClass rc = new ResourceClass(); // 写操作的线程 for (int i = 1; i <= 5; i++) { final int num = i; new Thread(() -> { rc.put(num + “”, num); }, i + “”).start(); } // 读操作的线程 for (int i = 1; i < 5; i++) { final int num = i; new Thread(() -> { rc.get(num + “”); }, i + “”).start(); } } }
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1774803/1626617395073-e92e3f8b-1d18-4377-a8c0-e20e3606e4df.png#crop=0&crop=0&crop=1&crop=1&height=458&id=ATFJi&margin=%5Bobject%20Object%5D&name=image.png&originHeight=554&originWidth=689&originalType=binary&ratio=1&rotation=0&showTitle=false&size=59966&status=done&style=none&title=&width=569)
<a name="kA5Bs"></a>
## 8.4 读写锁的演变
<a name="jntMe"></a>
## 8.4 小结(重要)
- 在线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。
- 在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。
原因: 当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把获取读锁的线程“升级”为写锁;而对于获得写锁的线程,它一定独占了读写锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。
<a name="EsLSB"></a>
# 9 阻塞队列(**线程间数据共享**)
<a name="MUlgt"></a>
## 9.1 BlockingQueue 接口简介
<a name="tjeGB"></a>
### 9.1.1 功能需求
各个线程之间需要共享(传递)数据的时候,就需要借由一个共享的空间来实现<br />一个线程往共享空间写入一个数据,另一个线程随后去这个共享的空间读取这个数据<br />这个共享空间的数据结构,需要解决诸如
- 线程并发写入安全问题
- 线程读取安全问题
- 线程读写前后的阻塞问题
- 。。。。。。
<a name="WPX9b"></a>
### 9.1.2 concurrent 包的解决方案
concurrent 包提供了一个接口 BlockingQueue,专门用于解决这类线程间数据共享的问题<br />BlockingQueue 接口的各种实现类,为这个共享空间提供了多种多样的功能<br />好处
- 不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切 BlockingQueue 都一手包办了
- 多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。
<a name="TyVRf"></a>
### 9.1.3 BlockingQueue 的功能
1. 线程安全
- 当队列是空的,从队列中**获取元素的操作将会被阻塞**,当队列有数据后,被阻塞的线程**自动唤醒消费**
- 当队列是满的,从队列中**添加元素的操作将会被阻塞**,当队列有空后,被阻塞的线程**自动唤醒**写入
2. 读取和写入有序
1. **队列(FIFO)**
1. 从某种程度上来说这种队列也体现了一种公平性
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1774803/1626257557726-4297f1de-9d75-4b38-a088-194780154ca0.png#crop=0&crop=0&crop=1&crop=1&height=235&id=XGVep&margin=%5Bobject%20Object%5D&name=image.png&originHeight=470&originWidth=1095&originalType=binary&ratio=1&rotation=0&showTitle=false&size=335206&status=done&style=none&title=&width=547.5)
1. 栈(LIFO)
1. 这种队列优先处理最近发生的事件
<a name="nxSBC"></a>
## 9.2 常见的 BlockingQueue
<a name="sqgSB"></a>
### 9.2.1 ArrayBlockingQueue(常用)
- **由数组结构组成的有界阻塞队列 **
- **基于数组的阻塞队列实现**,在 ArrayBlockingQueue 内部,维护了一个定长数组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数组外,ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的头部和尾部在数组中的位置。
- ArrayBlockingQueue 在**生产者放入数据和消费者获取数据,都是共用同一个锁对象**,由此也意味着**两者无法真正并行运行**,这点尤其不同 LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue 完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。Doug Lea 之所以没这样去做,也许是因为 ArrayBlockingQueue 的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue 和LinkedBlockingQueue 间还有一个明显的不同之处在于,前者在插入或删除元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的 Node 对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于GC 的影响还是存在一定的区别。而在创建 ArrayBlockingQueue 时,我们还可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
<a name="91GPc"></a>
### 9.2.2 LinkedBlockingQueue(常用)
- **由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列**
- **基于链表的阻塞队列**,同 ArrayListBlockingQueue 类似,其内部也维持着一个数据缓冲队列(该队列由一个链表构成),当生产者往队列中放入一个数据时,队列会从生产者手中获取数据,并缓存在队列内部,而生产者立即返回;只有当队列缓冲区达到最大值缓存容量时(LinkedBlockingQueue 可以通过构造函数指定该值),才会阻塞生产者队列,直到消费者从队列中消费掉一份数据,生产者线程会被唤醒,反之对于消费者这端的处理也基于同样的原理。
- LinkedBlockingQueue 之所以能够高效的处理并发数据,还因为其**对于生产者端和消费者端分别采用了独立的锁来控制数据同步**,这也意味着在高并发的情况下生产者和消费者可以并行地操作队列中的数据,以此来提高整个队列的并发性能。
ArrayBlockingQueue 和 LinkedBlockingQueue 是两个最普通也是最常用的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题,使用这两个类足以。
<a name="sEfGZ"></a>
### 9.2.3 DelayQueue
- 使用优先级队列实现的延迟无界阻塞队列
- DelayQueue 中的元素**只有当其指定的延迟时间到了,才能够从队列中获取到该元素**。
- DelayQueue 是一个**没有大小限制的队列**,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
<a name="nz3Zf"></a>
### 9.2.4 PriorityBlockingQueue
- 支持优先级排序的无界阻塞队列
- 基于优先级的阻塞队列(优先级的判断通过**构造函数传入的 Compator 对象来决定**)
- PriorityBlockingQueue 并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽有的可用堆内存空间。
- 在实现 PriorityBlockingQueue 时,内部控制线程同步的锁采用的是公平锁。
<a name="DnTRb"></a>
### 9.2.5 SynchronousQueue
- 不存储元素的阻塞队列,也即单个元素的队列
- 一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须亲自去集市找到所要商品的直接生产者,如果一方没有找到合适的目标,那么对不起,大家都在集市等待。
- 相对于有缓冲的 BlockingQueue 来说,少了一个中间经销商的环节(缓冲区),如果有经销商,生产者直接把产品批发给经销商,而无需在意经销商最终会将这些产品卖给那些消费者,由于经销商可以库存一部分商品,因此相对于直接交易模式,总体来说采用中间经销商的模式会吞吐量高一些(可以批量买卖);但另一方面,又因为经销商的引入,使得产品从生产者到消费者中间增加了额外的交易环节,单个产品的及时响应性能可能会降低。
- 声明一个 SynchronousQueue 有两种不同的方式,它们之间有着不太一样的行为。
- 公平模式和非公平模式的区别:
- 公平模式:SynchronousQueue 会采用公平锁,并配合一个 FIFO 队列来阻塞多余的生产者和消费者,从而体系整体的公平策略;
- 非公平模式(SynchronousQueue 默认):SynchronousQueue 采用非公平锁,同时配合一个 LIFO 队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理。
<a name="mNVQG"></a>
### 9.2.6 LinkedTransferQueue
- 由链表组成的无界阻塞队列
- LinkedTransferQueue 是一个由链表结构组成的无界阻塞 TransferQueue 队列。
- 相对于其他阻塞队列,LinkedTransferQueue 多了 tryTransfer 和 transfer 方法。
- LinkedTransferQueue 采用一种预占模式。意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素为 null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时发现有一个元素为 null 的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素,从调用的方法返回。
<a name="5Kqqx"></a>
### 9.2.7 LinkedBlockingDeque
- 由链表组成的双向阻塞队列
- LinkedBlockingDeque 是一个由链表结构组成的双向阻塞队列,即可以从队列的两端插入和移除元素。对于一些指定的操作,在插入或者获取队列元素时如果队列状态不允许该操作可能会阻塞住该线程直到队列状态变更为允许操作,这里的阻塞一般有两种情况
- 插入元素时: 如果当前队列已满将会进入阻塞状态,一直等到队列有空的位置时再将该元素插入,该操作可以通过设置超时参数,超时后返回 false 表示操作失败,也可以不设置超时参数一直阻塞,中断后抛出 InterruptedException 异常
- 读取元素时: 如果当前队列为空会阻塞住直到队列不为空然后返回元素,同样可以通过设置超时参数
<a name="ecRIS"></a>
## 9.3 BlockingQueue 核心方法
![image.png](https://cdn.nlark.com/yuque/0/2021/png/1774803/1626257849757-23d71e2d-2b42-4b24-b733-0113ee69ccdb.png#crop=0&crop=0&crop=1&crop=1&height=136&id=llLPn&margin=%5Bobject%20Object%5D&name=image.png&originHeight=353&originWidth=1718&originalType=binary&ratio=1&rotation=0&showTitle=false&size=447432&status=done&style=none&title=&width=662)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1774803/1626257874932-56a188a1-3cec-4d71-8b79-fc99df6d5c58.png#crop=0&crop=0&crop=1&crop=1&height=190&id=SwE5l&margin=%5Bobject%20Object%5D&name=image.png&originHeight=439&originWidth=1717&originalType=binary&ratio=1&rotation=0&showTitle=false&size=193977&status=done&style=none&title=&width=743)<br />BlockingQueue 的核心方法:
1. 放入数据
- `**offer(anObject)**`:
- 表示如果可能的话,将 anObject 加到 BlockingQueue 里,即如果 BlockingQueue 可以容纳,则返回 true,否则返回 false.
- (**本方法不阻塞当前执行方法的线程**)
- `**offer(E o, long timeout, TimeUnit unit)**`:
- 可以设定等待的时间,如果在指定的时间内,还不能往队列中加入 BlockingQueue,则返回失败
- **有限阻塞**
- `**put(anObject)**`:
- 把 anObject 加到 BlockingQueue 里,如果 BlockQueue 没有空间,则调用此方法的线程被阻断直到 BlockingQueue 里面有空间再继续
- **持续阻塞**
2. 获取数据
- `**poll(time)**`:
- 取走 BlockingQueue 里排在首位的对象,若不能立即取出,则可以等 time 参数规定的时间,**取不到时返回 null**
- (**本方法不阻塞当前执行方法的线程**)
- `**poll(long timeout, TimeUnit unit)**`
- 从 BlockingQueue 取出一个队首的对象,如果在指定时间内,队列一旦有数据可取,则立即返回队列中的数据。否则直到时间超时还没有数据可取,**返回失败**。
- **有限阻塞**
- `**take()**`
- 取走 BlockingQueue 里排在首位的对象,若 BlockingQueue 为空,阻断进入等待状态直到 BlockingQueue 有新的数据被加入
- **持续阻塞**
- drainTo()
- 一次性从 BlockingQueue 获取所有可用的数据对象(还可以指定获取数据的个数),通过该方法,可以提升获取数据效率;
- 不需要多次分批加锁或释放锁。
<a name="ghpIK"></a>
## 9.4 入门案例
```java
/**
* 阻塞队列
*/
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
// List list = new ArrayList();
// 创建一个存储String元素,长度固定为3的阻塞队列
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
//第一组
// System.out.println(blockingQueue.add("a"));
// System.out.println(blockingQueue.add("b"));
// System.out.println(blockingQueue.add("c"));
// System.out.println(blockingQueue.element());
//System.out.println(blockingQueue.add("x"));
// 取出元素
// System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());
// 第二组
// System.out.println(blockingQueue.offer("a"));
// System.out.println(blockingQueue.offer("b"));
// System.out.println(blockingQueue.offer("c"));
// System.out.println(blockingQueue.offer("x"));
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// System.out.println(blockingQueue.poll());
// 第三组
// blockingQueue.put("a");
// blockingQueue.put("b");
// blockingQueue.put("c");
// //blockingQueue.put("x");
// System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());
// System.out.println(blockingQueue.take());
// 第四组
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("a",3L, TimeUnit.SECONDS));
}
}