一、JUC介绍
介绍
JUC就是java.util .concurrent工具包的简称。这是一个处理线程的工具包,JDK1.5开始出现的。
进程、线程、多线程、管程
进程指正在运行的程序 确切的来说,当一个程序进入内存运行,即变成一个进程,进程是处于运行过程中的程序,并且具有一定独立功能。(比如电脑中运行的酷狗,qq等)
线程是进程中的一个执行单元,负责当前进程中程序的执行,线程共享进程的资源。一个程序运行后至少有一个进程,一个进程中可以包含多个线程。
多线程即就是一个程序中有多个线程在同时执行。多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。
管程是指管理共享变量以及对共享变量操作的过程,让它们支持并发。翻译成Java领域的语言,就是管理类的状态变量,让这个类是线程安全的。synchronized关键字和wait()、notify()、notifyAll()这三个方法是Java中实现管程技术的组成部分。管程对应的英文是Monitor,直译为“监视器”,而操作系统领域一般翻译为“管程”。在管程的发展史上,先后出现过三种不同的管程模型,分别是Hasen模型、Hoare模型和MESA模型。现在正在广泛使用的是MESA模型。
管程(monitor)是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同一时刻只被一个进程调用(由编译器实现).但是这样并不能保证进程以设计的顺序执行。JVM 中同步是基于进入和退出管程(monitor)对象实现的,每个对象都会有一个管程(monitor)对象,管程(monitor)会随着 java 对象一同创建和销毁。执行线程首先要持有管程对象,然后才能执行方法,当方法完成之后会释放管程,方法在执行时候会持有管程,其他线程无法再获取同一个管程
串行、并行、并发
串行即单个线程执行多个任务。比如下载文件,第一个没下完不能开始下载第二个,属于不同时刻。缺点很明显,效率很低。
并行即多个线程执行多个任务。比如开启迅雷同时下载好几个文件,属于同一时刻。效率很高,但是要注意不要每个任务都建一个线程去处理,任务数量越多,内存压力越大,严重会导致宕机。
并发同一时刻多个线程在访问同一个资源,多个线程对一个点。
用户线程与守护线程
用户线程: 平时用到的普通线程,自定义线程。
守护线程: 运行在后台,是一种特殊的线程,比如垃圾回收。
当主线程结束后,用户线程还在运行,JVM 存活。如果没有用户线程,都是守护线程,JVM 结束。
分时调度和抢占式调度
分时调度: 所有线程轮流使用CPU的使用权,平均分配每个线程占用CPU的时间。
抢占式调度: 优先让优先级高的线程使用CPU,如果线程的优先级相同,那么会随机选择一个线程(线程随机性),java使用的为抢占式调度。
复制代码
二、Thread、Runnable、Callable总结
1.继承Thread类
public class Test {
public static void main(String[] args) {
SubThread st = new SubThread();
st.start();
new Thread(new SubThread(),"thread-name").start();
for(int i=0;i<5;i++){
System.out.println("main"+i);
}
}
}
class SubThread extends Thread{
public void run(){
for(int i=0;i<5;i++){
System.out.println("run:"+i);
}
}
}
public class Test {
public static void main(String[] args) {
new Thread(){
public void run(){
for(int i=0;i<5;i++){
System.out.println("run:"+i);
}
}
}.start();
for(int j=0;j<5;j++){
System.out.println("main"+j);
}
}
}
复制代码
2.实现Runnable接口
public class Test {
public static void main(String[] args) {
SubRunnable sr = new SubRunnable();
Thread t = new Thread(sr);
t.start();
for(int i=0;i<5;i++){
System.out.println("main"+i);
}
}
}
class SubRunnable implements Runnable{
public void run(){
for(int i=0;i<5;i++){
System.out.println("run"+i);
}
}
}
public class Test {
public static void main(String[] args) {
new Thread(()->{
for(int i=0;i<5;i++){
System.out.println("run"+i);
}
},"Thread-name").start();
for(int i=0;i<5;i++){
System.out.println("main"+i);
}
}
}
复制代码
3.实现Callable接口
(1)概述
Callable接口的特点
为了实现 Runnable,需要实现不返回任何内容的 run()方法,而对于Callable,需要实现在完成时返回结果的 call()方法。
1.call()方法可以引发异常,而 run()则不能。
2.为实现 Callable 而必须重写 call 方法
3.不能直接替换 runnable,因为 Thread 类的构造方法根本没有 Callable
复制代码
public class Test2 {
public static void main(String[] args) throws ExecutionException, InterruptedException {
Callable<Integer> callable = new SubCallable();
FutureTask<Integer> futureTask = new FutureTask<Integer>(callable);
new Thread(futureTask,"Thread-name").start();
Integer integer = futureTask.get();
System.out.println(integer);
}
}
class SubCallable implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 200;
}
}
复制代码
(2)Future接口
当 call()方法完成时,结果必须存储在主线程已知的对象中,以便主线程可以知道该线程返回的结果。为此,可以使用 Future 对象。
将 Future 视为保存结果的对象–它可能暂时不保存结果,但将来会保存(一旦Callable 返回)。Future 基本上是主线程可以跟踪进度以及其他线程的结果的一种方式。要实现此接口,必须重写 5种方法,这里列出了重要的方法,如下:
1.public boolean cancel(boolean mayInterrupt):用于停止任务。
如果尚未启动,它将停止任务。如果已启动,则仅在 mayInterrupt 为 true时才会中断任务。
2.public Object get()抛出 InterruptedException,ExecutionException:用于获取任务的结果。
如果任务完成,它将立即返回结果,否则将等待任务完成,然后返回结果。
3.public boolean isDone():如果任务完成,则返回 true,否则返回 false
可以看到 Callable 和 Future 做两件事-Callable 与 Runnable 类似,因为它封装了要在另一个线程上运行的任务,而 Future 用于存储从另一个线程获得的结果。实际上,future 也可以与 Runnable 一起使用。要创建线程,需要 Runnable。为了获得结果,需要 future。
复制代码
(3)FutureTask
Java 库具有具体的 FutureTask 类型,该类型实现 Runnable 和 Future,并方便地将两种功能组合在一起。 可以通过为其构造函数提供 Callable 来创建FutureTask。然后,将 FutureTask 对象提供给 Thread 的构造函数以创建Thread 对象。因此,间接地使用 Callable 创建线程。
核心原理:(重点)
在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给 Future 对象在后台完成
1.当主线程将来需要时,就可以通过 Future 对象获得后台作业的计算结果或者执行状态
2.一般 FutureTask 多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
3.仅在计算完成时才能检索结果;如果计算尚未完成,则阻塞 get 方法
4.一旦计算完成,就不能再重新开始或取消计算
5.get 方法而获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常
6.get 只计算一次,因此 get 方法放到最后
复制代码
三、常用的方法/关键字
1.Synchronized
synchronized 是 Java 中的关键字,是一种同步锁。它修饰的对象有以下几种:
1.修饰一个代码块,被修饰的代码块称为同步语句块,其作用的范围是大括号{}括起来的代码,作用的对象是调用这个代码块的对象;
2.修饰一个方法,被修饰的方法称为同步方法,其作用的范围是整个方法,作用的对象是调用这个方法的对象;虽然可以使用 synchronized 来定义方法,但 synchronized 并不属于方法定义的一部分,因此,synchronized 关键字不能被继承。如果在父类中的某个方法使用了 synchronized 关键字,而在子类中覆盖了这个方法,在子类中的这个方法默认情况下并不是同步的,而必须显式地在子类的这个方法中加上synchronized关键字才可以。当然,还可以在子类方法中调用父类中相应的方法,这样虽然子类中的方法不是同步的,但子类调用了父类的同步方法,因此,子类的方法也就相当于同步了。
3.修改一个静态的方法,其作用的范围是整个静态方法,作用的对象是这个类的所有对象;
4.修改一个类,其作用的范围是 synchronized 后面括号括起来的部分,作用主的对象是这个类的所有对象。
synchronized实现同步的基础:Java中的每一个对象都可以作为锁。具体表现为以下3种形式:
1.对于普通同步方法,锁是当前实例对象。
2.对于静态同步方法,锁是当前类的Class对象。
3.对于同步方法块,锁是Synchonized括号里配置的对象
复制代码
(1)多线程消费一个公共资源
public class Test3 {
public static void main(String[] args) {
TestResource resource = new TestResource();
new Thread(()->{
for (int i = 0; i < 40; i++) {
resource.consume();
}
},"Thread-A").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
resource.consume();
}
},"Thread-B").start();
new Thread(()->{
for (int i = 0; i < 40; i++) {
resource.consume();
}
},"Thread-C").start();
}
}
class TestResource{
private int number = 5;
public synchronized void consume(){
if(number > 0){
System.out.println("线程 "+Thread.currentThread().getName()+" 消费了 "+ number-- +" 个资源,还剩下 " + number + " 个资源");
}
}
}
复制代码
如果一个代码块被 synchronized 修饰了,当一个线程获取了对应的锁,并执行该代码块时,其他线程便只能一直等待,等待获取锁的线程释放锁,而这里获取锁的线程释放锁只会有两种情况:
1.获取锁的线程执行完了该代码块,然后线程释放对锁的占有;
2.线程执行发生异常,此时 JVM 会让线程自动释放锁。
那么如果这个获取锁的线程由于要等待 IO 或者其他原因(比如调用sleep方法)被阻塞了,但是又没有释放锁,其他线程便只能干巴巴地等待,试想一下,这多么影响程序执行效率。
因此就需要有一种机制可以不让等待的线程一直无期限地等待下去(比如只等待一定的时间或者能够响应中断),通过 Lock 就可以办到。
复制代码
(2)synchronized+wait+notify实现 等待/通知模式
public class Test {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
}catch (Exception e){
e.printStackTrace();
}
}
},"Thread-Name-A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
}catch (Exception e){
e.printStackTrace();
}
}
},"Thread-Name-B").start();
}
}
class Share{
private int number = 0;
public synchronized void incr() throws Exception {
while(number !=0){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + " :: "+number);
this.notifyAll();
}
public synchronized void decr() throws Exception {
while(number !=1){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + " :: "+number);
this.notifyAll();
}
}
复制代码
2.Lock
Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。Lock 提供了比 synchronized 更多的功能。
Lock 与的 Synchronized 区别
1.Lock 不是 Java 语言内置的,synchronized 是 Java 语言的关键字,因此是内置特性。Lock 是一个类,通过这个类可以实现同步访问;
2.Lock 和 synchronized 有一点非常大的不同,采用 synchronized 不需要用户去手动释放锁,当 synchronized 方法或者 synchronized 代码块执行完之后,系统会自动让线程释放对锁的占用;而 Lock 则必须要用户去手动释放锁,如果没有主动释放锁,就有可能导致出现死锁现象。
lock()方法是平常使用得最多的一个方法,就是用来获取锁。如果锁已被其他线程获取,则进行等待。采用 Lock,必须主动去释放锁,并且在发生异常时,不会自动释放锁。因此一般来说,使用 Lock 必须在try{}catch{}块中进行,并且将释放锁的操作放在finally 块中进行,以保证锁一定被被释放,防止死锁的发生。
复制代码
(1)多线程消费一个公共资源
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
LockDemo lockDemo = new LockDemo();
new Thread(()->{
for (int i = 0; i < 6; i++) {
lockDemo.consume();
}
},"Thread-Name-A").start();
new Thread(()->{
for (int i = 0; i < 6; i++) {
lockDemo.consume();
}
},"Thread-Name-B").start();
new Thread(()->{
for (int i = 0; i < 6; i++) {
lockDemo.consume();
}
},"Thread-Name-C").start();
}
}
class LockDemo{
private int number = 5;
private final ReentrantLock lock = new ReentrantLock();
public void consume(){
lock.lock();
try{
if(number > 0){
System.out.println("线程 "+Thread.currentThread().getName() + " 消费了 " + number-- + "个资源");
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
复制代码
(2)newCondition对象实现等待/通知模式
关键字 synchronized 与 wait()/notify()这两个方法一起使用可以实现等待/通知模式, Lock 锁的 newContition()方法返回 Condition 对象,Condition 类也可以实现等待/通知模式。用 notify()通知时,JVM 会随机唤醒某个等待的线程, 使用 Condition 类可以进行选择性通知, Condition 比较常用的两个方法:
1.await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重新获得锁并继续执行。
2.signal()用于唤醒一个等待的线程。
注意:在调用 Condition 的 await()/signal()方法前,也需要线程持有相关的 Lock 锁,调用 await()后线程会释放这个锁,在 singal()调用后会从当前Condition 对象的等待队列中,唤醒一个线程,唤醒的线程尝试获得锁, 一旦获得锁成功就继续执行。
复制代码
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test6 {
public static void main(String[] args) {
Share1 share = new Share1();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
}catch (Exception e){
e.printStackTrace();
}
}
},"Thread-Name-A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
}catch (Exception e){
e.printStackTrace();
}
}
},"Thread-Name-B").start();
}
}
class Share1{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void incr() throws Exception {
lock.lock();
try {
while(number !=0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + " :: "+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
public void decr() throws Exception {
lock.lock();
try {
while(number !=1){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + " :: "+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
}
复制代码
四、线程间的通信
线程间通信的模型有两种:共享内存和消息传递,以下方式都是基本这两种模型来实现的。
场景
复制代码
1.虚假唤醒问题
首先先介绍正常的通信,其实例子在上面 ·三· 中 已经演示了 我这里将代码再复制过来一份,一共是两种方案,一个是synchronized+wait+notify实现 等待/通知模式,第二个是newCondition对象实现 等待/通知模式。
复制代码
public class Test {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
}catch (Exception e){
e.printStackTrace();
}
}
},"Thread-Name-A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
}catch (Exception e){
e.printStackTrace();
}
}
},"Thread-Name-B").start();
}
}
class Share{
private int number = 0;
public synchronized void incr() throws Exception {
while(number !=0){
this.wait();
}
number++;
System.out.println(Thread.currentThread().getName() + " :: "+number);
this.notifyAll();
}
public synchronized void decr() throws Exception {
while(number !=1){
this.wait();
}
number--;
System.out.println(Thread.currentThread().getName() + " :: "+number);
this.notifyAll();
}
}
public class Test {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
}catch (Exception e){
e.printStackTrace();
}
}
},"Thread-Name-A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
}catch (Exception e){
e.printStackTrace();
}
}
},"Thread-Name-B").start();
}
}
class Share{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void incr() throws Exception {
lock.lock();
try {
while(number !=0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + " :: "+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
public void decr() throws Exception {
lock.lock();
try {
while(number !=1){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + " :: "+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
}
复制代码
但是如果将share类里的incr和decr方法中的while改成if,然后再开两个线程对应的去+和-,可以看到结果可能会出现: Thread-Name-A :: 2 Thread-Name-B :: -1 这就是虚假唤醒
复制代码
package com.practise.learn.Juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test {
public static void main(String[] args) {
Share share = new Share();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
}catch (Exception e){
e.printStackTrace();
}
}
},"Thread-Name-A").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
}catch (Exception e){
e.printStackTrace();
}
}
},"Thread-Name-B").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.incr();
}catch (Exception e){
e.printStackTrace();
}
}
},"Thread-Name-C").start();
new Thread(()->{
for (int i = 0; i < 10; i++) {
try {
share.decr();
}catch (Exception e){
e.printStackTrace();
}
}
},"Thread-Name-D").start();
}
}
class Share{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void incr() throws Exception {
lock.lock();
try {
if(number !=0){
condition.await();
}
number++;
System.out.println(Thread.currentThread().getName() + " :: "+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
public void decr() throws Exception {
lock.lock();
try {
if(number !=1){
condition.await();
}
number--;
System.out.println(Thread.currentThread().getName() + " :: "+number);
condition.signalAll();
}finally {
lock.unlock();
}
}
}
复制代码
造成虚假唤醒的原因就是 如果使用的是if,那么判断只会进入一次.当当前线程再次被执行的时候,就会直接跨过if判断 直接执行下面的代码了.
按照上面的例子来说,线程A、C负责+ 线程B、D负责-,
一开始A开始执行 此时值是1 到了if那的代码之后,发现判断不通过,此时开始等待。
然后C开始执行也到了if那的代码,此时值还是1,也发现判断不通过,此时也开始等待
最后碰巧又回到线程A,此时线程A就会继续执行if下面的代码了,这个情况就叫做虚假唤醒。
那么如果是使用的while,只要值不满足条件,那么他就会一直在while里循环,即不会被虚假唤醒.
复制代码
2.线程间的定制化通信
需求: A 线程打印 5 次 A,B 线程打印 10 次 B,C 线程打印 15 次 C,按照此顺序循环 10 轮
复制代码
package com.practise.learn.Juc;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Test7 {
public static void main(String[] args) {
Communication communication = new Communication();
new Thread(() ->{
for (int i = 1; i <= 10; i++) {
communication.printA(i);
}
}, "A 线程").start();
new Thread(() ->{
for (int i = 1; i <= 10; i++) {
communication.printB(i);
}
}, "B 线程").start();
new Thread(() ->{
for (int i = 1; i <= 10; i++) {
communication.printC(i);
}
}, "C 线程").start();
}
}
class Communication{
private int number = 0;
private Lock lock = new ReentrantLock();
private Condition conditionA = lock.newCondition();
private Condition conditionB = lock.newCondition();
private Condition conditionC = lock.newCondition();
public void printA(int j){
try {
lock.lock();
while (number != 0){
conditionA.await();
}
System.out.println(Thread.currentThread().getName() + "输出 A,第" + j + " 轮开始");
for (int i = 0; i < 5; i++) {
System.out.println("A");
}
number = 1;
conditionB.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printB(int j){
try {
lock.lock();
while (number != 1){
conditionB.await();
}
System.out.println(Thread.currentThread().getName() + "输出 B,第" + j + " 轮开始");
for (int i = 0; i < 10; i++) {
System.out.println("B");
}
number = 2;
conditionC.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void printC(int j){
try {
lock.lock();
while (number != 2){
conditionC.await();
}
System.out.println(Thread.currentThread().getName() + "输出 C,第" + j + " 轮开始");
for (int i = 0; i < 15; i++) {
System.out.println("C");
}
System.out.println("-----------------------------------------");
number = 0;
conditionA.signal();
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();
}
}
}
复制代码
五、集合的线程安全
1.List
(1)Vector(安全)
复制代码
public static void main(String[] args) {
List<String> list = new Vector();
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString());
System.out.println(list);
},"线程"+i).start();
}
}
复制代码
(2)Collections.synchronizedList(安全)
原理也是将每一个方法都直接加上synchronized关键字了
复制代码
public static void main(String[] args) {
List<String> list = Collections.synchronizedList(new ArrayList<>());
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString());
System.out.println(list);
},"线程"+i).start();
}
}
复制代码
(3)CopyOnWriteArrayList(安全)
CopyOnWriteArrayList相当于线程安全的ArrayList。是个可变数组;但是和ArrayList 不同的时,它具有以下特性:
1. 它最适合于具有以下特征的应用程序:List 大小通常保持很小,只读操作远多于可变操作,需要在遍历期间防止线程间的冲突。
2. 它是线程安全的。
3. 因为通常需要复制整个基础数组,所以可变操作(add()、set() 和 remove() 等等)的开销很大。
4. 迭代器支持 hasNext(), next()等不可变操作,但不支持可变 remove()等操作。
5. 使用迭代器进行遍历的速度很快,并且不会与其他线程发生冲突。在构造迭代器时,迭代器依赖于不变的数组快照。
复制思想
当我们往一个容器添加元素的时候,不直接往当前容器添加,而是先将当前容器进行 Copy,复制出一个新的容器,然后新的容器里添加元素,添加完元素之后,再将原容器的引用指向新的容器。
问题
这时候会抛出来一个新的问题,也就是数据不一致的问题。如果写线程还没来得及写会内存,其他的线程就会读到了脏数据。
处理办法
“动态数组”机制和“线程安全”机制
动态数组:
它内部有个“volatile 数组”(array)来保持数据。在“添加/修改/删除”数据时,都会新建一个数组,并将更新后的数据拷贝到新建的数组中,最后再将该数组赋值给“volatile 数组”, 这就是它叫做 CopyOnWriteArrayList 的原因.由于它在“添加/修改/删除”数据时,都会新建数组,所以涉及到修改数据的操作,CopyOnWriteArrayList 效率很低;但是单单只是进行遍历查找的话,效率比较高。
线程安全:
1.通过 volatile 和互斥锁来实现的。
2.通过“volatile 数组”来保存数据的。一个线程读取 volatile 数组时,总能看到其它线程对该 volatile 变量最后的写入;就这样,通过 volatile 提供了“读取到的数据总是最新的”这个机制的保证。
3.通过互斥锁来保护数据。在“添加/修改/删除”数据时,会先“获取互斥锁”,再修改完毕之后,先将数据更新到“volatile 数组”中,然后再“释放互斥锁”,就达到了保护数据的目的。
复制代码
public static void main(String[] args) {
List<String> list = new CopyOnWriteArrayList();
for (int i = 0; i < 100; i++) {
new Thread(()->{
list.add(UUID.randomUUID().toString());
System.out.println(list);
},"线程"+i).start();
}
}
public boolean add(E var1) {
ReentrantLock var2 = this.lock;
var2.lock();
boolean var6;
try {
Object[] var3 = this.getArray();
int var4 = var3.length;
Object[] var5 = Arrays.copyOf(var3, var4 + 1);
var5[var4] = var1;
this.setArray(var5);
var6 = true;
} finally {
var2.unlock();
}
return var6;
}
复制代码
2.Map
3.Set
六、多线程锁
一个对象里面如果有多个 synchronized 方法,某一个时刻内,只要一个线程去调用其中的一个 synchronized 方法了,其它的线程都只能等待。
换句话说,某一个时刻内,只能有唯一一个线程去访问这些synchronized 方法
锁的是当前对象 this,被锁定后,其它的线程都不能进入到当前对象的其它的synchronized 方法,加个普通方法后发现和同步锁无关,换成两个对象后,不是同一把锁了,情况立刻变化。
synchronized 实现同步的基础:Java 中的每一个对象都可以作为锁。具体表现为以下 3 种形式:
对于普通同步方法,锁是当前实例对象。
对于静态同步方法,锁是当前类的 Class 对象。
对于同步方法块,锁是 Synchonized 括号里配置的对象
当一个线程试图访问同步代码块时,它首先必须得到锁,退出或抛出异常时必须释放锁。也就是说如果一个实例对象的非静态同步方法获取锁后,该实例对象的其他非静态同步方法必须等待获取锁的方法释放锁后才能获取锁,
可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁,所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。所有的静态同步方法用的也是同一把锁——类对象本身,这两把锁是两个不同的对象,所以静态同步方法与非静态同步方法之间是不会有竞态条件的。但是一旦一个静态同步方法获取锁后,其他的静态同步方法都必须等待该方法释放锁后才能获取锁,而不管是同一个实例对象的静态同步方法之间,还是不同的实例对象的静态同步方法之间,只要它们同一个类的实例对象!
复制代码
1.锁的八种情况演示
七、JUC三大辅助类
1.CountDownLatch(减少计数)
CountDownLatch 类可以设置一个计数器,然后通过 countDown 方法来进行减 1 的操作,使用 await 方法等待计数器不大于 0,然后继续执行 await 方法之后的语句。
1.CountDownLatch 主要有两个方法,当一个或多个线程调用 await 方法时,这些线程会阻塞
2.其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程不会阻塞)
3.当计数器的值变为 0 时,因 await 方法阻塞的线程会被唤醒,继续执行
复制代码
CountDownLatch的两种使用场景
场景1 让单个线程等待:多个线程(任务)完成后,进行汇总合并
场景2 让多个线程等待:模拟并发,让并发线程一起执行
复制代码
import java.util.concurrent.CountDownLatch;
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(3);
new Thread(()->{
System.out.println("这里模拟查询数据库user表");
countDownLatch.countDown();
},"Thread-Name-getUser").start();
new Thread(()->{
System.out.println("这里模拟查询数据库department表");
countDownLatch.countDown();
},"Thread-Name-getDepartment").start();
new Thread(()->{
System.out.println("这里模拟查询数据库auth表");
countDownLatch.countDown();
},"Thread-Name-getAuth").start();
System.out.println("主线程睡觉");
countDownLatch.await();
System.out.println("全部查询完毕,现在的计数器为" + countDownLatch.getCount());
}
}
复制代码
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(1);
new Thread(()->{
try {
countDownLatch.await();
System.out.println("这里模拟第一个人 抢票/秒杀 的动作");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Thread-Name-User1").start();
new Thread(()->{
try {
countDownLatch.await();
System.out.println("这里模拟第二个人 抢票/秒杀 的动作");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Thread-Name-User2").start();
new Thread(()->{
try {
countDownLatch.await();
System.out.println("这里模拟第三个人 抢票/秒杀 的动作");
} catch (InterruptedException e) {
e.printStackTrace();
}
},"Thread-Name-User3").start();
System.out.println("所有人准备完毕,准备开抢----");
TimeUnit.SECONDS.sleep(2L);
countDownLatch.countDown();
}
}
复制代码
2.CyclicBarrier(循环栏栅)
CyclicBarrier 看英文单词可以看出大概就是循环阻塞的意思,在使用中CyclicBarrier 的构造方法第一个参数是目标障碍数,每次执行 CyclicBarrier 一次障碍数会加一,如果达到了目标障碍数,才会执行 cyclicBarrier.await()之后的语句。可以将 CyclicBarrier 理解为加 1 操作.
看上去CyclicBarrier与CountDownLatch一个是+1操作,一个是-1操作,没有什么区别,
复制代码
public class CyclicBarrierDemo {
public static void main(String[] args) {
CyclicBarrier cyclicBarrier = new CyclicBarrier(3,()->{
System.out.println("下面三个线程都执行到await方法,会调此方法");
});
new Thread(()->{
try {
System.out.println("线程A的逻辑 比如处理某个数据处理到一个程度的时候需要等待其他线程结束之后用其他线程的值才能继续向下操作");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},"Thread-Name-A").start();
new Thread(()->{
try {
System.out.println("线程B的逻辑 比如处理某个数据处理到一个程度的时候需要等待其他线程结束之后用其他线程的值才能继续向下操作");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},"Thread-Name-B").start();
new Thread(()->{
try {
System.out.println("线程C的逻辑 比如处理某个数据处理到一个程度的时候需要等待其他线程结束之后用其他线程的值才能继续向下操作");
cyclicBarrier.await();
} catch (Exception e) {
e.printStackTrace();
}
},"Thread-Name-C").start();
}
}
复制代码
CountDownLatch和CyclicBarrier区别的理解:
CountDownLatch:一个或者多个线程,等待其他线程完成某件事情之后,等待其他多个线程完成某件事情之后才能执行;
CyclicBarrier:多个线程互相等待,直到到达同一个同步点,再继续一起执行;
对于CountDownLatch来说,重点是一个线程(多个线程)等待”,而其他的N个线程在完成”某件事情“之后,可以终止,也可以等待.
而对于CyclicBarrier,重点是多个线程,在任意一个线程没有完成,所有的线程都必须等待;
CountDownLatch是计数器,线程完成一个记录一个,只不过计数不是递增而是递减.
而CyclicBarrier更像是一个阀门,需要所有线程都要到达,阀门才能打开,然后继续执行;
复制代码
CountDownLatch |
CyclicBarrier |
减记数方式 |
加记数方式 |
计算为0时释放所有等待的线程 |
计数达到指定值时释放所有等待的线程 |
计数为0时,无法重置 |
计数达到指定值时,计数置为0重新开始 |
不可重复使用 |
可重复使用 |
调用countDown方法计数-1,调用await方法只进行阻塞,对计数没任何影响 |
调用await方法计数+1,若+1后的值不等于构造方法的值,则线程阻塞 |
3.Semaphore(信号灯)
Semaphore是一个计数[信号量],常用于限制可以访问某些资源(物理或逻辑的)线程数目。
Semaphore 的构造方法中传入的第一个参数是最大信号量(可以看成最大线程池),每个信号量初始化为一个最多只能分发一个许可证。
使用 acquire 方法获得许可证,release 方法释放许可。
复制代码
public class SemaphoreDemo {
public static void main(String[] args) throws Exception{
Semaphore semaphore = new Semaphore(2);
for (int i = 1; i <= 3; i++) {
Thread.sleep(100);
new Thread(() ->{
try {
System.out.println(Thread.currentThread().getName() + "开始找车位");
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();
}
}
}
复制代码
八、读写锁ReentrantReadWriteLock
九、阻塞队列
1.概念和基本方法
在多线程中,常常会遇到这样的场景:
1.即在某些情况下需要挂起当前线程(阻塞),一旦条件满足,被挂起的线程又被自动换起来.
2.生产者-消费者模型中,生产者往一个队列中存放需要处理的数据,消费者从队列中获取数据,当队列满了,生产者会先阻塞直到队列中有数据被消费者消费,或者当队列中无数据了,消费者会阻塞直到队列中被生产者再次存放数据。
那么BlockingQueue(阻塞队列)可以很好的解决上述场景下的问题。阻塞队列,首先它是一个队列,通过一个共享的队列,可以使得数据由队列的一端输入,从另一端输出。
1.当队列是空的,从队列中获取元素的操作将会被阻塞
2.当队列是满的,从队列中添加元素的操作将会被阻塞
3.当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起),直到有数据放入队列
4.当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起),直到队列中有空的位置,线程被自动唤醒
使用BlockingQueue(阻塞队列)可以让我们不需要关心什么时候阻塞线程,什么时候唤醒线程,因为这些操作都由BlockingQueue(阻塞队列)帮助我们来做了。
BlockingQueue核心方法:
复制代码
方法类型 |
抛出异常 |
特殊值 |
阻塞 |
超时 |
插入 |
add(e) |
offer(e) |
put(e) |
offer(e,time,unit) |
移除 |
remove() |
poll() |
take() |
poll(time,unit) |
检查 |
element() |
peek() |
不可用 |
不可用 |
抛出异常
当阻塞队列满时,再往队列里add插入元素会抛出illegalStateException:Queue full
当阻塞队列空时,再往队列里remove移除元素会抛出NoSuchElementException
特殊值
插入方法,成功true失败false
移除方法,成功返回出队列的元素,队列里没有就返回null
一直阻塞
当阻塞队列满时,生产者线程继续往队列里put元素, 队列会一直阻塞生产者线程直到put数据or响应中断退出
当阻塞队列空时 ,消费者线程试图从队列里take元素, 队列会一直阻塞消费者线程直到队列可用
超时退出
当阻塞队列满时,队列会阻塞生产者线程一定时间,超过限时后生产者线程会退出
复制代码
常见的阻塞队列
1.ArrayBlockingQueue : 由数组结构组成的有界阻塞队列。
2.LinkedBlockingQueue : 由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列。
3.DelayQueue : 基于PriorityQueue实现的支持延时获取元素的无界阻塞队列。
4.PriorityBlockingQueue : 支持优先级排序的无界阻塞队列。
5.SynchronousQueue : 不存储元素的阻塞队列,也即单个元素的队列。
6.LinkedTransferQueue : 由链表组成的无界阻塞队列。
7.LinkedBlockingDeque : 由链表组成的双向阻塞队列。
复制代码
2.ArrayBlockingQueue(常用)
ArrayBlockingQueue是基于数组(array-based)的先进先出(FIFO)有界(bounded)阻塞队列。
1.创建队列时,必须要指定队列容量(capacity),即数组大小。
2.创建队列时,可以传入Collection来初始化队列元素。
3.队列一旦被创建,那么队列容量不能被改变。
4.队列支持公平模式和非公平模式,默认非公平模式(false)。
5.队列中只有一把锁,写锁和读锁未分离,并发控制采用了经典的two-condition(notEmpty、notFull)算法。
复制代码
(1)插入
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
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.add("d"));
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("d"));
try {
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
blockingQueue.put("d");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("b", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("c", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("d", 2L, TimeUnit.SECONDS));
}
}
复制代码
(2)移除
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.add("a");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
}
}
复制代码
(3)检查
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.add("a");
System.out.println(blockingQueue.element());
blockingQueue.remove();
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.peek());
blockingQueue.remove();
System.out.println(blockingQueue.peek());
}
}
复制代码
(4)其他方法
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);
blockingQueue.add("a");
blockingQueue.add("b");
blockingQueue.add("c");
System.out.println(blockingQueue.contains("a"));
System.out.println(blockingQueue.containsAll(Arrays.asList("a")));
System.out.println(blockingQueue.containsAll(Arrays.asList("f")));
System.out.println(blockingQueue.containsAll(Arrays.asList("a", "b")));
System.out.println(blockingQueue.containsAll(Arrays.asList("a", "d")));
System.out.println(blockingQueue.containsAll(Arrays.asList("a","b","c")));
System.out.println(blockingQueue.size());
List<String> list1 = new CopyOnWriteArrayList<>();
System.out.println(blockingQueue.drainTo(list1));
System.out.println(list1);
List<String> list2 = new CopyOnWriteArrayList<>();
System.out.println(blockingQueue.drainTo(list2,2));
System.out.println(list2);
List<String> list3 = new CopyOnWriteArrayList<>();
System.out.println(blockingQueue.drainTo(list3,4));
System.out.println(list3);
Set<String> set = new HashSet<>();
int i = blockingQueue.drainTo(set);
System.out.println(set);
System.out.println(i);
List<String> list = new ArrayList<>();
blockingQueue.drainTo(list);
System.out.println(list);
blockingQueue.clear();
System.out.println(blockingQueue.size());
System.out.println(blockingQueue.isEmpty());
Iterator<String> iterator = blockingQueue.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
Spliterator<String> spliterator = blockingQueue.spliterator();
spliterator.forEachRemaining(s->{
System.out.println(s);
});
}
}
复制代码
3.LinkedBlockingQueue(常用)
由链表结构组成的有界(但大小默认值为integer.MAX_VALUE = 2147483647)阻塞队列。
LinkedBlockingQueue是基于链表(linked nodes)的先进先出(FIFO)的可选界(optionally-bounded)的阻塞队列。
1.创建队列时,为了避免额外开销,可以指定队列容量(capacity);如果不指定队列容量,那么默认队列容量为Integer.MAX_VALUE。
2.创建队列时,可以传入Collection来初始化队列元素,此时不能指定队列容量,默认为Integer.MAX_VALUE。
3.队列中的count即当前队列元素个数,采用AtomicInteger,避免put和take的竞争。
4.与ArrayBlockingQueue不同的是,LinkedBlockingQueue队列中有两把锁,读锁和写锁是分离的。
5.在使用LinkedBlockingQueue时,若队列大小为默认值,且生产速度大于消费速度时,可能会内存溢出。
6.LinkedBlockingQueue理论上来说比ArrayBlockingQueue有更高的吞吐量,但是在大多数的实际应用场景中,却没有很好的表现。
复制代码
(1)插入
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue1 = new LinkedBlockingDeque<>(3);
BlockingQueue<String> blockingQueue2 = new LinkedBlockingDeque<>();
BlockingQueue<String> blockingQueue3 = new LinkedBlockingDeque<>(Arrays.asList("1","2"));
System.out.println(blockingQueue1.remainingCapacity());
System.out.println(blockingQueue2.remainingCapacity());
System.out.println(blockingQueue3.remainingCapacity());
System.out.println(Integer.MAX_VALUE);
BlockingQueue<String> blockingQueue = new LinkedBlockingDeque<>(2);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
try {
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("b", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("c", 2L, TimeUnit.SECONDS));
}
复制代码
(2)移除
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new LinkedBlockingDeque<>(3);
blockingQueue.add("a");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
}
复制代码
(3)检查
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(3);
blockingQueue.add("a");
System.out.println(blockingQueue.element());
blockingQueue.remove();
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.peek());
blockingQueue.remove();
System.out.println(blockingQueue.peek());
}
复制代码
(4)其他方法
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new LinkedBlockingQueue<>(3);
blockingQueue.add("a");
blockingQueue.add("b");
blockingQueue.add("c");
System.out.println(blockingQueue.contains("a"));
System.out.println(blockingQueue.containsAll(Arrays.asList("a")));
System.out.println(blockingQueue.containsAll(Arrays.asList("f")));
System.out.println(blockingQueue.containsAll(Arrays.asList("a", "b")));
System.out.println(blockingQueue.containsAll(Arrays.asList("a", "d")));
System.out.println(blockingQueue.containsAll(Arrays.asList("a","b","c")));
System.out.println(blockingQueue.size());
List<String> list1 = new CopyOnWriteArrayList<>();
System.out.println(blockingQueue.drainTo(list1));
System.out.println(list1);
List<String> list2 = new CopyOnWriteArrayList<>();
System.out.println(blockingQueue.drainTo(list2,2));
System.out.println(list2);
List<String> list3 = new CopyOnWriteArrayList<>();
System.out.println(blockingQueue.drainTo(list3,4));
System.out.println(list3);
Set<String> set = new HashSet<>();
int i = blockingQueue.drainTo(set);
System.out.println(set);
System.out.println(i);
List<String> list = new ArrayList<>();
blockingQueue.drainTo(list);
System.out.println(list);
blockingQueue.clear();
System.out.println(blockingQueue.size());
System.out.println(blockingQueue.isEmpty());
Iterator<String> iterator = blockingQueue.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
Spliterator<String> spliterator = blockingQueue.spliterator();
spliterator.forEachRemaining(s->{
System.out.println(s);
});
}
复制代码
4.DelayQueue
DelayQueue是基于PriorityQueue实现的支持延时获取元素的无界阻塞队列。
1.DelayQueue中存放的对象必须实现Delayed接口。
2.如果没有到期元素,那么就没有head,poll方法返回null。
3.当一个元素的getDelay(TimeUnit.NANOSECONDS)返回值小于等于0时,该元素过期。
4.虽然不能用take和poll移除未过期的元素,但是这些未过期的元素仍然和过期元素一样同等对待。例如,size方法返回的数量就是过期元素和未过期元素的之和。
5.DelayQueue中的元素只有当其指定的延迟时间到了,才能够从队列中获取到该元素。
6.DelayQueue是一个没有大小限制的队列,因此往队列中插入数据的操作(生产者)永远不会被阻塞,而只有获取数据的操作(消费者)才会被阻塞。
7.DelayQueue适用于在一定时间后,做某些业务处理。
(1)关闭空闲链接。服务器中有很多空闲链接,在一定时间后,关闭他们。
(2)删除过期缓存。在一定时间后,删除某些缓存的对象。
(3)任务超时处理。在网络协议滑动窗口请求应答式交互时,处理超时未响应的请求。
(4)生成订单后的60秒后,给用户发送短信通知。
(5)下单15分钟后,如果用户不付款就关闭订单。
(6)待处理申请超时1天,通知审核人员经理,超时2天通知审核人员总监
DelayQueue非常有用,可以运用在以下两个应用场景:
缓存系统的设计:使用DelayQueue保存缓存元素的有效期,使用一个线程循环查询DelayQueue,一旦能从DelayQueue中获取元素时,就表示有缓存到期了。
定时任务调度:使用DelayQueue保存当天要执行的任务和执行时间,一旦从DelayQueue中获取到任务就开始执行,比如Timer就是使用DelayQueue实现的。
(7)缺点也很明显,它是内存存储,对分布式支持不友好,如果发生单点故障,可能会造成数据丢失,无界队列还存在 OOM 的风险。
复制代码
插入、移除、检查等方法参考上面的就可以了,基本的api都是一样的,这里不过多赘述
复制代码
(1)DelayQueue实现延迟任务处理
t.zoukankan.com/gaomanito-p…
5.PriorityBlockingQueue
PriorityBlockingQueue是基于数组(array based)的支持优先级的无界(unbounded)的阻塞队列。此队列的数据结构是堆。
1.创建队列时,如果未指定初始化容量(initialCapacity),那么默认初始化容量DEFAULT_INITIAL_CAPACITY为11。
2.创建队列时,可以指定队列初始化容量(initialCapacity),不是队列容量(capacity)。
3.对队列中的元素进行排序,如果未指定比较器,插入队列的元素必须实现Comparable接口
4.内部基于数组实现的最小二叉堆算法
5.队列的长度是可扩展的(类似ArrayList),上限为Integer.MAX_VALUE - 8
6.PriorityBlockingQueue 并不会阻塞数据生产者,而只会在没有可消费的数据时,阻塞数据的消费者。因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。
7.在实现 PriorityBlockingQueue 时,内部控制线程同步的锁采用的是公平锁。
复制代码
(1)插入
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> priorityBlockingQueue1 = new PriorityBlockingQueue<>();
BlockingQueue<String> priorityBlockingQueue2 = new PriorityBlockingQueue<>(3);
BlockingQueue<String> priorityBlockingQueue3 = new PriorityBlockingQueue<>(Arrays.asList("1","2"));
System.out.println(priorityBlockingQueue1.remainingCapacity());
System.out.println(priorityBlockingQueue2.remainingCapacity());
System.out.println(priorityBlockingQueue3.remainingCapacity());
System.out.println(priorityBlockingQueue1.size());
System.out.println(priorityBlockingQueue2.size());
System.out.println(priorityBlockingQueue3.size());
BlockingQueue<String> blockingQueue = new PriorityBlockingQueue<>(2);
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.offer("e"));
System.out.println(blockingQueue.offer("f"));
try {
blockingQueue.put("g");
blockingQueue.put("h");
blockingQueue.put("i");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("b", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("c", 2L, TimeUnit.SECONDS));
}
复制代码
(2)移除
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new PriorityBlockingQueue<>(3);
blockingQueue.add("a");
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll(2L, TimeUnit.SECONDS));
}
复制代码
(3)检查
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new PriorityBlockingQueue<>(3);
blockingQueue.add("a");
System.out.println(blockingQueue.element());
blockingQueue.remove();
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.peek());
blockingQueue.remove();
System.out.println(blockingQueue.peek());
}
复制代码
(4)其他方法
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new PriorityBlockingQueue<>(3);
blockingQueue.add("a");
blockingQueue.add("b");
blockingQueue.add("c");
System.out.println(blockingQueue.contains("a"));
System.out.println(blockingQueue.containsAll(Arrays.asList("a")));
System.out.println(blockingQueue.containsAll(Arrays.asList("f")));
System.out.println(blockingQueue.containsAll(Arrays.asList("a", "b")));
System.out.println(blockingQueue.containsAll(Arrays.asList("a", "d")));
System.out.println(blockingQueue.containsAll(Arrays.asList("a","b","c")));
System.out.println(blockingQueue.size());
List<String> list1 = new CopyOnWriteArrayList<>();
System.out.println(blockingQueue.drainTo(list1));
System.out.println(list1);
List<String> list2 = new CopyOnWriteArrayList<>();
System.out.println(blockingQueue.drainTo(list2,2));
System.out.println(list2);
List<String> list3 = new CopyOnWriteArrayList<>();
System.out.println(blockingQueue.drainTo(list3,4));
System.out.println(list3);
Set<String> set = new HashSet<>();
int i = blockingQueue.drainTo(set);
System.out.println(set);
System.out.println(i);
List<String> list = new ArrayList<>();
blockingQueue.drainTo(list);
System.out.println(list);
blockingQueue.clear();
System.out.println(blockingQueue.size());
System.out.println(blockingQueue.isEmpty());
Iterator<String> iterator = blockingQueue.iterator();
while (iterator.hasNext()) {
System.out.println(iterator.next());
}
Spliterator<String> spliterator = blockingQueue.spliterator();
spliterator.forEachRemaining(s->{
System.out.println(s);
});
}
复制代码
(5)优先级
始终把[优先级]最高的放在第一位方便取,其他不变,因为在去的过程中可能会添加下个数据,
可能是优先级高的,如果不高则取下一个优先级高的放在第一位,以此类推,二分法
复制代码
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User implements Comparable<User>{
private String name;
private Integer age;
@Override
public int compareTo(User o) {
if(this.age > o.age){
return 1;
}else if(this.age < o.age){
return -1;
}else{
return 0;
}
}
}
public static void main(String[] args) throws InterruptedException {
BlockingQueue<User> blockingQueue = new PriorityBlockingQueue<>(3);
blockingQueue.add(new User("张三",21));
blockingQueue.add(new User("李四",25));
blockingQueue.add(new User("王五",10));
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
}
复制代码
6.SynchronousQueue
SynchronousQueue是一个没有数据缓冲的BlockingQueue。或者说不存储元素的阻塞队列,也即单个元素的队列。
1.一个线程的插入必须等待另一个线程的删除操作才能完成,反之亦然。
2.SynchronousQueue适合传递性设计(handoff designs),即一个线程中运行的对象,需要将某些信息、任务或者事件等传递给另一个线程中运行的对象的场景。
3.Executors.newCachedThreadPool()中就使用了SynchronousQueue队列。
4.不能在同步队列上进行peek,因为仅在试图要移除元素时,该元素才存在。
5.不能迭代队列,因为其中没有元素可用于迭代。
6.SynchronousQueue支持公平和非公平模式。
(1)公平模式:SynchronousQueue 会采用公平锁,并配合一个 FIFO 队列来阻塞多余的生产者和消费者,从而体系整体的公平策略;
(2)非公平模式(SynchronousQueue 默认):SynchronousQueue 采用非公平锁,同时配合一个 LIFO队列来管理多余的生产者和消费者,而后一种模式,如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有某些生产者或者是消费者的数据永远都得不到处理.
7.SynchronousQueue是[线程安全]的。
SynchronousQueue和其他的BlockingQueue不同的是SynchronousQueue的capacity是0。
即SynchronousQueue不存储任何元素。
复制代码
(1)插入
public static void main(String[] args) throws Exception {
BlockingQueue<String> blockingQueue1 = new SynchronousQueue<>();
BlockingQueue<String> blockingQueue2 = new SynchronousQueue<>(false);
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
System.out.println(blockingQueue.remainingCapacity());
System.out.println(blockingQueue.size());
try {
blockingQueue.put("g");
System.out.println("11");
blockingQueue.put("h");
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(blockingQueue.offer("a", 2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("b", 2L, TimeUnit.SECONDS));
blockingQueue.add("a");
System.out.println(blockingQueue.offer("d"));
System.out.println(blockingQueue.size());
System.out.println(blockingQueue.offer("e"));
}
复制代码
(2)移除
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
new Thread(()->{
System.out.println("线程A开始");
try {
blockingQueue.offer("1111",2L,TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程A结束");
},"线程A").start();
new Thread(()->{
System.out.println("线程B开始");
String removeValue = blockingQueue.remove();
System.out.println(removeValue);
System.out.println("线程B结束");
},"线程B").start();
new Thread(()->{
System.out.println("线程C开始");
try {
blockingQueue.offer("1111",2L,TimeUnit.SECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("线程C结束");
},"线程C").start();
new Thread(()->{
System.out.println("线程D开始");
blockingQueue.remove("1111");
System.out.println("线程D结束");
},"线程D").start();
}
复制代码
7.LinkedTransferQueue
LinkedTransferQueue是基于链表(linked nodes)的无界(unbounded)阻塞队列。
1.相对于其他阻塞队列,LinkedTransferQueue 多了 tryTransfer 和transfer 方法。
2.无界队列(Integer.MAX_VALUE),进出队列采用FIFO(先进先出)原则。
3.生产者会一直阻塞直到所添加到队列的元素被某一个消费者所消费。主要用于线程间消息的传递,与SynchronousQueue很类似,但是比起SynchronousQueue更好用。
4.LinkedTransferQueue既可以使用BlockingQueue的put方法进行常规的添加元素操作,也可以使用transfer方法进行阻塞添加。
5.相比SynchronousQueue灵活之处在于,队列长度非0,阻塞插入和非阻塞插入的元素可以共存。
6.如果当前有消费者正在等待接收元素(消费者使用take()方法或带时间限制的poll()方法时),transfer方法可以把生产者传入的元素立刻transfer(传输)给消费者。如果没有消费者在等待接收元素,transfer方法会将元素存放在队列的tail节点,并等到该元素被消费者消费了才返回。
7.LinkedTransferQueue采用一种预占模式。意思就是消费者线程取元素时,如果队列不为空,则直接取走数据,若队列为空,那就生成一个节点(节点元素为null)入队,然后消费者线程被等待在这个节点上,后面生产者线程入队时发现有一个元素为 null 的节点,生产者线程就不入队了,直接就将元素填充到该节点,并唤醒该节点等待的线程,被唤醒的消费者线程取走元素,从调用的方法返回。
复制代码
8.LinkedBlockingDeque
LinkedBlockingDueue是基于链表(linked nodes)的可选界(optionally-bounded)的双向阻塞队列。
1.该阻塞队列同时支持FIFO和FILO两种操作方式,即可以从队列的头和尾同时操作(插入/删除)。
2.该阻塞队列是支持线程安全。
3.在初始化LinkedBlockingDeque时可以设置容量防止其过渡膨胀。
4.双向阻塞队列可以运用在“工作窃取”(work stealing)模式中。
5.插入元素时: 如果当前队列已满将会进入阻塞状态,一直等到队列有空的位置时再将该元素插入,该操作可以通过设置超时参数,超时后返回 false 表示操作失败,也可以不设置超时参数一直阻塞,中断后抛出 InterruptedException 异常
6.读取元素时: 如果当前队列为空会阻塞住直到队列不为空然后返回元素,同样可以通过设置超时参数
复制代码
十、线程池
1.基本概念
线程池(英语:thread pool)
一种线程使用模式。线程过多会带来调度开销,进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
简单来说,线程池,其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作,无需反复创建线程而消耗过多的资源。
线程池的优势
线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
它的主要特点为
1.降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
2.提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
3.提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
复制代码
2.组成
1.线程管理器:用于创建并管理线程池。
2.工作线程:线程池中的线程。
3.任务接口:每个任务必须实现的接口,用于工作线程调度其运行。
4.任务队列:用于存放待处理的任务,提供一种缓冲机制。
java中的线程池是通过Executor框架实现的,该框架中用到了Executor、Executors、ExecutorService、ThreadPoolExecutor、Callable、Future和FutureTask这几个类。
复制代码
3.七个核心参数
1.corePoolSize:线程池中的核心线程数量
2.maximumPoolSize:线程池中的最大线程数量
3.keepAliveTime:当前线程数量超过corePoolSize时,多余的空闲线程的存活时间,即多长时间内会被销毁
4.unit:存活时间(keepAliveTime)的单位
5.workQueue:任务队列,存放提交但未执行任务的队列
6.threadFactory:线程工厂,用于创建线程,一般用默认的即可
7.handler:拒绝策略,当任务太多来不及处理(等待队列满),如何拒绝任务。
复制代码
4.线程池工作过程
1.线程池刚创建时,里面没有一个线程。任务队列是作为参数传进来的。不过,就算队列里面有任务,线程池也不会马上执行他们。
2.当调用execute()方法添加一个任务时,线程池会做如下判断:
(1)如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务。
(2)如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列。
(3)如果这时候队列满了,而且正在运行的线程数量小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务。
(4)如果队列满了,而且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会抛出异常RejectExecutionException。
3.当一个线程完成任务时,它会从队列中取下一个任务来执行。
4.当一个线程无事可做,超过一定的时间(keepAliveTime)时,线程池会判断,如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
复制代码
5.拒绝策略
线程池中的线程已经用完了,无法继续为新任务服务,同时,等待队列也已经排满了,再也塞不下新任务了。这时候我们就需要拒绝策略机制合理的处理这个问题。
线程池中,有三个重要的参数,决定影响了拒绝策略:
1.corePoolSize - 核心线程数,也即最小的线程数。
2.workQueue - 阻塞队列 。
3.maximumPoolSize -最大线程数
当提交任务数大于 corePoolSize 的时候,会优先将任务放到 workQueue 阻塞队列中。当阻塞队列饱和后,会扩充线程池中线程数,直到达到maximumPoolSize 最大线程数配置。此时,再多余的任务,则会触发线程池
的拒绝策略了。总结起来,也就是一句话,当提交的任务数大于(workQueue.size() + maximumPoolSize ),就会触发线程池的拒绝策略。
JDK内置的拒绝策略如下:
1.AbortPolicy:直接抛出异常,阻止系统正常运行。
2.CallerRunsPolicy:只要线程池未关闭,该策略直接在调用者线程中,运行当前被废弃的任务。显然这样做不会真的丢弃任务,但是,任务提交线程的性能极有可能急剧下降。
3.DiscardOldestPolicy:丢弃最老的一个请求,也就是即将被执行的一个任务,并尝试再次提交当前任务。
4.DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任务处理。如果允许任务丢失,这是最好的一种策略。
5.以上内置拒绝策略均实现了RejectedExecutionHandler接口,若以上策略仍无法满足实际需要,完全可以自己扩展RejectedExecutionHandler接口。
复制代码
6.常用线程池
项目中创建多线程时,使用常见的三种线程池创建方式,单一、可变、定长都有一定问题,原因是 FixedThreadPool 和 SingleThreadExecutor 底层都是用LinkedBlockingQueue 实现的,这个队列最大长度为 Integer.MAX_VALUE,容易导致 OOM。所以实际生产一般自己通过 ThreadPoolExecutor 的 7 个参数自定义线程池。不允许适用不允许 Executors.的方式手动创建线程池
阿里巴巴 Java 开发手册
【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
Executors 返回的线程池对象的弊端如下:
1.FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为Integer.MAX VALUE,可能会堆积大量的请求,从而导致 OOM。
2.CachedThreadPool 和ScheduledThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM.
复制代码
(1)newCachedThreadPool(常用)
作用
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程.
特点
1.线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
2.线程池中的线程可进行缓存重复利用和回收(回收默认时间为 1 分钟)
3.当线程池中,没有可用线程,会重新创建一个线程
适用场景
适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较短,任务多的场景
复制代码
(2)newFixedThreadPool(常用)
作用
创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这些线程。在任意点,在大多数线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池中的线程将一直存在。
特征
1.线程池中的线程处于一定的量,可以很好的控制线程的并发量
2.线程可以重复被使用,在显示关闭之前,都将一直存在
3.超出一定量的线程被提交时候需在队列中等待
适用场景
适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严格限制的场景
复制代码
(3)newSingleThreadExecutor(常用)
作用
创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程,那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的newFixedThreadPool 不同,可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。
特征
线程池中最多执行 1 个线程,之后提交的线程活动将会排在队列中以此执行
场景
适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个线程的场景
复制代码
(4)newScheduleThreadPool(了解)
作用
线程池支持定时以及周期性执行任务,创建一个 corePoolSize 为传入参数,最大线程数为整形的最大数的线程池
特征
1.线程池中具有指定数量的线程,即便是空线程也将保留
2.可定时或者延迟执行线程活动
场景
适用于需要多个后台线程执行周期任务的场景
复制代码
(5)newWorkStealingPool
jdk1.8 提供的线程池,底层使用的是 ForkJoinPool 实现,创建一个拥有多个任务队列的线程池,可以减少连接数,创建当前可用 cpu 核数的线程来并行执行任务
场景
适用于大耗时,可并行执行的场景
复制代码
十一、Fork/Join框架
十二、CompletableFuture
1. CompletableFuture介绍
# CompletableFuture
CompletableFuture在Java里面被用于异步编程,异步通常意味着非阻塞,可以使得我们的任务单独运行在与主线程分离的其他线程中,并且通过回调可以在主线程中得到异步任务的执行状态,是否完成,和是否异常等信息。CompletableFuture 实现了Future,CompletionStage接口,实现了Future接口就可以兼容现在有线程池框架,而CompletionStage接口才是异步编程的接口抽象,里面定义多种异步方法,通过这两者集合,从而打造出了强大的CompletableFuture 类。
# Future
Future 在 Java 里面,通常用来表示一个异步任务的引用,比如我们将任务提交到线程池里面,然后我们会得到一个Future,在Future里面有isDone方法来判断任务是否处理结束,还有get方法可以一直阻塞直到任务结束然后获取结果,但整体来说这种方式,还是同步的,因为需要客户端不断阻塞等待或者不断轮询才能知道任务是否完成。
# Future缺点
(1)不支持手动完成
我提交了一个任务,但是执行太慢了,我通过其他路径已经获取到了任务结果,现在没法把这个任务结果通知到正在执行的线程,所以必须主动取消或者一直等待它执行完成
(2)不支持进一步的非阻塞调用
通过 Future 的 get 方法会一直阻塞到任务完成,但是想在获取任务之后执行额外的任务,因为 Future 不支持回调函数,所以无法实现这个功能
(3)不支持链式调用
对于 Future 的执行结果,我们想继续传到下一个 Future 处理使用,从而形成一个链式的 pipline 调用,这在 Future 中是没法实现的。
(4)不支持多个 Future 合并
比如我们有 10 个 Future 并行执行,我们想在所有的 Future 运行完毕之后,执行某些函数,是没法通过 Future 实现的。
(5)不支持异常处理
Future 的 API 没有任何的异常处理的 api,所以在异步运行时,如果出了问题是不好定位的。
# completableFuture使用场景
1.创建异步任务
2.简单任务异步回调
3.多个任务组合处理
# 为什么引入CompletableFuture
对于jdk1.5的Future,虽然提供了异步处理任务的能力,但是获取结果的方式很不优雅,还是需要通过阻塞(或者轮训)的方式。CompletableFuture通过扩展Future,引入函数式编程,通过回调的方式去处理结果。
复制代码
这里提供一个简单的例子,作为CompletableFuture的入门。
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
CompletableFuture<String> future = new CompletableFuture<>();
new Thread(()->{
try {
System.out.println("子线程的代码逻辑---开始");
TimeUnit.SECONDS.sleep(5L);
System.out.println("子线程的代码逻辑---结束");
future.complete("success");
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
System.out.println("主线程调用get方法阻塞 结果为"+future.get());
System.out.println("主线程结束阻塞");
}
}
复制代码
2. 创建线程
1. runAsync(无返回值)
CompletableFuture.runAsync() 创建一个无返回值的异步任务
复制代码
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
System.out.println("主线程的代码逻辑---开始");
CompletableFuture<Void> future = CompletableFuture.runAsync(()->{
try {
System.out.println("子线程的代码逻辑---开始");
TimeUnit.SECONDS.sleep(5L);
System.out.println("子线程的代码逻辑---结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
future.get();
System.out.println("主线程的代码逻辑---结束");
}
}
复制代码
2. supplyAsync(有返回值)
CompletableFuture.supplyAsync() 有返回值的异步任务
复制代码
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
System.out.println("主线程的代码逻辑---开始");
CompletableFuture<String> future = CompletableFuture.supplyAsync(()->{
try {
System.out.println("子线程的代码逻辑---开始");
TimeUnit.SECONDS.sleep(5L);
System.out.println("子线程的代码逻辑---结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
return "子线程结束";
});
future.get();
System.out.println("主线程的代码逻辑---结束,子线程返回值为" + future.get());
}
}
复制代码
3. 回调函数
特点 |
thenApply |
thenAccept |
thenRun |
入参 |
有 |
有 |
无 |
返回值 |
有 |
无 |
无 |
1. thenApply/thenApplyAsync(转换结果)
定义: 当一个线程依赖另一个线程时,可以使用 thenApply 方法来把这两个线程串行化。thenApply是同步的,thenApplyAsync是异步的。
理解: 把上一个线程的结果应用于下一个线程的计算。相当于结果值的传递。
场景: 使用先对一个数加 10,然后取平方
复制代码
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
System.out.println("主线程的代码逻辑---开始");
AtomicInteger num = new AtomicInteger(1);
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
try {
System.out.println("子线程的代码逻辑---开始");
num.addAndGet(10);
TimeUnit.SECONDS.sleep(5L);
System.out.println("子线程的代码逻辑---结束");
} catch (InterruptedException e) {
e.printStackTrace();
}
return num;
}).thenApply(integer->{
System.out.println("子线程thenApplay调用");
return num.get() * num.get();
});
Integer integer = future.get();
System.out.println("主线程的代码逻辑---结束,子线程返回值为" + integer);
}
}
复制代码
2. thenAccept/thenAcceptAsync(消费结果)
thenAccept 消费处理结果, 接收任务的处理结果,并消费处理,无返回结果。
thenAccept 同 thenApply 接收上一个任务的返回值作为参数,但是回调方法无返回值。
复制代码
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
System.out.println("主线程任务开始");
AtomicInteger num = new AtomicInteger(1);
CompletableFuture.supplyAsync(() -> {
try {
System.out.println("子线程任务执行");
num.addAndGet(10);
} catch (Exception e) {
e.printStackTrace();
}
return num;
}).thenApply(integer -> {
System.out.println("thenApply执行");
return num.get() * num.get();
}).thenAccept(new Consumer<Integer>() {
@Override
public void accept(Integer integer) {
System.out.println("子线程全部处理完成,最后调用了 accept,结果为:" + integer);
}
});
}
}
复制代码
3. thenRun/thenRunAsync(任务完成后触发的回调)
thenRun 是上一个任务完成后触发的回调,没有入参,也没有返回值。
复制代码
public static void main(String[] args) {
System.out.println("主线程任务开始");
AtomicInteger num = new AtomicInteger(1);
CompletableFuture.supplyAsync(() -> {
try {
System.out.println("子线程任务执行");
num.addAndGet(10);
} catch (Exception e) {
e.printStackTrace();
}
return num;
}).thenApply(integer -> {
System.out.println("thenApply执行");
return num.get() * num.get();
}).thenRun(() -> {
System.out.println("thenRun执行");
});
System.out.println("主线程任务结束");
}
复制代码
4. 异常处理
1. exceptionally(出现异常才会调用,能处理异常)
exceptionally 异常处理,出现异常时触发 作用相当于catch
exceptionally 中可指定默认返回结果,如果出现异常,则返回默认的返回结果
复制代码
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
System.out.println("主线程开始");
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
int num = 1;
int i= 1/0;
System.out.println("这行包括下面的代码都不会被执行");
num += 10;
return num;
}).exceptionally(ex -> {
System.out.println(ex.getMessage());
return -1;
});
System.out.println(future.get());
}
}
复制代码
2. whenComplete(是否出现异常都会调用,不能处理异常)
当CompletableFuture的任务不论是正常完成还是出现异常它都会调用whenComplete这回调函数
正常完成:whenComplete返回结果和上级任务一致,异常为null;
出现异常:whenComplete返回结果为null,异常为上级任务的异常;
即调用get()时正常完成时就获取到结果,出现异常时就会抛出异常,需要你处理该异常。
复制代码
public static void main(String[] args) {
System.out.println("主线程任务开始");
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程任务执行");
if(Math.random() < 0.5){
System.out.println("子线程任务异常");
throw new RuntimeException("");
}
System.out.println("子线程任务正常结束");
return 1;
}).whenComplete(new BiConsumer<Integer, Throwable>() {
@Override
public void accept(Integer integer, Throwable throwable) {
System.out.println(integer == null);
System.out.println(throwable == null);
}
});
System.out.println("最终的返回结果为" + future.get());
System.out.println("主线程任务结束");
}
复制代码
public static void main(String[] args) {
System.out.println("主线程任务开始");
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程任务执行");
if(Math.random() < 0.5){
System.out.println("子线程任务异常");
throw new RuntimeException("");
}
System.out.println("子线程任务正常结束");
return 1;
}).whenComplete(new BiConsumer<Integer, Throwable>() {
@Override
public void accept(Integer integer, Throwable throwable) {
System.out.println(integer == null);
System.out.println(throwable == null);
}
}).exceptionally(new Function<Throwable, Integer>() {
@Override
public Integer apply(Throwable throwable) {
System.out.println("进入异常处理");
return 2;
}
});
System.out.println("最终的返回结果为" + future.get());
System.out.println("主线程任务结束");
}
复制代码
3. handle(是否出现异常都会调用,能处理异常)
任务出现异常就不会进入thenApply,
任务出现异常也会进入handle,可对异常处理
handle对传入值进行转换,并产生自己的返回结果,T -> R
whenComplete的返回值和上级任务传入的结果一致,不能对其转换
复制代码
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
System.out.println("主线程开始");
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
int num = 1;
System.out.println("加 10 任务开始");
num += 10;
return num;
}).handle((i,ex) ->{
System.out.println("进入 handle 方法");
if(ex != null){
System.out.println("发生了异常,内容为:" + ex.getMessage());
return -1;
}else{
System.out.println("正常完成,内容为: " + i);
return i;
}
});
System.out.println(future.get());
}
}
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
System.out.println("主线程开始");
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
int num = 1;
System.out.println("加 10 任务开始");
num = 1/0;
return num;
}).handle((i,ex) ->{
System.out.println("进入 handle 方法");
if(ex != null){
System.out.println("发生了异常,内容为:" + ex.getMessage());
return -1;
}else{
System.out.println("正常完成,内容为: " + i);
return i;
}
});
System.out.println(future.get());
}
}
复制代码
5. 结果合并
1. thenCompose/thenComposeAsync(两个任务有依赖顺序)
thenCompose 可以用于组合多个CompletableFuture,将前一个任务的返回结果作为下一个任务的参数,它们之间存在着业务逻辑上的先后顺序。
thenCompose方法会在某个任务执行完成后,将该任务的执行结果作为方法入参然后执行指定的方法,该方法会返回一个新的CompletableFuture实例。
thenApply()转换的是泛型中的类型,相当于将CompletableFuture<T> 转换生成新的CompletableFuture<U>
thenCompose()用来连接两个CompletableFuture,是生成一个新的CompletableFuture。
复制代码
public static void main(String[] args) {
System.out.println("主线程任务开始");
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程任务执行");
return 1;
}).thenCompose(s->CompletableFuture.supplyAsync(()->{
return 2 + s;
}));
System.out.println("最终的返回结果为" + future.get());
System.out.println("主线程任务结束");
}
复制代码
2. thenCombine/thenCombineAsync(两个任务没有依赖顺序)
thenCombine会在两个任务都执行完成后,把两个任务的结果合并。
两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。
两个任务是并行执行的,它们之间并没有先后依赖顺序。
复制代码
public static void main(String[] args) {
System.out.println("主线程任务开始");
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程1任务执行");
return 1;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程2任务执行");
return 2;
});
CompletableFuture<Integer> future = future1.thenCombine(future2, new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer integer1, Integer integer2) {
return integer1 + integer2;
}
});
System.out.println("线程1最终的返回结果为" + future1.get());
System.out.println("线程2的返回结果为" + future2.get());
System.out.println("整体返回结果为" + future.get());
System.out.println("主线程任务结束");
}
public static void main(String[] args) {
System.out.println("主线程任务开始");
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程1任务执行");
int i = 1/0;
return 1;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程2任务执行");
return 2;
});
CompletableFuture<Integer> future = future1.thenCombine(future2, new BiFunction<Integer, Integer, Integer>() {
@Override
public Integer apply(Integer integer1, Integer integer2) {
return integer1 + integer2;
}
});
System.out.println("线程1最终的返回结果为" + future1.get());
System.out.println("线程2的返回结果为" + future2.get());
System.out.println("整体返回结果为" + future.get());
System.out.println("主线程任务结束");
}
复制代码
3. AllOf(所有任务都执行完才会结束)
allOf: 一系列独立的 future 任务,等其所有的任务执行完后做一些事情
如果任意一个任务异常,allOf的CompletableFuture,执行get方法,会抛出异常
复制代码
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
System.out.println("主线程开始");
List<CompletableFuture> list = new ArrayList<>();
CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
int num = 1;
System.out.println("加 10 任务开始");
num += 10;
return num;
});
list.add(job1);
CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
int num = 1;
System.out.println("乘以 10 任务开始");
num = num * 10;
return num;
});
list.add(job2);
CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
int num = 1;
System.out.println("减以 10 任务开始");
num = num - 10;
return num;
});
list.add(job3);
CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() -> {
int num = 1;
System.out.println("除以 10 任务开始");
num = num / 10;
return num;
});
list.add(job4);
List<Integer> collect = list.stream().map(CompletableFuture<Integer>::join).collect(Collectors.toList());
System.out.println(collect);
}
}
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
System.out.println("主线程开始");
List<CompletableFuture> list = new ArrayList<>();
CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
int num = 1;
System.out.println("加 10 任务开始");
try {
TimeUnit.SECONDS.sleep(3L);
} catch (InterruptedException e) {
e.printStackTrace();
}
num += 10;
System.out.println("加 10 任务结束");
return num;
});
list.add(job1);
CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
int num = 1;
System.out.println("乘以 10 任务开始");
try {
TimeUnit.SECONDS.sleep(3L);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = num * 10;
System.out.println("乘以 10 任务结束");
return num;
});
list.add(job2);
CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
int num = 1;
System.out.println("减以 10 任务开始");
try {
TimeUnit.SECONDS.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = num - 10;
System.out.println("减以 10 任务结束");
return num;
});
list.add(job3);
CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() -> {
int num = 1;
System.out.println("除以 10 任务开始");
try {
TimeUnit.SECONDS.sleep(2L);
} catch (InterruptedException e) {
e.printStackTrace();
}
num = num / 10;
System.out.println("除以 10 任务结束");
return num;
});
list.add(job4);
CompletableFuture.allOf(job1, job2, job3, job4).get();
System.out.println("主线程结束");
}
}
复制代码
4. AnyOf(任意任务执行完就会结束)
anyOf: 只要在多个 future 里面有一个返回,整个任务就可以结束,而不需要等到每一个future 结束
如果执行的任务异常,anyOf的CompletableFuture,执行get方法,会抛出异常
复制代码
public class CompletableFutureDemo {
public static void main(String[] args) throws Exception {
System.out.println("主线程开始");
CompletableFuture<Integer>[] futures = new CompletableFuture[4];
CompletableFuture<Integer> job1 = CompletableFuture.supplyAsync(() -> {
try{
Thread.sleep(5000);
int num = 1;
System.out.println("加 10 任务开始");
num += 10;
return num;
}catch (Exception e){
return 0;
}
});
futures[0] = job1;
CompletableFuture<Integer> job2 = CompletableFuture.supplyAsync(() -> {
try{
Thread.sleep(2000);
int num = 1;
System.out.println("乘以 10 任务开始");
num = num * 10;
return num;
}catch (Exception e){
return 1;
}
});
futures[1] = job2;
CompletableFuture<Integer> job3 = CompletableFuture.supplyAsync(() -> {
try{
Thread.sleep(3000);
int num = 1;
System.out.println("减以 10 任务开始");
num = num - 10;
return num;
}catch (Exception e){
return 2;
}
});
futures[2] = job3;
CompletableFuture<Integer> job4 = CompletableFuture.supplyAsync(() -> {
try{
Thread.sleep(4000);
int num = 1;
System.out.println("除以 10 任务开始");
num = num / 10;
return num;
}catch (Exception e){
return 3;
}
});
futures[3] = job4;
CompletableFuture<Object> future = CompletableFuture.anyOf(futures);
System.out.println(future.get());
}
}
复制代码
6. 多任务组合处理
1. AND关系
thenCombine / thenAcceptBoth / runAfterBoth都表示:
将两个CompletableFuture组合起来,只有这两个都正常执行完了,才会执行某个任务 。
复制代码
方法 |
执行结果入参 |
有无返回值 |
thenCombine |
会将两个任务的执行结果作为方法入参,传递到指定方法中 |
有返回值 |
thenAcceptBoth |
会将两个任务的执行结果作为方法入参,传递到指定方法中 |
无返回值 |
runAfterBoth |
不会把执行结果当做方法入参 |
无返回值 |
1. thenCombine/thenCombineAsync
上面已经介绍过具体的使用了,这里不过多介绍。
复制代码
2. thenAcceptBoth/thenAcceptBothAsync
public static void main(String[] args) {
System.out.println("主线程任务开始");
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程1任务执行");
return 1;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程2任务执行");
return 2;
});
future1.thenAcceptBoth(future2, new BiConsumer<Integer, Integer>() {
@Override
public void accept(Integer integer1, Integer integer2) {
System.out.println(integer1 + integer2);
}
});
System.out.println("线程1最终的返回结果为" + future1.get());
System.out.println("线程2的返回结果为" + future2.get());
System.out.println("主线程任务结束");
}
复制代码
3. runAfterBoth/runAfterBothAsync
public static void main(String[] args) {
System.out.println("主线程任务开始");
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程1任务执行");
return 1;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程2任务执行");
return 2;
});
future1.runAfterBoth(future2, new Runnable() {
@Override
public void run() {
System.out.println("runAfterBoth执行");
}
});
System.out.println("线程1最终的返回结果为" + future1.get());
System.out.println("线程2的返回结果为" + future2.get());
System.out.println("主线程任务结束");
}
复制代码
2. OR关系
applyToEither / acceptEither / runAfterEither 都表示:
将两个CompletableFuture组合起来,只要其中一个执行完了,就会执行某个任务。
复制代码
方法 |
执行结果入参 |
有无返回值 |
applyToEither |
会将已经执行完成的任务,作为方法入参,传递到指定方法中 |
有返回值 |
acceptEither |
会将已经执行完成的任务,作为方法入参,传递到指定方法中 |
无返回值 |
runAfterEither |
不会把执行结果当做方法入参 |
无返回值 |
1. applyToEither/applyToEitherAsync
public static void main(String[] args) {
System.out.println("主线程任务开始");
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程1任务执行");
return 1;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程2任务执行");
return 2;
});
CompletableFuture<Integer> future = future1.applyToEither(future2,(res) ->{
System.out.println("res = " + res);
return res;
});
System.out.println("线程1最终的返回结果为" + future1.get());
System.out.println("线程2的返回结果为" + future2.get());
System.out.println("线程3的返回结果(获取最先执行完的线程的结果)" + future.get());
System.out.println("主线程任务结束");
}
复制代码
2. acceptEither/acceptEitherAsync
public static void main(String[] args) {
System.out.println("主线程任务开始");
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程1任务执行");
return 1;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程2任务执行");
return 2;
});
future1.acceptEither(future2,(res) ->{
System.out.println("res = " + res);
});
System.out.println("线程1最终的返回结果为" + future1.get());
System.out.println("线程2的返回结果为" + future2.get());
System.out.println("主线程任务结束");
}
复制代码
3. runAfterEither/runAfterEitherAsync
public static void main(String[] args) {
System.out.println("主线程任务开始");
CompletableFuture<Integer> future1 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程1任务执行");
return 1;
});
CompletableFuture<Integer> future2 = CompletableFuture.supplyAsync(() -> {
System.out.println("子线程2任务执行");
return 2;
});
future1.runAfterEither(future2,() ->{
System.out.println("线程1和2任意一个执行完就会执行我 无返回值 无参数");
});
System.out.println("线程1最终的返回结果为" + future1.get());
System.out.println("线程2的返回结果为" + future2.get());
System.out.println("主线程任务结束");
}
复制代码
7. 一些其他内容补充
1. get、getNow、join方法
CompletableFuture的get()方法是阻塞的,如果使用它来获取异步调用的返回值,建议添加超时时间.
如果在指定时间内未获取结果将抛出超时异常
原写法: future.get(); 现写法: future.get(5, TimeUnit.SECONDS);
getNow => 立即获取结果不阻塞,结果计算已完成将返回结果或计算过程中的异常,如果未计算完成将返回设定的valueIfAbsent值
join => 方法里不会抛出异常
复制代码
2. 带Async与不带的区别
不带async的方法执行第二个任务时,则第二个任务和第一个任务是共用同一个线程池 。
带async的方法执行第二个任务时,则第一个任务使用的是你自己传入的线程池,第二个任务使用的是ForkJoin线程池
以async结尾的方法将会在一个新的线程中执行组合操作
复制代码
3. CompletableFuture指定线程池
一般我们建议CompletableFuture使用自定义线程池
但是如果线程池拒绝策略是DiscardPolicy或者DiscardOldestPolicy,当线程池饱和时,会直接丢弃任务,不会抛弃异常。
因此建议,CompletableFuture线程池策略最好使用AbortPolicy,然后耗时的异步线程,做好线程池隔离哈。
写法类似
public static void main(String[] args) {
Executor executor = Executors.newFixedThreadPool(10);
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(()->{
return 1;
},executor);
}
复制代码
4. 什么时候用CompletableFuture
存在IO密集型的任务可以选择CompletableFuture,IO部分交由另外一个线程去执行。
Logback、Log4j2异步日志记录的实现原理就是新起了一个线程去执行IO操作,
这部分可以以CompletableFuture.runAsync(()->{ioOperation();})的方式去调用。
如果是CPU密集型就不推荐使用了推荐使用并行流
复制代码
5. CompletableFuture的一些特点
1.以Async结尾的方法都是异步执行的
2.以run开头的方法一般无返回值,而已supply开头的方法是有返回值的,如 runAsync 和supplyAsync
3.以 then 开头的方法都会在上一个任务执行结束之后执行下一个任务。如 thenApply 和 thenAccept
4.以Accept结尾的方法均为消耗上个任务执行的结果,无返回值。
5.以run开头的方法忽略上个任务的执行结果,在上个任务执行结束后执行下个方法。
6.以Executor 结尾的方法可以自定义线程池,如果没有指定线程池,则会默认在ForkJoinPool.commonPool() 线程池中执行。
复制代码