一:Java 线程池相关(*****)
1.Thread类
1.1. 创建线程的几种方式
- 继承Thread类
- 实现Runnable接口,把实现丢给Thread对象实现
- 线程池(下面有详细解析)
// 创建线程池Demo代码
// 1. 继承Thread类
public class MyThread extends Thread{
@Override
public void run() {
for(int i=0;i<1000;i++){
System.out.println(Thread.currentThread().getName()+"线程"+(i*2/10+23*12-38)+(i*3/10+23*12-38));
}
}
}
// 1.1 继承Thread类-启动线程
MyThread myThread = new MyThread();
myThread.start();
// 2. 实现Runnable接口创建及启动
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "线程=============");
}
}
}).start();
// 线程池实现下文分解
......
1.2 继承Thread类原理
-
为什么不直接创建Thread对象调用start()方法?
因为直接创建Thread对象调用start()方法,在该线程中没有我们想要的逻辑代码执行,所以要继承Thread类重写run()方法。run()方法里面的代码就是该线程要执行的逻辑代码。main方法里面的代码其实就是main线程的执行代码,其他线程需放在run()方法中。
-
多线程内存图解
// 图解素材代码
public static void main(String[] args) throws InterruptedException {
// 线程0
MyThread myThread = new MyThread();
myThread.start();
// 线程1
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "线程=============");
}
}
}).start();
// main线程
for (int i = 0; i < 1000; i++) {
System.out.println(Thread.currentThread().getName() + "线程");
}
}
上面代码片段内存图解: |
---|
1.3 实现Runnable原理
- 实现Runnable接口,把实现作为参数给Thread执行是启动线程执行逻辑代码的另一种方式而已。
- 对比继承Thread的好处
- 避免Java单继承限制
- 更符合面向对象的原则,对象和实现分离
1.4 线程的几种状态
-
线程从创建到消亡经历几种状态的转变
创建(new)
、就绪(runable)
、运行(running)
、阻塞(block)
、time waiting
、waiting
、消亡(dead)
;其中block、time wating、 wating不分先后就是在running和dead的中间步骤。
-
七种状态的转化图示
线程状态转化图示: |
---|
1.5 线程的上下文切换
-
对于单核CPU来说(对于多核CPU,此处就理解为一个核),CPU在一个时刻只能运行一个线程,当在运行一个线程的过程中转去运行另外一个线程,这个叫做线程上下文切换(对于进程也是类似)。
-
由于可能当前线程的任务并没有执行完毕,所以在切换时需要保存线程的运行状态,以便下次重新切换回来时能够继续切换之前的状态运行。举个简单的例子:比如一个线程A正在读取一个文件的内容,正读到文件的一半,此时需要暂停线程A,转去执行线程B,当再次切换回来执行线程A的时候,我们不希望线程A又从文件的开头来读取。
-
因此需要记录线程A的运行状态,那么会记录哪些数据呢?因为下次恢复时需要知道在这之前当前线程已经执行到哪条指令了,所以需要记录程序计数器的值,另外比如说线程正在进行某个计算的时候被挂起了,那么下次继续执行的时候需要知道之前挂起时变量的值时多少,因此需要记录CPU寄存器的状态。所以一般来说,线程上下文切换过程中会记录程序计数器、CPU寄存器状态等数据。
-
说简单点的:对于线程的上下文切换实际上就是 存储和恢复CPU状态的过程,它使得线程执行能够从中断点恢复执行。
-
虽然多线程可以使得任务执行的效率得到提升,但是由于在线程切换时同样会带来一定的开销代价,并且多个线程会导致系统资源占用的增加,所以在进行多线程编程时要注意这些因素。
1.6 Thread类属性方法理解
- 重要的几个属性解释
private volatile String name;
:线程的名字private int priority;
:线程的优先级(最大值为10,最小值为1,默认值为5)private boolean daemon = false;
:是否为守护线程private Runnable target;
:要执行的任务
- 重要方法的理解
-
start() 方法:
- start()用来启动一个线程,当调用start方法后,系统才会开启一个新的线程来执行用户定义的子任务,在这个过程中,会为相应的线程分配需要的资源。
-
run() 方法:
- run()方法是不需要用户来调用的,当通过start方法启动一个线程之后,当线程获得了CPU执行时间,便进入run方法体去执行具体的任务。注意,继承Thread类必须重写run方法,在run方法中定义具体要执行的任务。
-
sleep() 方法:
-
两个重载方法:
sleep(
long millis)
//参数为毫秒sleep(
long millis,
intnanoseconds) //第一参数为毫秒,第二个参数为纳秒 -
但是有一点要非常注意,sleep方法不会释放锁,也就是说如果当前线程持有对某个对象的锁,则即使调用sleep方法,其他线程也无法访问这个对象。看下面这个例子就清楚了:
// sleep()方法在休眠的过程中不会释放锁的演示代码 // 线程类 public class MyThread extends Thread{ private Object object = new Object(); @Override public void run() { synchronized (MyThread.class){ System.out.println("进入同步块拿到锁"+new Date()); System.out.println(Thread.currentThread().getName()+"进入休眠状态"); try { Thread.sleep(2000); } catch (InterruptedException e) { e.printStackTrace(); } } } } // 测试方法 public static void main(String[] args) { MyThread myThread0 = new MyThread(); myThread0.start(); MyThread myThread1 = new MyThread(); myThread1.start(); } // 结果 进入同步块拿到锁Thu Mar 03 16:04:04 CST 2022 Thread-0进入休眠状态 进入同步块拿到锁Thu Mar 03 16:04:06 CST 2022 Thread-1进入休眠状态
- 从上面输出结果可以看出,当Thread-0进入睡眠状态之后,Thread-1并没有去执行具体的任务。只有当Thread-0执行完之后,此时Thread-0释放了对象锁,Thread-1才开始执行。
- 注意,如果调用了 sleep() 方法,必须捕获InterruptedException异常或者将该异常向上层抛出。当线程睡眠时间满后,不一定会立即得到执行,因为此时可能CPU正在执行其他的任务。所以说调用sleep方法相当于让线程进入阻塞状态。(加入线程1休眠1000毫秒,当休眠结束但此时CPU在执行其他任务就要等待获取CPU时间片;但是下面说的 yield() 不同它的中断时间是不确定的,时间的长短取决于下次CPU时间片再次轮到自己。加入执行yield()方法但立即该线程又拿到时间片,就又立刻执行)。
-
-
yield() 方法:
- 调用yield方法会让当前线程交出CPU权限,让CPU去执行其他的线程。它跟sleep方法类似,同样不会释放锁。但是yield不能控制具体的交出CPU的时间,另外,yield方法只能让拥有相同优先级的线程有获取CPU执行时间的机会。
- 注意,调用yield方法并不会让线程进入阻塞状态,而是让线程重回就绪状态,它只需要等待重新获取CPU执行时间,这一点是和sleep方法不一样的。
-
join() 方法:
- join方法有三个重载版本:
- join()
- join(long millis) //参数为毫秒
- join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒
- join() 方法用在在一个线程中加入另一个线程的执行:比如A线程正在执行,此时线程调用Thread.join() 方法那么A先停止等待B线程执行;具体是等待指定时间还是等待线程执行完毕看使用的具体重载方法。
// join()方法代码示例 // 线程逻辑代码 public class MyThread2 extends Thread{ private Object object = new Object(); @Override public void run() { try { System.out.println("当前线程"+Thread.currentThread().getName()+"开始执行"); Thread.sleep(2000); System.out.println("当前线程"+Thread.currentThread().getName()+"执行完毕"); } catch (InterruptedException e) { e.printStackTrace(); } } } // main线程等待其他线程执行完毕 public static void main(String[] args) throws InterruptedException { System.out.println(Thread.currentThread().getName()+"开始执行"); MyThread2 myThread = new MyThread2(); myThread.start(); System.out.println(Thread.currentThread().getName()+"等待"); myThread.join(); System.out.println(Thread.currentThread().getName()+"继续执行"); } // 结果 main开始执行 main等待 当前线程Thread-0开始执行 当前线程Thread-0执行完毕 main继续执行 ** 结论:可以看出,myThread.join()方法后,main线程会进入等待,然后等待Thread-0执行完之后再继续执行。
- join() 方法的特性:
- wait方法会让线程进入阻塞状态,并且会释放线程占有的锁,并交出CPU执行权限。
- 由于wait方法会让线程释放对象锁,所以join方法同样会让线程释放对一个对象持有的锁
- join方法有三个重载版本:
-
interrupt() 方法
- interrupt 顾名思义,即中断的意思。
- 单独调用interrupt方法可以使得处于阻塞状态的线程抛出一个异常,也就说,它可以用来中断一个正处于阻塞状态的线程;另外,通过interrupt方法和isInterrupted()方法来停止正在运行的线程。
- interrupt()方法只能中断**
阻塞
的线程,不能中断正常
**执行的
// 线程
public class MyThread2 extends Thread{
private Object object = new Object();
@Override
public void run() {
try {
System.out.println("当前线程"+Thread.currentThread().getName()+"开始执行");
System.out.println("当前线程"+Thread.currentThread().getName()+"开始休眠");
Thread.sleep(2000);
System.out.println("当前线程"+Thread.currentThread().getName()+"执行完毕");
} catch (InterruptedException e) {
System.out.println("当前线程"+Thread.currentThread().getName()+"发生异常"+e.getMessage());
e.printStackTrace();
}
}
}
// 测试
public static void main(String[] args) {
System.out.println("主线程开始工作");
MyThread2 myThread2 = new MyThread2();
myThread2.start();
myThread2.interrupt();
}
// 结果
主线程开始工作
当前线程Thread-0开始执行
当前线程Thread-0开始休眠
当前线程Thread-0发生异常sleep interrupted
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at com.zhw.thread.pool.thread.MyThread2.run(MyThread2.java:15)
总结:通过interrupt方法可以中断处于阻塞状态的线程;补充不能中断非阻塞的线程
1.7 thread的方法对线程状态的影响
2. 线程池-Executor 框架
线程池的好处:
- 降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
- 提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
2.1. Executor 框架的结构
- Executor框架的结构由三部分组成:
-
任务(Runnable 或者 Callable)
-
任务的执行(Executor)
-
异步执行结果(Future)
- 任务(Runnable 或者 Callable)
public class MyRunnable implements Runnable{
@Override
public void run() {
// 要执行的任务代码
......
}
}
public class MyCallable implements Callable<String> {
@Override
public String call() throws Exception {
// 要执行的任务代码
......
return "任务执行完成的返回值";
}
}
- 任务的执行(Executor)
Executor类图: |
---|
如上图所示:
-
包括任务执行机制的核心接口
Executor
,以及继承自Executor
接口的ExecutorService
接口。 -
ThreadPoolExecutor
和ScheduledThreadPoolExecutor
这两个关键类实现了 ExecutorService 接口。 -
ThreadPoolExecutor 这个类,这个类在我们实际使用线程池的过程中,使用频率还是非常高的。
-
异步执行结果(Future)
Future
接口以及Future
接口的实现类FutureTask
类都可以代表异步计算的结果。当我们把
Runnable
接口 或Callable
接口 的实现类提交给ThreadPoolExecutor
或ScheduledThreadPoolExecutor
执行,可以调用
execute()
方法和submit()
方法执行(调用submit()
方法时会返回一个FutureTask
对象) -
Executor框架的执行步骤
-
主线程首先要创建实现
Runnable
或者Callable
接口的任务对象。 -
把创建完成的实现
Runnable
/Callable
接口的 任务对象直接交给ExecutorService
执行:ExecutorService.execute(Runnable command)
)/ExecutorService.execute(Callable <T> task)
)ExecutorService.submit(Runnable task)
/ExecutorService.submit(Callable <T> task)
)
-
如果执行
ExecutorService.submit(…)
,ExecutorService
将返回一个实现Future
接口的对象 -
最后,主线程可以执行
FutureTask.get()
方法来等待任务执行完成。主线程也可以执行FutureTask.cancel(boolean mayInterruptIfRunning)
来取消此任务的执行。
-
2.2. Executor框架之ThreadPoolExecutor类
- 构造函数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler) {
if (corePoolSize < 0 ||
maximumPoolSize <= 0 ||
maximumPoolSize < corePoolSize ||
keepAliveTime < 0)
throw new IllegalArgumentException();
if (workQueue == null || threadFactory == null || handler == null)
throw new NullPointerException();
this.acc = System.getSecurityManager() == null ?
null :
AccessController.getContext();
this.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
- 参数释义
- corePoolSize:线程池的核心线程数,当线程池创建的时候就会预先创建的线程数
- workQueue:任务队列,用来储存等待执行任务的队列
- maximumPoolSize:最大线程数,当核心线程用完 && 任务队列排满,可以启动新的线程
- keepAliveTime:当线程数大于核心线程数时,多余的空闲线程存活的最长时间
- TimeUnit unit:存活时间的时间单位
- threadFactory:线程工厂,用来创建线程,一般默认即可
- handler:拒绝策略,当提交的任务过多而不能及时处理时,我们可以定制策略来处理任务
- 阻塞队列
-
线程工厂(ThreadFactory)
-
拒绝策略
- ThreadPoolExecutor.AbortPolicy :抛出 RejectedExecutionException来拒绝新任务的处理。
- ThreadPoolExecutor.CallerRunsPolicy :调用执行自己的线程运行任务,也就是直接在调用execute方法的线程中运行(run)被拒绝的任务,如果执行程序已关闭,则会丢弃该任务。因此这种策略会降低对于新任务提交速度,影响程序的整体性能。如果您的应用程序可以承受此延迟并且你要求任何一个任务请求都要被执行的话,你可以选择这个策略。
- ThreadPoolExecutor.DiscardPolicy :不处理新任务,直接丢弃掉。
- ThreadPoolExecutor.DiscardOldestPolicy : 此策略将丢弃最早的未处理的任务请求。
2.3. 创建线程池的方式
- 方式一:通过ThreadPoolExecutor构造函数实现(推荐)
- 方式二:通过
Executor
框架的工具类Executors
来实现 我们可以创建三种类型的ThreadPoolExecutor
- FixedThreadPool
- SingleThreadExecutor
- CachedThreadPool
第二种方式存在的问题:
FixedThreadPool 和 SingleThreadExecutor : 允许请求的队列长度为 Integer.MAX_VALUE,可能堆积大量的请求,从而导致 OOM。
CachedThreadPool 和 ScheduledThreadPool : 允许创建的线程数量为 Integer.MAX_VALUE ,可能会创建大量线程,从而导致 OOM。
- 推荐使用
ThreadPoolExecutor
构造函数创建线程池
- 在《阿里巴巴 Java 开发手册》“并发处理”这一章节,明确指出线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
2.4. ThreadPoolExecutor 使用+原理分析
- ThreadPoolExecutor 使用Demo
public static void main(String[] args) {
// 创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(30), new ThreadPoolExecutor.AbortPolicy());
// 执行线程
for (int i = 0; i < 50; i++) {
// threadPoolExecutor.execute(new MyRunnable("dd"));
threadPoolExecutor.execute(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
}
});
}
//终止线程池
threadPoolExecutor.shutdown();
while (!threadPoolExecutor.isTerminated()) {
}
System.out.println("Finished all threads");
}
-
线程池原理分析
① ThreadPoolExecutor类重要 属性标识
// 用来标记线程池状态和线程池工作线程个数的int数值 // 32位 前3位标记线程池状态 & 后29位标记线程池工作线程个数 private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); // 固定值 29 (方便后面做位运算) private static final int COUNT_BITS = Integer.SIZE - 3; // 线程池中工作线程最大容量 00011111 11111111 11111111 11111111 private static final int CAPACITY = (1 << COUNT_BITS) - 1; // 线程池状态 // runState is stored in the high-order bits // -1 32个1 ;左移29位 11100000 00000000 00000000 00000000 // 111 RUNNING : 正常接收任务 private static final int RUNNING = -1 << COUNT_BITS; // 000 SHUTDOWN : 不接受任务、队列和正在执行的继续 private static final int SHUTDOWN = 0 << COUNT_BITS; // 001 STOP : 不接受任务、队列和正在执行的中断 private static final int STOP = 1 << COUNT_BITS; // 010 TIDYING : 过渡状态;清空工作线程,为结束作准备 private static final int TIDYING = 2 << COUNT_BITS; // 011 TERMINATED : 结束 private static final int TERMINATED = 3 << COUNT_BITS; // Packing and unpacking ctl // 线程池的状态 private static int runStateOf(int c) { return c & ~CAPACITY; } // 线程池的工作线程数 private static int workerCountOf(int c) { return c & CAPACITY; } private static int ctlOf(int rs, int wc) { return rs | wc; }
线程池各个状态及它们之间的转换 ② execute()方法源码分析:
public void execute(Runnable command) {
if (command == null)
throw new NullPointerException();
// 32位ctl值,线程池的状态和正在工作的线程数
int c = ctl.get();
if (workerCountOf(c) < corePoolSize) {
// 1.当前正在跑的线程数 < 核心线程数 ,通过addWorker(command, true)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
if (addWorker(command, true))
return;
// 可能由于并发第二个if没有进来,重新获取32位ctl值
c = ctl.get();
}
// 2.如果当前之行的任务数量大于等于 corePoolSize 的时候就会走到这里(核心线程已满,队列未满)
// 通过 isRunning 方法判断线程池状态,线程池处于 RUNNING 状态并且队列可以加入任务,该任务才会被加入进去
if (isRunning(c) && workQueue.offer(command)) {
int recheck = ctl.get();
// 再次获取线程池状态,如果线程池状态不是 RUNNING 状态,就移除任务并拒绝
if (! isRunning(recheck) && remove(command))
reject(command);
// 线程池是Running状态,但是线程池没有正在工作的线程
else if (workerCountOf(recheck) == 0)
// 启动一个工作线程
addWorker(null, false);
}
//3. 通过addWorker(command, false)新建一个线程,并将任务(command)添加到该线程中;然后,启动该线程从而执行任务。
//如果addWorker(command, false)执行失败,则通过reject()执行相应的拒绝策略的内容。
else if (!addWorker(command, false))
reject(command);
}
启用线程池线程三部曲图示: |
---|
③ execute()方法的执行核心是addWorker(Runnable firstTask, boolean core)
方法;这个方法主要用来创建新的工作线程,如果返回 true 说明创建和启动工作线程成功,否则的话返回的就是 false。
firstTask:需要往线程池里面添加的任务
core:使用的是否是核心线程
// 全局锁,并发操作必备
private final ReentrantLock mainLock = new ReentrantLock();
// 跟踪线程池的最大大小,只有在持有全局锁mainLock的前提下才能访问此集合
private int largestPoolSize;
// 工作线程集合,存放线程池中所有的(活跃的)工作线程,只有在持有全局锁mainLock的前提下才能访问此集合
private final HashSet<Worker> workers = new HashSet<>();
//获取线程池状态
private static int runStateOf(int c) { return c & ~CAPACITY; }
//判断线程池的状态是否为 Running
private static boolean isRunning(int c) {
return c < SHUTDOWN;
}
private boolean addWorker(Runnable firstTask, boolean core) {
retry:
for (;;) {
int c = ctl.get();
int rs = runStateOf(c);
// rs >= SHUTDOWN:线程池非Running状态
// 1. 线程池是stop及后面的状态 2. firstTask不为null 3. 阻塞队列为空 (只要满足任何一个)
if (rs >= SHUTDOWN &&
! (rs == SHUTDOWN &&
firstTask == null &&
! workQueue.isEmpty()))
return false;
for (;;) {
// 获取线程池的工作线程个数
int wc = workerCountOf(c);
if (wc >= CAPACITY ||
wc >= (core ? corePoolSize : maximumPoolSize))
return false;
//原子操作将workcount的数量加1
if (compareAndIncrementWorkerCount(c))
// 跳出两层循环
break retry;
c = ctl.get(); // Re-read ctl
// 如果线程的状态被其他线程改变了,就再次执行上述操作重新尝试启动线程执行任务
if (runStateOf(c) != rs)
continue retry;
// else CAS failed due to workerCount change; retry inner loop
}
}
// 工作线程启动状态
boolean workerStarted = false;
// 工作线程创建状态
boolean workerAdded = false;
Worker w = null;
// 创建并启动线程
try {
w = new Worker(firstTask);
final Thread t = w.thread;
if (t != null) {
final ReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
// Recheck while holding lock.
// Back out on ThreadFactory failure or if
// shut down before lock acquired.
int rs = runStateOf(ctl.get());
if (rs < SHUTDOWN ||
(rs == SHUTDOWN && firstTask == null)) {
if (t.isAlive()) // precheck that t is startable
throw new IllegalThreadStateException();
// 将任务添加带集合
workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize = s;
// 任务创建成功
workerAdded = true;
}
} finally {
mainLock.unlock();
}
if (workerAdded) {
// 任务启动成功
t.start();
workerStarted = true;
}
}
} finally {
if (! workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
知识补充:
- 内层for跳出外层for循环
punlic void template(){
retry
for(;;){
for(;;){
break retry;
}
}
}
2.5. 线程相关几组对比
Runnable
vsCallable
-
Runnable
接口自 Java 1.0 以来一直存在,但
Callable
接口仅在 Java 1.5 中引入,目的就是为了来处理
Runnable`不支持的用例。 -
Runnable
接口不会返回结果或抛出检查异常,但是Callable
接口可以。所以,如果任务不需要返回结果或抛出异常推荐使用Runnable
接口,这样代码看起来会更加简洁。 -
工具类
Executors
可以实现将Runnable
对象转换成Callable
对象。Runnable: @FunctionalInterface public interface Runnable { public abstract void run(); } Callable: @FunctionalInterface public interface Callable<V> { V call() throws Exception; }
execute()
vssubmit()
-
execute()
方法用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功与否; -
submit()
方法用于提交需要返回值的任务。线程池会返回一个Future
类型的对象,通过这个Future
对象可以判断任务是否执行成功,并且可以通过Future
的get()
方法来获取返回值,get()
方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)
方法的话,如果在timeout
时间内任务还没有执行完,就会抛出java.util.concurrent.TimeoutException
。// submit()方法使用示例 public class SubmitMethod { public static void main(String[] args) { MyThreadPool myThreadPool = new MyThreadPool(); ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 1, TimeUnit.SECONDS, new ArrayBlockingQueue<>(30), new ThreadPoolExecutor.AbortPolicy()); try { Future<String> future = threadPoolExecutor.submit(new Callable<String>() { @Override public String call() throws InterruptedException { // 休眠5秒 Thread.sleep(1000 * 5l); return "dd"; } }); String s1 = null; // 4秒没结果,抛出异常 s1 = future.get(4, TimeUnit.SECONDS); System.out.println(s1 + "输出"); threadPoolExecutor.shutdown(); }catch (Exception e){ e.printStackTrace(); } } } 结果: java.util.concurrent.TimeoutException at java.util.concurrent.FutureTask.get(FutureTask.java:205) at com.zhw.thread.pool.SubmitMethod.main(SubmitMethod.java:23)
shutdown()
VSshutdownNow()
shutdown()
:关闭线程池,线程池的状态变为SHUTDOWN
。线程池不再接受新任务了,但是队列里的任务得执行完毕。shutdownNow()
:关闭线程池,线程的状态变为STOP
。线程池会终止当前正在运行的任务,并停止处理排队的任务并返回正在等待执行的 List。
isTerminated()
VSisShutdown()
isShutDown
当调用shutdown()
方法后返回为 true。isTerminated
当调用shutdown()
方法后,并且所有提交的任务完成后返回为 true。
2.6. ScheduledThreadPoolExecutor简单使用案例
简介:ScheduledThreadPoolExecutor 主要用来在给定的延迟后运行任务,或者定期执行任务。 这个在实际项目中基本不会被用到,也不推荐使用,大家只需要简单了解一下它的思想即可。
public static void main(String[] args) {
ScheduledThreadPoolExecutor scheduledThreadPoolExecutor = new ScheduledThreadPoolExecutor(5);
// 固定延迟执行
scheduledThreadPoolExecutor.schedule(() -> {
System.out.println(".....");
}, 2, TimeUnit.SECONDS);
// 固定频率执行
scheduledThreadPoolExecutor.scheduleAtFixedRate(() -> {
System.out.println(".....");
}, 2, 5, TimeUnit.SECONDS);
......
}
2.7. 线程池大小的确定
- 上下文切换概念:
多线程编程中一般线程的个数都大于 CPU 核心的个数,而一个 CPU 核心在任意时刻只能被一个线程使用,为了让这些线程都能得到有效执行,CPU 采取的策略是为每个线程分配时间片并轮转的形式。
当一个线程的时间片用完的时候就会重新处于就绪状态让给其他线程使用,这个过程就属于一次上下文切换。
概括来说就是:当前任务在执行完 CPU 时间片切换到另一个任务之前会先保存自己的状态,以便下次再切换回这个任务时,可以再加载这个任务的状态。
任务从保存到再加载的过程就是一次上下文切换。
上下文切换通常是计算密集型的。也就是说,它需要相当可观的处理器时间,在每秒几十上百次的切换中,每次切换都需要纳秒量级的时间。所以,上下文切换对系统来说意味着消耗大量的 CPU 时间,事实上,可能是操作系统中时间消耗最大的操作。 Linux 相比与其他操作系统(包括其他类 Unix 系统)有很多的优点,其中有一项就是,其上下文切换和模式切换的时间消耗非常少。
- 线程池设置出发点
- 如果我们设置的线程池数量太小的话,如果同一时间有大量任务/请求需要处理,可能会导致大量的请求/任务在任务队列中排队等待执行,甚至会出现任务队列满了之后任务/请求无法处理的情况,或者大量任务堆积在任务队列导致 OOM。这样很明显是有问题的! CPU 根本没有得到充分利用。
- 如果我们设置线程数量太大,大量线程可能会同时在争取 CPU 资源,这样会导致大量的上下文切换,从而增加线程的执行时间,影响了整体执行效率。
- 简单并且适用面比较广的公式:
- CPU 密集型任务(N+1): 这种任务消耗的主要是 CPU 资源,可以将线程数设置为 N(CPU 核心数)+1,比 CPU 核心数多出来的一个线程是为了防止线程偶发的缺页中断,或者其它原因导致的任务暂停而带来的影响。一旦任务暂停,CPU 就会处于空闲状态,而在这种情况下多出来的一个线程就可以充分利用 CPU 的空闲时间。
- I/O 密集型任务(2N): 这种任务应用起来,系统会用大部分的时间来处理 I/O 交互,而线程在处理 I/O 的时间段内不会占用 CPU 来处理,这时就可以将 CPU 交出给其它线程使用。因此在 I/O 密集型任务的应用中,我们可以多配置一些线程,具体的计算方法是 2N。
-
CPU 密集型任务:与I/O 密集型任务
CPU 密集型简单理解就是利用 CPU 计算能力的任务比如你在内存中对大量数据进行排序。但凡涉及到网络读取,文件读取这类都是 IO 密集型,这类任务的特点是 CPU 计算耗费时间相比于等待 IO 操作完成的时间来说很少,大部分时间都花在了等待 IO 操作完成上。
3. 线程间的协作
3.1. wait()、notify()和notifyAll()
-
调用某个对象的wait()方法能让当前线程阻塞,并且当前线程必须拥有此对象的monitor(即锁);
- 如果调用某个对象的wait()方法,当前线程必须拥有这个对象的monitor(即锁),因此调用wait()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
- 调用某个对象的wait()方法,相当于让当前线程交出此对象的monitor,然后进入等待状态,等待后续再次获得此对象的锁(Thread类中的sleep方法使当前线程暂停执行一段时间,从而让其他线程有机会继续执行,但它并不释放对象锁);
-
调用某个对象的notify()方法能够唤醒一个正在等待这个对象的monitor的线程,如果有多个线程都在等待这个对象的monitor,则只能唤醒其中一个线程;
- notify()方法能够唤醒一个正在等待该对象的monitor的线程,当有多个线程都在等待该对象的monitor的话,则只能唤醒其中一个线程,具体唤醒哪个线程则不得而知。
- 同样地,调用某个对象的notify()方法,当前线程也必须拥有这个对象的monitor,因此调用notify()方法必须在同步块或者同步方法中进行(synchronized块或者synchronized方法)。
-
调用notifyAll()方法能够唤醒所有正在等待这个对象的monitor的线程;
- 这里要注意一点:notify()和notifyAll()方法只是唤醒等待该对象的monitor的线程,并不决定哪个线程能够获取到monitor。
举个例子: 假如有三个线程Thread1、Thread2和Thread3都在等待对象objectA的monitor,此时Thread4拥有对象objectA的monitor,当在Thread4中调用objectA.notify()方法之后,Thread1、Thread2和Thread3只有一个能被唤醒。注意,被唤醒不等于立刻就获取了objectA的monitor。假若在Thread4中调用objectA.notifyAll()方法,则Thread1、Thread2和Thread3三个线程都会被唤醒,至于哪个线程接下来能够获取到objectA的monitor就具体依赖于操作系统的调度了。尤其要注意一点,一个线程被唤醒不代表立即获取了对象的monitor,只有等调用完notify()或者notifyAll()并退出synchronized块,释放对象锁后,其余线程才可获得锁执行。 // 实例代码 // 线程一作为被唤醒的线程 public class MyThread1 extends Thread { @Override public void run() { try { synchronized (MyThread2.object) { Thread.currentThread().setName("线程一"); System.out.println(Thread.currentThread().getName()+"等待"); MyThread2.object.wait(); System.out.println(Thread.currentThread().getName()+"被唤醒"); } } catch (InterruptedException e) { e.printStackTrace(); } } } // 线程二作为唤醒线程一的线程 public class MyThread2 extends Thread{ static Object object = new Object(); @Override public void run() { try { synchronized (MyThread2.object){ Thread.currentThread().setName("线程二"); System.out.println(Thread.currentThread().getName() + "唤醒等待该对象锁的线程"); object.notify(); System.out.println(Thread.currentThread().getName() + "交出对象锁唤醒线程"); } } catch (Exception e) { e.printStackTrace(); } } } // 测试 public static void main(String[] args) { MyThread1 myThread1 = new MyThread1(); myThread1.start(); MyThread2 myThread2 = new MyThread2(); myThread2.start(); } // 结果 线程一等待 线程二唤醒等待该对象锁的线程 线程二交出对象锁唤醒线程 线程一被唤醒 ** 总结: 1. 无论是等待操作、还是唤醒操作都在同步代码块中进行 2. 在线程A中只有拿到某个对象的锁才能让线程A wait(), wait()之后就是使得该线程交出对象锁 3. 此时线程B就可以拿到对象锁执行 notify(),这个方法将唤醒等待该对象锁(处于wait的线程);当有多个线程都在等待该对象的monitor的话,则只能唤醒其中 一个线程。只是唤醒线程具体是否执行要看CPU调度。
-
wait() 和 notify() 实现生产者-消费者模型
// 生产者 public class MyThread1 extends Thread { static int queueLength = 10; static public PriorityQueue queue = new PriorityQueue(queueLength); @Override public void run() { try { while (true) { synchronized (MyThread2.object) { while (queue.size() == 0) { System.out.println("消费队列空"); // 把对象锁释放,使得生产者可以获取到对象锁 MyThread2.object.wait(); } Object poll = queue.poll(); System.out.println("弹出元素"+poll+"剩余元素个数"+queue.size()); // 把对象锁释放,生产者就可以拿到锁生产 MyThread2.object.notify(); } } } catch (Exception e) { e.printStackTrace(); } } } // 消费者 public class MyThread2 extends Thread { static Object object = new Object(); @Override public void run() { try { while (true) { synchronized (MyThread2.object) { while (MyThread1.queue.size() == MyThread1.queueLength){ System.out.println("生产者队列已满"); MyThread2.object.wait(); } System.out.println("填充元素"); MyThread1.queue.offer(Math.random()); MyThread2.object.notify(); } } } catch (Exception e) { e.printStackTrace(); } } } // 测试 public class WaitTest { public static void main(String[] args) { MyThread1 myThread1 = new MyThread1(); myThread1.start(); MyThread2 myThread2 = new MyThread2(); myThread2.start(); } }
3.2. Condition
-
基本概念
-
Condition是个接口,基本的方法就是await()和signal()方法;
-
Condition依赖于Lock接口,生成一个Condition的基本代码是lock.newCondition() ;
-
调用Condition的await()和signal()方法,都必须在lock保护之内,就是说必须在lock.lock()和lock.unlock之间才可以使用;
-
Conditon中的await()对应Object的wait();
Condition中的signal()对应Object的notify();
Condition中的signalAll()对应Object的notifyAll()。
-
-
生产者-消费者模型的实现
二:异步编排(completableFuture)
异步编排不同于多线程,它会有任务的执行顺序的要求
1. 异步编排-启动任务
启动任务的四个方法
// 返回值 ,不使用指定线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier) {
return asyncSupplyStage(asyncPool, supplier);
}
// 返回值 ,使用指定线程池
public static <U> CompletableFuture<U> supplyAsync(Supplier<U> supplier,Executor executor) {
return asyncSupplyStage(screenExecutor(executor), supplier);
}
// 无返回值 ,不使用指定线程池
public static CompletableFuture<Void> runAsync(Runnable runnable) {
return asyncRunStage(asyncPool, runnable);
}
// 无返回值 ,使用指定线程池
public static CompletableFuture<Void> runAsync(Runnable runnable, Executor executor) {
return asyncRunStage(screenExecutor(executor), runnable);
}
代码示例
// 线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(10, 20, 10, TimeUnit.SECONDS, new ArrayBlockingQueue(30), new ThreadPoolExecutor.AbortPolicy());
// ===============================runAsync示例==============================================
CompletableFuture.runAsync(()->{
System.out.println("无返回值的异步方法执行");
},threadPoolExecutor);
//==================================supplyAsync示例=============================================
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println("有返回值的异步方法执行");
return 1;
}, threadPoolExecutor);
System.out.println(future.get());
}
2. 回调与异常感知
一个任务执行结束之后,需要执行业务或者抛出异常之后需要执行的业务
// ==============================whenComplete:没有返回值===============================================
public CompletableFuture<T> whenComplete(
BiConsumer<? super T, ? super Throwable> action) {
return uniWhenCompleteStage(null, action);
}
// 对上一步执行的结果和异常处理,非自定义线程池
public CompletableFuture<T> whenCompleteAsync(
BiConsumer<? super T, ? super Throwable> action) {
return uniWhenCompleteStage(asyncPool, action);
}
// 对上一步执行的结果和异常处理,自定义线程池
public CompletableFuture<T> whenCompleteAsync(
BiConsumer<? super T, ? super Throwable> action, Executor executor) {
return uniWhenCompleteStage(screenExecutor(executor), action);
}
// ===================================handle:有返回值==========================================
public <U> CompletableFuture<U> handle(
BiFunction<? super T, Throwable, ? extends U> fn) {
return uniHandleStage(null, fn);
}
// 对上一步执行的结果和异常处理,非自定义线程池
public <U> CompletableFuture<U> handleAsync(
BiFunction<? super T, Throwable, ? extends U> fn) {
return uniHandleStage(asyncPool, fn);
}
// 对上一步执行的结果和异常处理,自定义线程池
public <U> CompletableFuture<U> handleAsync(
BiFunction<? super T, Throwable, ? extends U> fn, Executor executor) {
return uniHandleStage(screenExecutor(executor), fn);
}
代码示例
// ============================whenCompleteAsync示例========================================
// 无返回值不能对异常处理
CompletableFuture.supplyAsync(()-> {
System.out.println(10);
System.out.println(1/0);
return 10;
// res:上一步操作的返回值 ; exception: 上一步操作的异常
},threadPoolExecutor ).whenCompleteAsync((res,exception)->{
System.out.println(res);
System.out.println(exception.getMessage());
},threadPoolExecutor);
// ============================handle示例========================================
// handle有返回值可以对异常处理
CompletableFuture<Integer> future = CompletableFuture.supplyAsync(() -> {
System.out.println(10);
System.out.println(1 / 0);
return 10;
// res:上一步操作的返回值 ; exception: 上一步操作的异常
}, threadPoolExecutor).handleAsync((res, exception) -> {
System.out.println(res);
System.out.println(exception.getMessage());
if (res != null) {
return 1;
}
if (exception != null) {
return 0;
}
return null;
}, threadPoolExecutor);
System.out.println("============"+future.get());
3. 线程串行化
串行化是任务的执行顺序的控制,把多个任务按照业务需求排序执行
// =====================thenRun:不能感知上一步结果无返回值===============================================
public CompletableFuture<Void> thenRun(Runnable action) {
return uniRunStage(null, action);
}
public CompletableFuture<Void> thenRunAsync(Runnable action) {
return uniRunStage(asyncPool, action);
}
public CompletableFuture<Void> thenRunAsync(Runnable action,
Executor executor) {
return uniRunStage(screenExecutor(executor), action);
}
// ========================thenAccept:能感知上一步结果无返回值============================================
public CompletableFuture<Void> thenAccept(Consumer<? super T> action) {
return uniAcceptStage(null, action);
}
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action) {
return uniAcceptStage(asyncPool, action);
}
public CompletableFuture<Void> thenAcceptAsync(Consumer<? super T> action,
Executor executor) {
return uniAcceptStage(screenExecutor(executor), action);
}
// ======================thenApply:能感知上一步结果有返回值==============================================
public <U> CompletableFuture<U> thenApply(
Function<? super T,? extends U> fn) {
return uniApplyStage(null, fn);
}
public <U> CompletableFuture<U> thenApplyAsync(
Function<? super T,? extends U> fn) {
return uniApplyStage(asyncPool, fn);
}
public <U> CompletableFuture<U> thenApplyAsync(
Function<? super T,? extends U> fn, Executor executor) {
return uniApplyStage(screenExecutor(executor), fn);
}
4. 多任务组合
// ======================thenCombine:组合任务能感知上一步结果有返回值==================================
public <U,V> CompletableFuture<V> thenCombine(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn) {
return biApplyStage(null, other, fn);
}
public <U,V> CompletableFuture<V> thenCombineAsync(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn) {
return biApplyStage(asyncPool, other, fn);
}
public <U,V> CompletableFuture<V> thenCombineAsync(
CompletionStage<? extends U> other,
BiFunction<? super T,? super U,? extends V> fn, Executor executor) {
return biApplyStage(screenExecutor(executor), other, fn);
}
// ======================thenAcceptBoth:组合任务能感知上一步结果无返回值==================================
public <U> CompletableFuture<Void> thenAcceptBoth(
CompletionStage<? extends U> other,
BiConsumer<? super T, ? super U> action) {
return biAcceptStage(null, other, action);
}
public <U> CompletableFuture<Void> thenAcceptBothAsync(
CompletionStage<? extends U> other,
BiConsumer<? super T, ? super U> action) {
return biAcceptStage(asyncPool, other, action);
}
public <U> CompletableFuture<Void> thenAcceptBothAsync(
CompletionStage<? extends U> other,
BiConsumer<? super T, ? super U> action, Executor executor) {
return biAcceptStage(screenExecutor(executor), other, action);
}
// ======================thenCombine:组合任务不能感知上一步结果无返回值==================================
public CompletableFuture<Void> runAfterBoth(CompletionStage<?> other,
Runnable action) {
return biRunStage(null, other, action);
}
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,
Runnable action) {
return biRunStage(asyncPool, other, action);
}
public CompletableFuture<Void> runAfterBothAsync(CompletionStage<?> other,
Runnable action,
Executor executor) {
return biRunStage(screenExecutor(executor), other, action);
}
-
thenCombine、thenAcceptBoth 和runAfterBoth
这三个方法都是将两个CompletableFuture组合起来处理,只有两个任务都正常完成时,才进行下阶段任务。 -
区别:
- thenCombine会将两个任务的执行结果作为所提供函数的参数,且该方法有返回值;
- thenAcceptBoth同样将两个任务的执行结果作为方法入参,但是无返回值;
- runAfterBoth没有入参,也没有返回值。注意两个任务中只要有一个执行异常,则将该异常信息作为指定任务的执行结果。
测试代码:
// ================thenCombine===================
CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + " cf1 do something....");
return 1;
});
CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + " cf2 do something....");
return 2;
});
CompletableFuture<Integer> cf3 = cf1.thenCombine(cf2, (a, b) -> {
System.out.println(Thread.currentThread() + " cf3 do something....");
return a + b;
});
System.out.println("cf3结果->" + cf3.get());
// ===============thenAcceptBoth====================
CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + " cf1 do something....");
return 1;
});
CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + " cf2 do something....");
return 2;
});
CompletableFuture<Void> cf3 = cf1.thenAcceptBoth(cf2, (a, b) -> {
System.out.println(Thread.currentThread() + " cf3 do something....");
System.out.println(a + b);
});
System.out.println("cf3结果->" + cf3.get());
// ==============runAfterBoth========================
CompletableFuture<Integer> cf1 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + " cf1 do something....");
return 1;
});
CompletableFuture<Integer> cf2 = CompletableFuture.supplyAsync(() -> {
System.out.println(Thread.currentThread() + " cf2 do something....");
return 2;
});
CompletableFuture<Void> cf3 = cf1.runAfterBoth(cf2, () -> {
System.out.println(Thread.currentThread() + " cf3 do something....");
});
System.out.println("cf3结果->" + cf3.get());