面试总结-线程池
- 1.线程池类型及应用
- 1.1newSingleThreadExecutor单个线程的线程池
- 1.2newFixedThreadExecutor(n)固定数量的线程池
- 1.3newCacheThreadExecutor(推荐使用)可缓存线程池
- 1.4newScheduleThreadExecutor大小无限制的线程池
- 2.线程池任务执行流程:
- 3.一个线程有三个方法,怎么保证三个是顺序执行
- 第一种方式:
- 第二种方式:
- 第三种方式:
- 第四种方式:
1.线程池类型及应用
线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。 Executors可以说是一个工厂类,可以用它来创建不同的线程池。
常见的几个线程池
1.1newSingleThreadExecutor单个线程的线程池
ExecutorService sPool = Executors.newSingleThreadExecutor();
即线程池中每次只有一个线程工作,单线程串行执行任务。
底层:FinalizableDelegatedExecutorService包装的ThreadPoolExecutor实例,corePoolSize为1;maximumPoolSize为1;keepAliveTime为0L;unit为:TimeUnit.MILLISECONDS;workQueue为:new LinkedBlockingQueue() 无解阻塞队列
通俗:创建只有一个线程的线程池,且线程的存活时间是无限的;当该线程正繁忙时,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:一个任务一个任务执行的场景
1.2newFixedThreadExecutor(n)固定数量的线程池
ExecutorService fPool = Executors.newFixedThreadPool(3);
每提交一个任务就是一个线程,直到达到线程池的最大数量,然后后面进入等待队列直到前面的任务完成才继续执行。
底层:返回ThreadPoolExecutor实例,接收参数为所设定线程数量nThread,corePoolSize为nThread,maximumPoolSize为nThread;keepAliveTime为0L(不限时);unit为:TimeUnit.MILLISECONDS;WorkQueue为:new LinkedBlockingQueue() 无解阻塞队列
通俗:创建可容纳固定数量线程的池子,每隔线程的存活时间是无限的,当池子满了就不在添加线程了;如果池中的所有线程均在繁忙状态,对于新任务会进入阻塞队列中(无界的阻塞队列)
适用:执行长期的任务,性能好很多
1.3newCacheThreadExecutor(推荐使用)可缓存线程池
ExecutorService cPool = Executors.newCachedThreadPool();
当线程池大小超过了处理任务所需的线程,那么就会回收部分空闲(一般是60秒无执行)的线程,当有任务来时,又智能的添加新线程来执行。
底层:返回ThreadPoolExecutor实例,corePoolSize为0;maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为60L;unit为TimeUnit.SECONDS;workQueue为SynchronousQueue(同步队列)
通俗:当有新任务到来,则插入到SynchronousQueue中,由于SynchronousQueue是同步队列,因此会在池中寻找可用线程来执行,若有可以线程则执行,若没有可用线程则创建一个线程来执行该任务;若池中线程空闲时间超过指定大小,则该线程会被销毁。
适用:执行很多短期异步的小程序或者负载较轻的服务器
1.4newScheduleThreadExecutor大小无限制的线程池
ExecutorService executorService= Executors.newScheduledThreadPool(10);
支持定时和周期性的执行线程。
底层:创建ScheduledThreadPoolExecutor实例,corePoolSize为传递来的参数,maximumPoolSize为Integer.MAX_VALUE;keepAliveTime为0;unit为:TimeUnit.NANOSECONDS;workQueue为:new DelayedWorkQueue() 一个按超时时间升序排序的队列
通俗:创建一个固定大小的线程池,线程池内线程存活时间无限制,线程池可以支持定时及周期性任务执行,如果所有线程均处于繁忙状态,对于新任务会进入DelayedWorkQueue队列中,这是一种按照超时时间排序的队列结构
适用:周期性执行任务的场景
2.线程池任务执行流程:
1.当线程池小于corePoolSize时,新提交任务将创建一个新线程执行任务,即使此时线程池中存在空闲线程。
2.当线程池达到corePoolSize时,新提交任务将被放入workQueue中,等待线程池中任务调度执行
3.当workQueue已满,且maximumPoolSize>corePoolSize时,新提交任务会创建新线程执行任务
4.当提交任务数超过maximumPoolSize时,新提交任务由RejectedExecutionHandler处理
5.当线程池中超过corePoolSize线程,空闲时间达到keepAliveTime时,关闭空闲线程
6.当设置allowCoreThreadTimeOut(true)时,线程池中corePoolSize线程空闲时间达到keepAliveTime也将关闭
3.一个线程有三个方法,怎么保证三个是顺序执行
第一种方式:
顺序在线程中创建实例(最容易想到的办法)。
public class TestTwo {
static TestTwo t=new TestTwo();
class T1 extends Thread{
@Override
public void run() {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
//T1线程中要处理的东西
System.out.println("T1线程执行")
}
}
class T2 extends Thread{
@Override
public void run() {
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
//T2线程中要处理的东西
System.out.println("T2线程执行");
t.new T1().start();
}
}
class T3 extends Thread{
@Override
public void run() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
//T3线程中要处理的东西
System.out.println("T3线程执行");
t.new T2().start();
}
}
public static void main(String[] args) {
t.new T3().start();
//打印结果如下:
//T3线程执行
//T2线程执行
//T1线程执行
}
}
第二种方式:
看到有人说运用单个线程池(SingleThreadExecutor)来实现,确切的说这里不太符合,从打印结果看出,其实是在一个线程里,执行了三个任务。
Thread t1 = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " run 1");
}
}, "T1");
Thread t2 = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " run 2");
}
}, "T2");
Thread t3 = new Thread(new Runnable() {
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + " run 3");
}
}, "T3");
//三个线程顺序执行 第一种方案,单个线程池 顺序放入执行队列中
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(t3);
executor.submit(t2);
executor.submit(t1);
executor.shutdown();
第三种方式:
thread.Join把指定的线程加入到当前线程,可以将两个交替执行的线程合并为顺序执行的线程。比如在线程B中调用了线程A的Join()方法,直到线程A执行完毕后,才会继续执行线程B。
package com.gs.demo1;
public class ThreadTest1 {
public static void main(String[] args) {
Thread t1 = new Thread(new Work(null),"线程t1");
Thread t2 = new Thread(new Work(t1),"线程t2");
Thread t3 = new Thread(new Work(t2),"线程t3");
t1.start();
t2.start();
t3.start();
}
static class Work implements Runnable{
private Thread beforeThread;
public Work(Thread beforeThread) {
this.beforeThread = beforeThread;
}
@Override
public void run() {
if(beforeThread!=null) {
try {
//某线程调用该方法,会让其他线程处于等待状态,让其运行完毕,再执行其他线程.
beforeThread.join();
System.out.println("Thread start:"+Thread.currentThread().getName());
}catch(Exception e){
e.printStackTrace();
}
}else {
System.out.println("Thread start:"+Thread.currentThread().getName());
}
}
}
}
第四种方式:
使用CountDownLatch
CountDownLatch(闭锁)是一个很有用的工具类,利用它我们可以拦截一个或多个线程使其在某个条件成熟后再执行。它的内部提供了一个计数器,在构造闭锁时必须指定计数器的初始值,且计数器的初始值必须大于0。另外它还提供了一个countDown方法来操作计数器的值,每调用一次countDown方法计数器都会减1,直到计数器的值减为0时就代表条件已成熟,所有因调用await方法而阻塞的线程都会被唤醒。这就是CountDownLatch的内部机制,看起来很简单,无非就是阻塞一部分线程让其在达到某个条件之后再执行。
package com.gs.demo2;
import java.util.concurrent.CountDownLatch;
public class ThreadTest2 {
public static void main(String[] args) {
CountDownLatch c1 = new CountDownLatch(0);//计数器为0
CountDownLatch c2 = new CountDownLatch(1);//计数器为1
CountDownLatch c3 = new CountDownLatch(1);//计数器为1
Thread t1 = new Thread(new Work(c1, c2),"线程t1");
//c1为0,t1线程可以执行。t1线程的计数器 c2 减1
Thread t2 = new Thread(new Work(c2, c3),"线程t2");
//t1的计数器c2为0时,t2才能执行。t2的计数器c3减1
Thread t3 = new Thread(new Work(c3, c3),"线程t3");
//t3的计数器c3为0时,t3才能执行
t1.start();
t2.start();
t3.start();
}
//定义Work线程类,需要传入开始和结束的CountDownLatch参数
static class Work implements Runnable {
private CountDownLatch c1;
private CountDownLatch c2;
public Work(CountDownLatch c1, CountDownLatch c2) {
super();
this.c1 = c1;
this.c2 = c2;
}
@Override
public void run() {
try {
//当某个线程调用CountDownLatch对象的await方法时,将会阻塞,直到计数器的值变成0才放行。
c1.await();
System.out.println("thread start:" + Thread.currentThread().getName());
c2.countDown(); //本线程计数器减 1
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}