十堰网站建设专家,安徽省建设厅八大员报名网站,wordpress编辑管理员,你会怎么做外国的网站吗JUC 高并发编程基础篇
• 1、什么是 JUC • 2、Lock 接口 • 3、线程间通信 • 4、集合的线程安全 • 5、多线程锁 • 6、Callable 接口 • 7、JUC 三大辅助类: CountDownLatch CyclicBarrier Semaphore • 8、读写锁: ReentrantReadWriteLock • 9、阻塞队列 • 10、ThreadPo…JUC 高并发编程基础篇
• 1、什么是 JUC • 2、Lock 接口 • 3、线程间通信 • 4、集合的线程安全 • 5、多线程锁 • 6、Callable 接口 • 7、JUC 三大辅助类: CountDownLatch CyclicBarrier Semaphore • 8、读写锁: ReentrantReadWriteLock • 9、阻塞队列 • 10、ThreadPool 线程池 • 11、Fork/Join 框架 • 12、CompletableFuture
1 什么是 JUC 1.1 JUC 简介 在 Java 中线程部分是一个重点 JUC 也是关于线程的。JUC 就是 java.util .concurrent 工具包的简称。这是一个处理线程的工具包JDK 1.5 开始出现的。 1.2 进程与线程
进程Process 是计算机中的程序关于某数据集合上的一次运行活动是系统进行资源分配和调度的基本单位是操作系统结构的基础。 在当代面向线程设计的计算机结构中进程是线程的容器。程序是指令、数据及其组织形式的描述进程是程序的实体。是计算机中的程序关于某数据集合上的一次运行活动是系统进行资源分配和调度的基本单位是操作系统结构的基础。程序是指令、数据及其组织形式的描述进程是程序的实体。
线程thread 是操作系统能够进行运算调度的最小单位。它被包含在进程之中是进程中的实际运作单位。一条线程指的是进程中一个单一顺序的控制流一个进程中可以并发多个线程每条线程并行执行不同的任务。
总结来说: 进程指在系统中正在运行的一个应用程序程序一旦运行就是进程进程——资源分配的最小单位。 线程系统分配处理器时间资源的基本单元或者说进程之内独立执行的一个单元执行流。线程——程序执行的最小单位。 1.3 线程的状态
1.3.1 线程状态枚举类
Thread.Statepublic enum State {
/**
* Thread state for a thread which has not yet started.
*/
NEW,(新建)
/**
* Thread state for a runnable thread. A thread in the runnable
* state is executing in the Java virtual machine but it may
* be waiting for other resources from the operating system
* such as processor.
*/
RUNNABLE,准备就绪
/**
* Thread state for a thread blocked waiting for a monitor lock.
* A thread in the blocked state is waiting for a monitor lock
* to enter a synchronized block/method or
* reenter a synchronized block/method after calling
* {link Object#wait() Object.wait}.
*/
BLOCKED,阻塞
/**
* Thread state for a waiting thread.
* A thread is in the waiting state due to calling one of the
* following methods:
* ul
* li{link Object#wait() Object.wait} with no timeout/li
* li{link #join() Thread.join} with no timeout/li
* li{link LockSupport#park() LockSupport.park}/li
* /ul
*
* pA thread in the waiting state is waiting for another thread to
* perform a particular action.
*
* For example, a thread that has called ttObject.wait()/tt
* on an object is waiting for another thread to call
* ttObject.notify()/tt or ttObject.notifyAll()/tt on
* that object. A thread that has called ttThread.join()/tt
* is waiting for a specified thread to terminate.
*/
WAITING,不见不散
/**
* Thread state for a waiting thread with a specified waiting time.
* A thread is in the timed waiting state due to calling one of
* the following methods with a specified positive waiting time:
* ul
* li{link #sleep Thread.sleep}/li
* li{link Object#wait(long) Object.wait} with timeout/li
* li{link #join(long) Thread.join} with timeout/li
* li{link LockSupport#parkNanos LockSupport.parkNanos}/li
* li{link LockSupport#parkUntil LockSupport.parkUntil}/li
* /ul
*/
TIMED_WAITING,过时不候
/**
* Thread state for a terminated thread.
* The thread has completed execution.
*/
TERMINATED;(终结)
}1.3.2 wait/sleep 的区别 1sleep 是 Thread 的静态方法wait 是 Object 的方法任何对象实例都能调用。 2sleep 不会释放锁它也不需要占用锁。wait 会释放锁但调用它的前提是当前线程占有锁(即代码要在 synchronized 中)。 3它们都可以被 interrupted 方法中断。
1.4 并发与并行 1.4.1 串行模式 串行表示所有任务都一一按先后顺序进行。串行意味着必须先装完一车柴才能运送这车柴只有运送到了才能卸下这车柴并且只有完成了这整个三个步骤才能进行下一个步骤。 串行是一次只能取得一个任务并执行这个任务。
1.4.2 并行模式 并行意味着可以同时取得多个任务并同时去执行所取得的这些任务。并行模式相当于将长长的一条队列划分成了多条短队列所以并行缩短了任务队列的长度。并行的效率从代码层次上强依赖于多进程/多线程代码从硬件角度上则依赖于多核 CPU。
1.4.3 并发 并发(concurrent)指的是多个程序可以同时运行的现象更细化的是多进程可以同时运行或者多指令可以同时运行。但这不是重点在描述并发的时候也不会去扣这种字眼是否精确并发的重点在于它是一种现象, 并发描述的是多进程同时运行的现象。但实际上对于单核心 CPU 来说同一时刻只能运行一个线程。所以这里的同时运行表示的不是真的同一时刻有多个线程运行的现象这是并行的概念而是提供一种功能让用户看来多个程序同时运行起来了但实际上这些程序中的进程不是一直霸占 CPU 的而是执行一会停一会。 要解决大并发问题通常是将大任务分解成多个小任务, 由于操作系统对进程的调度是随机的所以切分成多个小任务后可能会从任一小任务处执行。这可能会出现一些现象
• 可能出现一个小任务执行了多次还没开始下个任务的情况。这时一般会采用队列或类似的数据结构来存放各个小任务的成果 • 可能出现还没准备好第一步就执行第二步的可能。这时一般采用多路复用或异步的方式比如只有准备好产生了事件通知才执行某个任务。 • 可以多进程/多线程的方式并行执行这些小任务。也可以单进程/单线程执行这些小任务这时很可能要配合多路复用才能达到较高的效率
1.4.4 小结(重点) 并发同一时刻多个线程在访问同一个资源多个线程对一个点 例子春运抢票 电商秒杀… 并行多项工作一起执行之后再汇总 例子泡方便面电水壶烧水一边撕调料倒入桶中
1.5 管程 管程(monitor)是保证了同一时刻只有一个进程在管程内活动,即管程内定义的操作在同一时刻只被一个进程调用(由编译器实现).但是这样并不能保证进程以设计的顺序执行JVM 中同步是基于进入和退出管程(monitor)对象实现的每个对象都会有一个管程(monitor)对象管程(monitor)会随着 java 对象一同创建和销毁执行线程首先要持有管程对象然后才能执行方法当方法完成之后会释放管程方法在执行时候会持有管程其他线程无法再获取同一个管程
1.6 用户线程和守护线程 用户线程:平时用到的普通线程,自定义线程 守护线程:运行在后台,是一种特殊的线程,比如垃圾回收 当主线程结束后,用户线程还在运行,JVM 存活 如果没有用户线程,都是守护线程,JVM 结束
2 Lock 接口 2.1 Synchronized 2.1.1 Synchronized 关键字回顾 synchronized 是 Java 中的关键字是一种同步锁。它修饰的对象有以下几种
修饰一个代码块被修饰的代码块称为同步语句块其作用的范围是大括号{}括起来的代码作用的对象是调用这个代码块的对象修饰一个方法被修饰的方法称为同步方法其作用的范围是整个方法作用的对象是调用这个方法的对象 o 虽然可以使用 synchronized 来定义方法但 synchronized 并不属于方法定义的一部分因此synchronized 关键字不能被继承。如果在父类中的某个方法使用了 synchronized 关键字而在子类中覆盖了这个方法在子类中的这个方法默认情况下并不是同步的而必须显式地在子类的这个方法中加上synchronized 关键字才可以。当然还可以在子类方法中调用父类中相应的方法这样虽然子类中的方法不是同步的但子类调用了父类的同步方法因此子类的方法也就相当于同步了。修改一个静态的方法其作用的范围是整个静态方法作用的对象是这个类的所有对象修改一个类其作用的范围是 synchronized 后面括号括起来的部分作用主的对象是这个类的所有对象。 2.1.2 售票案例
class Ticket {//票数private int number 30;//操作方法卖票public synchronized void sale() {//判断是否有票if(number 0) {System.out.println(Thread.currentThread().getName() :
(number--) number);}}
}如果一个代码块被 synchronized 修饰了当一个线程获取了对应的锁并执行该代码块时其他线程便只能一直等待等待获取锁的线程释放锁而这里获取锁的线程释放锁只会有两种情况 1获取锁的线程执行完了该代码块然后线程释放对锁的占有 2线程执行发生异常此时 JVM 会让线程自动释放锁。 那么如果这个获取锁的线程由于要等待 IO 或者其他原因比如调用 sleep方法被阻塞了但是又没有释放锁其他线程便只能干巴巴地等待试想一下这多么影响程序执行效率。 因此就需要有一种机制可以不让等待的线程一直无期限地等待下去比如只等待一定的时间或者能够响应中断通过 Lock 就可以办到。 2.2 什么是 Lock Lock 锁实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构可能具有非常不同的属性并且可能支持多个关联的条件对象。Lock 提供了比 synchronized 更多的功能。
Lock 与的 Synchronized 区别 • Lock 不是 Java 语言内置的synchronized 是 Java 语言的关键字因此是内置特性。Lock 是一个类通过这个类可以实现同步访问 • Lock 和 synchronized 有一点非常大的不同采用 synchronized 不需要用户去手动释放锁当 synchronized 方法或者 synchronized 代码块执行完之后系统会自动让线程释放对锁的占用而 Lock 则必须要用户去手动释放锁如果没有主动释放锁就有可能导致出现死锁现象。 2.2.1 Lock 接口
public interface Lock {
void lock();
void lockInterruptibly() throws InterruptedException;
boolean tryLock();
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
void unlock();
Condition newCondition();
}下面来逐个讲述 Lock 接口中每个方法的使用 2.2.2 lock lock()方法是平常使用得最多的一个方法就是用来获取锁。如果锁已被其他线程获取则进行等待。 采用 Lock必须主动去释放锁并且在发生异常时不会自动释放锁。因此一般来说使用 Lock 必须在 try{}catch{}块中进行并且将释放锁的操作放在finally 块中进行以保证锁一定被被释放防止死锁的发生。通常使用 Lock来进行同步的话是以下面这种形式去使用的
Lock lock ...;
lock.lock();
try{
//处理任务
}catch(Exception ex){
}finally{
lock.unlock(); //释放锁
}2.2.3 newCondition 关键字 synchronized 与 wait()/notify()这两个方法一起使用可以实现等待/通知模式 Lock 锁的 newContition()方法返回 Condition 对象Condition 类也可以实现等待/通知模式。 用 notify()通知时JVM 会随机唤醒某个等待的线程 使用 Condition 类可以进行选择性通知 Condition 比较常用的两个方法 • await()会使当前线程等待,同时会释放锁,当其他线程调用 signal()时,线程会重新获得锁并继续执行。 • signal()用于唤醒一个等待的线程。 注意在调用 Condition 的 await()/signal()方法前也需要线程持有相关 的 Lock 锁调用 await()后线程会释放这个锁在 singal()调用后会从当前 Condition 对象的等待队列中唤醒 一个线程唤醒的线程尝试获得锁 一旦 获得锁成功就继续执行。 2.3 ReentrantLock ReentrantLock意思是“可重入锁”关于可重入锁的概念将在后面讲述。 ReentrantLock 是唯一实现了 Lock 接口的类并且 ReentrantLock 提供了更 多的方法。下面通过一些实例看具体看一下如何使用。
public class Test {
private ArrayListInteger arrayList new ArrayListInteger();
public static void main(String[] args) {
final Test test new Test();
new Thread(){
public void run() {
test.insert(Thread.currentThread());
};
}.start();
new Thread(){
public void run() {
test.insert(Thread.currentThread());
};
}.start();
}
public void insert(Thread thread) {
Lock lock new ReentrantLock(); //注意这个地方
lock.lock();
try {
System.out.println(thread.getName()得到了锁);
for(int i0;i5;i) {
arrayList.add(i);
}
} catch (Exception e) {
// TODO: handle exception
}finally {
System.out.println(thread.getName()释放了锁);
lock.unlock();
}
}
}
2.4 ReadWriteLock
ReadWriteLock 也是一个接口在它里面只定义了两个方法
public interface ReadWriteLock {/*** Returns the lock used for reading.** return the lock used for reading.*/Lock readLock();/*** Returns the lock used for writing.** return the lock used for writing.*/Lock writeLock();
}一个用来获取读锁一个用来获取写锁。也就是说将文件的读写操作分开分 成 2 个锁来分配给线程从而使得多个线程可以同时进行读操作。下面的 ReentrantReadWriteLock 实现了 ReadWriteLock 接口。 ReentrantReadWriteLock 里面提供了很多丰富的方法不过最主要的有两个 方法readLock()和 writeLock()用来获取读锁和写锁。 下面通过几个例子来看一下 ReentrantReadWriteLock 具体用法。 假如有多个线程要同时进行读操作的话先看一下 synchronized 达到的效果
public class Test {private ReentrantReadWriteLock rwl new
ReentrantReadWriteLock();public static void main(String[] args) {final Test test new Test();new Thread(){public void run() {test.get(Thread.currentThread());};}.start();new Thread(){public void run() {test.get(Thread.currentThread());};}.start();} public synchronized void get(Thread thread) {long start System.currentTimeMillis();while(System.currentTimeMillis() - start 1) {System.out.println(thread.getName()正在进行读操作);}System.out.println(thread.getName()读操作完毕);}
}
而改成用读写锁的话
public class Test {private ReentrantReadWriteLock rwl new
ReentrantReadWriteLock();public static void main(String[] args) {final Test test new Test();new Thread(){public void run() {test.get(Thread.currentThread());};}.start();new Thread(){public void run() {test.get(Thread.currentThread());};}.start();} public void get(Thread thread) {rwl.readLock().lock();try {long start System.currentTimeMillis();while(System.currentTimeMillis() - start 1) {System.out.println(thread.getName()正在进行读操作);}System.out.println(thread.getName()读操作完毕);} finally {rwl.readLock().unlock();}}
}说明 thread1 和 thread2 在同时进行读操作。这样就大大提升了读操作的效率。 注意: • 如果有一个线程已经占用了读锁则此时其他线程如果要申请写锁则申请写 锁的线程会一直等待释放读锁。 • 如果有一个线程已经占用了写锁则此时其他线程如果申请写锁或者读锁则 申请的线程会一直等待释放写锁。
2.5 小结(重点) Lock 和 synchronized 有以下几点不同
Lock 是一个接口而 synchronized 是 Java 中的关键字synchronized 是内置的语言实现synchronized 在发生异常时会自动释放线程占有的锁因此不会导致死锁现象发生而 Lock 在发生异常时如果没有主动通过 unLock()去释放锁则很可能造成死锁现象因此使用 Lock 时需要在 finally 块中释放锁Lock 可以让等待锁的线程响应中断而 synchronized 却不行使用 synchronized 时等待的线程会一直等待下去不能够响应中断通过 Lock 可以知道有没有成功获取锁而 synchronized 却无法办到。Lock 可以提高多个线程进行读操作的效率。 在性能上来说如果竞争资源不激烈两者的性能是差不多的而当竞争资源非常激烈时即有大量线程同时竞争此时 Lock 的性能要远远优于synchronized。
3 线程间通信 线程间通信的模型有两种共享内存和消息传递以下方式都是基本这两种模型来实现的。我们来基本一道面试常见的题目来分析 场景—两个线程一个线程对当前数值加 1另一个线程对当前数值减 1,要求用线程间通信 3.1 synchronized 方案
package com.atguigu.test;
/**
* volatile 关键字实现线程交替加减
*/
public class TestVolatile {/*** 交替加减* param args*/public static void main(String[] args){DemoClass demoClass new DemoClass();new Thread(() -{for (int i 0; i 5; i) {demoClass.increment();}}, 线程 A).start();new Thread(() -{for (int i 0; i 5; i) {demoClass.decrement();}}, 线程 B).start();}
}
package com.atguigu.test;
class DemoClass{//加减对象private int number 0;/*** 加 1*/public synchronized void increment() {try {while (number ! 0){this.wait();}number;System.out.println(-------- Thread.currentThread().getName() 加一成
功----------,值为: number);notifyAll();}catch (Exception e){e.printStackTrace();}}/*** 减一*/public synchronized void decrement(){try {while (number 0){this.wait();}number--;System.out.println(-------- Thread.currentThread().getName() 减一成
功----------,值为: number);notifyAll();}catch (Exception e){e.printStackTrace();}}
}3.2 Lock 方案
package com.atguigu.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class DemoClass{//加减对象private int number 0;//声明锁private Lock lock new ReentrantLock();//声明钥匙private Condition condition lock.newCondition();/*** 加 1*/public void increment() {try {lock.lock();while (number ! 0){condition.await();}number;System.out.println(-------- Thread.currentThread().getName() 加一成
功----------,值为: number);condition.signalAll();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}/*** 减一*/public void decrement(){try {lock.lock();while (number 0){condition.await();}number--;System.out.println(-------- Thread.currentThread().getName() 减一成
功----------,值为: number);condition.signalAll();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}
}3.4 线程间定制化通信 4.4.1 案例介绍 问题: A 线程打印 5 次 AB 线程打印 10 次 BC 线程打印 15 次 C,按照 此顺序循环 10 轮 4.4.2 实现流程
package com.atguigu.test;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class DemoClass{//通信对象:0--打印 A 1---打印 B 2----打印 Cprivate int number 0;//声明锁private Lock lock new ReentrantLock();//声明钥匙 Aprivate Condition conditionA lock.newCondition();//声明钥匙 Bprivate Condition conditionB lock.newCondition();//声明钥匙 Cprivate Condition conditionC lock.newCondition();/*** A 打印 5 次*/public void printA(int j){try {lock.lock();while (number ! 0){conditionA.await();}System.out.println(Thread.currentThread().getName() 输出 A,第 j
轮开始);//输出 5 次 Afor (int i 0; i 5; i) {System.out.println(A);}//开始打印 Bnumber 1;//唤醒 BconditionB.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}/*** B 打印 10 次*/public void printB(int j){try {lock.lock();while (number ! 1){conditionB.await();}System.out.println(Thread.currentThread().getName() 输出 B,第 j
轮开始);//输出 10 次 Bfor (int i 0; i 10; i) {System.out.println(B);}//开始打印 Cnumber 2;//唤醒 CconditionC.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}/*** C 打印 15 次*/public void printC(int j){try {lock.lock();while (number ! 2){conditionC.await();}System.out.println(Thread.currentThread().getName() 输出 C,第 j
轮开始);//输出 15 次 Cfor (int i 0; i 15; i) {System.out.println(C);}System.out.println(-----------------------------------------);//开始打印 Anumber 0;//唤醒 AconditionA.signal();}catch (Exception e){e.printStackTrace();}finally {lock.unlock();}}
}
测试类
package com.atguigu.test;
/**
* volatile 关键字实现线程交替加减
*/
public class TestVolatile {/*** 交替加减* param args*/public static void main(String[] args){DemoClass demoClass new DemoClass();new Thread(() -{for (int i 1; i 10; i) {demoClass.printA(i);}}, A 线程).start();new Thread(() -{for (int i 1; i 10; i) {demoClass.printB(i);}}, B 线程).start();new Thread(() -{for (int i 1; i 10; i) {demoClass.printC(i);}}, C 线程).start();}
}5 多线程锁 5.1 锁的八个问题演示
class Phone {public static synchronized void sendSMS() throws Exception {//停留 4 秒TimeUnit.SECONDS.sleep(4);System.out.println(------sendSMS);}public synchronized void sendEmail() throws Exception {System.out.println(------sendEmail);}public void getHello() {System.out.println(------getHello);}
}
/**
* Description: 8 锁
*
1 标准访问先打印短信还是邮件
------sendSMS
------sendEmail
2 停 4 秒在短信方法内先打印短信还是邮件
------sendSMS
------sendEmail
3 新增普通的 hello 方法是先打短信还是 hello
------getHello
------sendSMS
4 现在有两部手机先打印短信还是邮件
------sendEmail
------sendSMS
5 两个静态同步方法1 部手机先打印短信还是邮件
------sendSMS
------sendEmail
6 两个静态同步方法2 部手机先打印短信还是邮件
------sendSMS
------sendEmail
7 1 个静态同步方法,1 个普通同步方法1 部手机先打印短信还是邮件
------sendEmail
------sendSMS
8 1 个静态同步方法,1 个普通同步方法2 部手机先打印短信还是邮件
------sendEmail
------sendSMS
结论:
一个对象里面如果有多个 synchronized 方法某一个时刻内只要一个线程去调用其中的
一个 synchronized 方法了
其它的线程都只能等待换句话说某一个时刻内只能有唯一一个线程去访问这些
synchronized 方法
锁的是当前对象 this被锁定后其它的线程都不能进入到当前对象的其它的
synchronized 方法
加个普通方法后发现和同步锁无关
换成两个对象后不是同一把锁了情况立刻变化。
synchronized 实现同步的基础Java 中的每一个对象都可以作为锁。
具体表现为以下 3 种形式。
对于普通同步方法锁是当前实例对象。
对于静态同步方法锁是当前类的 Class 对象。
对于同步方法块锁是 Synchonized 括号里配置的对象
当一个线程试图访问同步代码块时它首先必须得到锁退出或抛出异常时必须释放锁。
也就是说如果一个实例对象的非静态同步方法获取锁后该实例对象的其他非静态同步方
法必须等待获取锁的方法释放锁后才能获取锁
可是别的实例对象的非静态同步方法因为跟该实例对象的非静态同步方法用的是不同的锁
所以毋须等待该实例对象已获取锁的非静态同步方法释放锁就可以获取他们自己的锁。
所有的静态同步方法用的也是同一把锁——类对象本身这两把锁是两个不同的对象所
以静态同步方法与非静态同步方法之间是不会有竞态条件的。
但是一旦一个静态同步方法获取锁后其他的静态同步方法都必须等待该方法释放锁后才
能获取锁而不管是同一个实例对象的静态同步方法之间还是不同的实例对象的静态同
步方法之间只要它们同一个类的实例对象6 CallableFuture 接口 6.1 Callable 接口 目前我们学习了有两种创建线程的方法-一种是通过创建 Thread 类另一种是 通过使用 Runnable 创建线程。但是Runnable 缺少的一项功能是当线程 终止时即 run完成时我们无法使线程返回结果。为了支持此功能 Java 中提供了 Callable 接口。 现在我们学习的是创建线程的第三种方案—Callable 接口 Callable 接口的特点如下(重点) • 为了实现 Runnable需要实现不返回任何内容的 run方法而对于 Callable需要实现在完成时返回结果的 call方法。 • call方法可以引发异常而 run则不能。 • 为实现 Callable 而必须重写 call 方法 • 不能直接替换 runnable,因为 Thread 类的构造方法根本没有 Callable
创建新类 MyThread 实现 runnable 接口
class MyThread implements Runnable{
Override
public void run() {
}
}新类 MyThread2 实现 callable 接口
class MyThread2 implements CallableInteger{
Override
public Integer call() throws Exception {
return 200;
}
}6.2 Future 接口 当 call方法完成时结果必须存储在主线程已知的对象中以便主线程可 以知道该线程返回的结果。为此可以使用 Future 对象。 将 Future 视为保存结果的对象–它可能暂时不保存结果但将来会保存一旦 Callable 返回。Future 基本上是主线程可以跟踪进度以及其他线程的结果的 一种方式。要实现此接口必须重写 5 种方法这里列出了重要的方法,如下: • public boolean cancelboolean mayInterrupt用于停止任务。 如果尚未启动它将停止任务。如果已启动则仅在 mayInterrupt 为 true 时才会中断任务。 • public Object get抛出 InterruptedExceptionExecutionException 用于获取任务的结果。 如果任务完成它将立即返回结果否则将等待任务完成然后返回结果。
• public boolean isDone如果任务完成则返回 true否则返回 false 可以看到 Callable 和 Future 做两件事-Callable 与 Runnable 类似因为它封 装了要在另一个线程上运行的任务而 Future 用于存储从另一个线程获得的结 果。实际上future 也可以与 Runnable 一起使用。 要创建线程需要 Runnable。为了获得结果需要 future。 6.3 FutureTask Java 库具有具体的 FutureTask 类型该类型实现 Runnable 和 Future并方 便地将两种功能组合在一起。 可以通过为其构造函数提供 Callable 来创建 FutureTask。然后将 FutureTask 对象提供给 Thread 的构造函数以创建 Thread 对象。因此间接地使用 Callable 创建线程。 核心原理:(重点) 在主线程中需要执行比较耗时的操作时但又不想阻塞主线程时可以把这些 作业交给 Future 对象在后台完成 • 当主线程将来需要时就可以通过 Future 对象获得后台作业的计算结果或者执 行状态 • 一般 FutureTask 多用于耗时的计算主线程可以在完成自己的任务后再去 获取结果。 • 仅在计算完成时才能检索结果如果计算尚未完成则阻塞 get 方法 • 一旦计算完成就不能再重新开始或取消计算 • get 方法而获取结果只有在计算完成时获取否则会一直阻塞直到任务转入完 成状态然后会返回结果或者抛出异常 • get 只计算一次,因此 get 方法放到最后 demo 案例 6.4 使用 Callable 和 Future CallableDemo 案例
/**
* CallableDemo 案列
*/
public class CallableDemo {/*** 实现 runnable 接口*/static class MyThread1 implements Runnable{/*** run 方法*/Overridepublic 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*/Overridepublic 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{//声明 runableRunnable runable new MyThread1();//声明 callableCallable callable new MyThread2();//future-callableFutureTaskLong 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: 循环栅栏 • Semaphore: 信号灯 下面我们分别进行详细的介绍和学习 7.1 减少计数 CountDownLatch CountDownLatch 类可以设置一个计数器然后通过 countDown 方法来进行 减 1 的操作使用 await 方法等待计数器不大于 0然后继续执行 await 方法 之后的语句。 • CountDownLatch 主要有两个方法当一个或多个线程调用 await 方法时这 些线程会阻塞 • 其它线程调用 countDown 方法会将计数器减 1(调用 countDown 方法的线程 不会阻塞) • 当计数器的值变为 0 时因 await 方法阻塞的线程会被唤醒继续执行 场景: 6 个同学陆续离开教室后值班同学才可以关门。 CountDownLatchDemo
package com.atguigu.test;
import java.util.concurrent.CountDownLatch;
/**
* CountDownLatchDemo
*/
public class CountDownLatchDemo {/*** 6 个同学陆续离开教室后值班同学才可以关门* param args*/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()之后 的语句。可以将 CyclicBarrier 理解为加 1 操作 场景: 集齐 7 颗龙珠就可以召唤神龙 CyclicBarrierDemo
package com.atguigu.test;
import java.util.concurrent.CyclicBarrier;
/**
* CyclicBarrierDemo 案列
*/
public class CyclicBarrierDemo {//定义神龙召唤需要的龙珠总数private final static int NUMBER 7;/*** 集齐 7 颗龙珠就可以召唤神龙* param args*/public static void main(String[] args) {//定义循环栅栏CyclicBarrier cyclicBarrier new CyclicBarrier(NUMBER, () -{System.out.println(集齐 NUMBER 颗龙珠,现在召唤神龙!!!!!!!!!);});//定义 7 个线程分别去收集龙珠for (int i 1; i 7; i) {new Thread(()-{try {if(Thread.currentThread().getName().equals(龙珠 3 号)){System.out.println(龙珠 3 号抢夺战开始,孙悟空开启超级赛亚人模式!);Thread.sleep(5000);System.out.println(龙珠 3 号抢夺战结束,孙悟空打赢了,拿到了龙珠 3
号!);}else{System.out.println(Thread.currentThread().getName() 收集到
了!!!!);}cyclicBarrier.await();}catch (Exception e){e.printStackTrace();}}, 龙珠 i 号).start();}}
}7.3 信号灯 Semaphore Semaphore 的构造方法中传入的第一个参数是最大信号量可以看成最大线 程池每个信号量初始化为一个最多只能分发一个许可证。使用 acquire 方 法获得许可证release 方法释放许可 场景: 抢车位, 6 部汽车 3 个停车位 SemaphoreDemo
package com.atguigu.test;
import java.util.concurrent.Semaphore;
/**
* Semaphore 案列
*/
public class SemaphoreDemo {/*** 抢车位, 10 部汽车 1 个停车位* param args*/public static void main(String[] args) throws Exception{//定义 3 个停车位Semaphore semaphore new Semaphore(1);//模拟 6 辆汽车停车for (int i 1; i 10; 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();}}
}8 读写锁 8.1 读写锁介绍 现实中有这样一种场景对共享资源有读和写的操作且写操作没有读操作那么频繁。在没有写操作的时候多个线程同时读一个资源没有任何问题所以应该允许多个线程同时读取共享资源但是如果一个线程想去写这些共享资源就不应该允许其他线程对该资源进行读和写的操作了。 针对这种场景JAVA 的并发包提供了读写锁 ReentrantReadWriteLock 它表示两个锁一个是读操作相关的锁称为共享锁一个是写相关的锁称为排他锁
线程进入读锁的前提条件 • 没有其他线程的写锁 • 没有写请求, 或者有写请求但调用线程和持有锁的线程是同一个(可重入锁)。线程进入写锁的前提条件 • 没有其他线程的读锁 • 没有其他线程的写锁 而读写锁有以下三个重要的特性 1公平选择性支持非公平默认和公平的锁获取方式吞吐量还是非公平优于公平。 2重进入读锁和写锁都支持线程重进入。 3锁降级遵循获取写锁、获取读锁再释放写锁的次序写锁能够降级成为读锁。
8.3 入门案例
场景: 使用 ReentrantReadWriteLock 对一个 hashmap 进行读和写操作
8.3.1 实现案例
//资源类
class MyCache {//创建 map 集合private volatile MapString,Object map new HashMap();//创建读写锁对象private ReadWriteLock rwLock new ReentrantReadWriteLock();//放数据public void put(String key,Object value) {//添加写锁rwLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName()
key);//暂停一会TimeUnit.MICROSECONDS.sleep(300);//放数据map.put(key,value);System.out.println(Thread.currentThread().getName()
key);} catch (InterruptedException e) {e.printStackTrace();} finally {//释放写锁rwLock.writeLock().unlock();}}//取数据public Object get(String key) {//添加读锁rwLock.readLock().lock();Object result null;try {System.out.println(Thread.currentThread().getName()
key);//暂停一会TimeUnit.MICROSECONDS.sleep(300);result map.get(key);System.out.println(Thread.currentThread().getName()
key);} catch (InterruptedException e) {e.printStackTrace();} finally {//释放读锁rwLock.readLock().unlock();}return result;}
}8.4 小结(重要) • 在线程持有读锁的情况下该线程不能取得写锁(因为获取写锁的时候如果发现当前的读锁被占用就马上获取失败不管读锁是不是被当前线程持有)。 • 在线程持有写锁的情况下该线程可以继续获取读锁获取读锁时如果发现写锁被占用只有写锁没有被当前线程占用的情况才会获取失败。 原因: 当线程获取读锁的时候可能有其他线程同时也在持有读锁因此不能把获取读锁的线程“升级”为写锁而对于获得写锁的线程它一定独占了读写锁因此可以继续让它获取读锁当它同时获取了写锁和读锁后还可以先释放写锁继续持有读锁这样一个写锁就“降级”为了读锁
9 阻塞队列 9.1 BlockingQueue 简介 Concurrent 包中BlockingQueue 很好的解决了多线程中如何高效安全 “传输”数据的问题。通过这些高效并且线程安全的队列类为我们快速搭建 高质量的多线程程序带来极大的便利。本文详细介绍了 BlockingQueue 家庭 中的所有成员包括他们各自的功能以及常见使用场景。 阻塞队列顾名思义首先它是一个队列, 通过一个共享的队列可以使得数据 由队列的一端输入从另外一端输出
当队列是空的从队列中获取元素的操作将会被阻塞 当队列是满的从队列中添加元素的操作将会被阻塞 试图从空的队列中获取元素的线程将会被阻塞直到其他线程往空的队列插入新的元素 试图向已满的队列中添加新元素的线程将会被阻塞直到其他线程从队列中移除一个或多 个元素或者完全清空使队列变得空闲起来并后续新增 常用的队列主要有以下两种 • 先进先出FIFO先插入的队列的元素也最先出队列类似于排队的功能。 从某种程度上来说这种队列也体现了一种公平性 • 后进先出LIFO后插入队列的元素最先出队列这种队列优先处理最近发 生的事件(栈) 在多线程领域所谓阻塞在某些情况下会挂起线程即阻塞一旦条件满足被挂起 的线程又会自动被唤起 为什么需要 BlockingQueue 好处是我们不需要关心什么时候需要阻塞线程什么时候需要唤醒线程因为这一切BlockingQueue 都给你一手包办了 在 concurrent 包发布以前在多线程环境下我们每个程序员都必须去自己控制这些细节尤其还要兼顾效率和线程安全而这会给我们的程序带来不小的复杂度。多线程环境中通过队列可以很容易实现数据共享比如经典的“生产者”和 “消费者”模型中通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程利用队列的方式来传递数据就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内万一发生数据处理速度不匹配的情况呢理想情况下如果生产者产出数据的速度大于消费者消费的速度并且当生产出来的数据累积到一定程度的时候那么生产者必须暂停等待一下阻塞生产者线程以便等待消费者线程把累积的数据处理完毕反之亦然。 • 当队列中没有数据的情况下消费者端的所有线程都会被自动阻塞挂起直到有数据放入队列 • 当队列中填满数据的情况下生产者端的所有线程都会被自动阻塞挂起直到队列中有空的位置线程被自动唤醒
9.2 BlockingQueue 核心方法 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 获取所有可用的数据对象还可以指定 获取数据的个数通过该方法可以提升获取数据效率不需要多次分批加 锁或释放锁。 9.3 入门案例
mport java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* 阻塞队列
*/
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
// List list new ArrayList();
BlockingQueueString 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));
}
}10 ThreadPool 线程池 10.1 线程池简介 线程池英语thread pool一种线程使用模式。线程过多会带来调度开销 进而影响缓存局部性和整体性能。而线程池维护着多个线程等待着监督管理者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代价。线程池不仅能够保证内核的充分利用还能防止过分调度。 例子 10 年前单核 CPU 电脑假的多线程像马戏团小丑玩多个球CPU 需要来回切换。 现在是多核电脑多个线程各自跑在独立的 CPU 上不用切换效率高。 线程池的优势 线程池做的工作只要是控制运行的线程数量处理过程中将任务放入队列然后在线程创建后启动这些任务如果线程数量超过了最大数量超出数量的线程排队等候等其他线程执行完毕再从队列中取出任务来执行。它的主要特点为 • 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。 • 提高响应速度: 当任务到达时任务可以不需要等待线程创建就能立即执行。 • 提高线程的可管理性: 线程是稀缺资源如果无限制的创建不仅会销耗系统资 源还会降低系统的稳定性使用线程池可以进行统一的分配调优和监控。 • Java 中的线程池是通过 Executor 框架实现的该框架中用到了 ExecutorExecutors ExecutorServiceThreadPoolExecutor 这几个类 10.2 线程池参数说明 本次介绍 5 种类型的线程池 10.2.1 常用参数(重点) • corePoolSize 线程池的核心线程数 • maximumPoolSize 能容纳的最大线程数 • keepAliveTime 空闲线程存活时间 • unit 存活的时间单位 • workQueue 存放提交但未执行任务的队列 • threadFactory 创建线程的工厂类 • handler 等待队列满后的拒绝策略 线程池中有三个重要的参数决定影响了拒绝策略corePoolSize - 核心线 程数也即最小的线程数。workQueue - 阻塞队列 。 maximumPoolSize - 最大线程数 当提交任务数大于 corePoolSize 的时候会优先将任务放到 workQueue 阻 塞队列中。当阻塞队列饱和后会扩充线程池中线程数直到达到 maximumPoolSize 最大线程数配置。此时再多余的任务则会触发线程池 的拒绝策略了。 总结起来也就是一句话当提交的任务数大于workQueue.size() maximumPoolSize 就会触发线程池的拒绝策略。
10.3 线程池的种类与创建
10.3.1 newCachedThreadPool(常用) 作用创建一个可缓存线程池如果线程池长度超过处理需要可灵活回收空闲线程若无可回收则新建线程. 特点: • 线程池中数量没有固定可达到最大值Interger. MAX_VALUE • 线程池中的线程可进行缓存重复利用和回收回收默认时间为 1 分钟 • 当线程池中没有可用线程会重新创建一个线程 创建方式
/**
* 可缓存线程池
* return
*/
public static ExecutorService newCachedThreadPool(){
/**
* corePoolSize 线程池的核心线程数
* maximumPoolSize 能容纳的最大线程数
* keepAliveTime 空闲线程存活时间
* unit 存活的时间单位
* workQueue 存放提交但未执行任务的队列
* threadFactory 创建线程的工厂类:可以省略
* handler 等待队列满后的拒绝策略:可以省略
*/
return new ThreadPoolExecutor(0,
Integer.MAX_VALUE,
60L,
TimeUnit.SECONDS,
new SynchronousQueue(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}场景: 适用于创建一个可无限扩大的线程池服务器负载压力较轻执行时间较短任务多的场景 10.3.2 newFixedThreadPool(常用) 作用创建一个可重用固定线程数的线程池以共享的无界队列方式来运行这些线程。在任意点在大多数线程会处于处理任务的活动状态。如果在所有线程处于活动状态时提交附加任务则在有可用线程之前附加任务将在队列中等待。如果在关闭前的执行期间由于失败而导致任何线程终止那么一个新线程将代替它执行后续的任务如果需要。在某个线程被显式地关闭之前池中的线程将一直存在。 特征 • 线程池中的线程处于一定的量可以很好的控制线程的并发量 • 线程可以重复被使用在显示关闭之前都将一直存在 • 超出一定量的线程被提交时候需在队列中等待 创建方式
/**
* 固定长度线程池
* return
*/
public static ExecutorService newFixedThreadPool(){
/**
* corePoolSize 线程池的核心线程数
* maximumPoolSize 能容纳的最大线程数
* keepAliveTime 空闲线程存活时间
* unit 存活的时间单位
* workQueue 存放提交但未执行任务的队列
* threadFactory 创建线程的工厂类:可以省略
* handler 等待队列满后的拒绝策略:可以省略
*/
return new ThreadPoolExecutor(10,
10,
0L,
TimeUnit.SECONDS,
new LinkedBlockingQueue(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}场景: 适用于可以预测线程数量的业务中或者服务器负载较重对线程数有严格限制的场景 10.3.3 newSingleThreadExecutor(常用) 作用创建一个使用单个 worker 线程的 Executor以无界队列方式来运行该线程。注意如果因为在关闭前的执行期间出现失败而终止了此单个线程那么如果需要一个新线程将代替它执行后续的任务。可保证顺序地执行各个任务并且在任意给定的时间不会有多个线程是活动的。与其他等效的newFixedThreadPool 不同可保证无需重新配置此方法所返回的执行程序即可使用其他的线程。 特征 线程池中最多执行 1 个线程之后提交的线程活动将会排在队列中以此执行 创建方式
/**
* 单一线程池
* return
*/
public static ExecutorService newSingleThreadExecutor(){
/**
* corePoolSize 线程池的核心线程数
* maximumPoolSize 能容纳的最大线程数
* keepAliveTime 空闲线程存活时间
* unit 存活的时间单位
* workQueue 存放提交但未执行任务的队列
* threadFactory 创建线程的工厂类:可以省略
* handler 等待队列满后的拒绝策略:可以省略
*/
return new ThreadPoolExecutor(1,
1,
0L,
TimeUnit.SECONDS,
new LinkedBlockingQueue(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.AbortPolicy());
}场景: 适用于需要保证顺序执行各个任务并且在任意时间点不会同时有多个线程的场景 10.3.4 newScheduleThreadPool(了解) 作用: 线程池支持定时以及周期性执行任务创建一个 corePoolSize 为传入参数最大线程数为整形的最大数的线程池** 特征: 1线程池中具有指定数量的线程即便是空线程也将保留 2可定时或者延迟执行线程活动 创建方式:
public static ScheduledExecutorService newScheduledThreadPool(int
corePoolSize,
ThreadFactory threadFactory) {
return new ScheduledThreadPoolExecutor(corePoolSize,
threadFactory);
}场景: 适用于需要多个后台线程执行周期任务的场景
10.3.5 newWorkStealingPool jdk1.8 提供的线程池底层使用的是 ForkJoinPool 实现创建一个拥有多个任务队列的线程池可以减少连接数创建当前可用 cpu 核数的线程来并行执行任务 创建方式:
public static ExecutorService newWorkStealingPool(int parallelism) {
/**
* parallelism并行级别通常默认为 JVM 可用的处理器个数
* factory用于创建 ForkJoinPool 中使用的线程。
* handler用于处理工作线程未处理的异常默认为 null
* asyncMode用于控制 WorkQueue 的工作模式:队列---反队列
*/
return new ForkJoinPool(parallelism,
ForkJoinPool.defaultForkJoinWorkerThreadFactory,
null,
true);
}场景: 适用于大耗时可并行执行的场景 10.4 线程池入门案例
场景: 火车站 3 个售票口, 10 个用户买票
package com.atguigu.test;
import java.util.concurrent.*;
/**
* 入门案例
*/
public class ThreadPoolDemo1 {
/**
* 火车站 3 个售票口, 10 个用户买票
* param args
*/
public static void main(String[] args) {
//定时线程次:线程数量为 3---窗口数为 3
ExecutorService threadService new ThreadPoolExecutor(3,
3,
60L,
TimeUnit.SECONDS,
new LinkedBlockingQueue(),
Executors.defaultThreadFactory(),
new ThreadPoolExecutor.DiscardOldestPolicy());
try {
//10 个人买票
for (int i 1; i 10; i) {
threadService.execute(()-{
try {
System.out.println(Thread.currentThread().getName()
窗口,开始卖票);
Thread.sleep(5000);
System.out.println(Thread.currentThread().getName()
窗口买票结束);
}catch (Exception e){
e.printStackTrace();
}
});
}
}catch (Exception e){
e.printStackTrace();
}finally {
//完成后结束
threadService.shutdown();
}
}
}12.1 CompletableFuture 简介 CompletableFuture 在 Java 里面被用于异步编程异步通常意味着非阻塞 可以使得我们的任务单独运行在与主线程分离的其他线程中并且通过回调可 以在主线程中得到异步任务的执行状态是否完成和是否异常等信息。 CompletableFuture 实现了 Future, CompletionStage 接口实现了 Future 接口就可以兼容现在有线程池框架而 CompletionStage 接口才是异步编程 的接口抽象里面定义多种异步方法通过这两者集合从而打造出了强大的 CompletableFuture 类。 12.2 Future 与 CompletableFuture Futrue 在 Java 里面通常用来表示一个异步任务的引用比如我们将任务提交到线程池里面然后我们会得到一个 Futrue在 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 入门使用 可详细查看另一篇文章 Java 高级应用-多线程-四FutureTask的介绍及使用