线程池(实现线程的第四种方式)
1. 线程是什么
线程池其实就是一个容纳多个线程的容器,其中的线程可以反复使用,省去了频繁创建线程对象的操作。无需反复创建线程而消耗过多的资源
2.使用线程池的优点
**推荐使用线程池来创建线程 **
**1.降低资源消耗:**通过重复利用已创建的线程降低线程创建和销毁带来的消耗
**2.提高响应速度:**当任务到达时,任务可以不需要等待线程创建就能立即执行
**3.提高线程的可管理性: **使用线程池可以统一进行线程分配,调度和监控。
JDK 1.5 之后内置了线程池
两个接口与两个类
3.两大接口
3.1 ExecutorService
普通线程池
提交任务:
void execute(Runnable )
<T> Futer<T> submit (Callable<T> tack || Runnable);
3.2 ScheduledExcutorService
:定时线程池
ScheduledFuture<?> scheduleAtFixedRate(Runnbale command,long initiableDelay,long period,TimeUnit unit);
4. 线程池的工作原理
4.1 线程池工作步骤
1)线程池判断核心线程池里的线程是否都在执行任务。如果不是,则创建一个新的工作线程来执行任务。则创建一个新的工作线程来执行任务。如果核心线程池里的线程都在执行任务,则进入下个流程。
2)线程池判断工作队列是否已满。如果工作没满,则将新提交的任务存储在这个工作队列中。反之进入下一流程。
3)线程池判断池中线程是否都处于工作状态。如果没有,则创建一个新的工作线程来执行任务。
4.1.1 类
Executor框架最核心的类是ThreadPoolExecutor
,它是线程池的工厂类,用来生产线程池
ThreadPoolExecutor:
ExecutorService
的子类
ThreadPoolExecutor(int corePoolSize,//基本线程池大小
int maximumPoolSize,//最大线程池大小
long keepAliveTime,//线程活动保持时间
TimeUnit unit,//线程活动保持时间单位
BlockingQueue<Runnable> workQueue,//阻塞队列
RejectExecutionHandler handler)//饱和策略
实例实现:创建一个线程池
ThreadPoolExecutor threadpoolexecutor = new ThreadPoolExecutor(3,5,2000,TimeUnit.MIlLISECONDS,new LinkBlockingDeque<Runnable>());
4.1.2 参数详解
corePoolSize
: 线程池基本大小
2) maximumPoolSize
: 线程池最大数量:线程池允许创建的最大线程数,若使用无界的任务队列这个参数无意义
3)keepAliveTime
:线程活动保持时间:线程池的工作线程空闲后,保持存活的时间。当任务多,且单个任务耗时短时。可以调大这个参数,提高线程的利用率。
4)TimeUnit
:线程活动保持时间的单位 ,可选单位:天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一秒)、纳秒(NANOSECONDS)
5)RejectedExecutionHandler
:饱和策略 当队列和线程都满了,说明线程处于饱和状态,有以下四种策略:
a:
AbortPoilcy
,直接抛出异常b:
CallerRunPoilcy
:只用调用者所在线程来运行任务。c:
DiscardOldestPoilcy
:丢弃队列中最近的一个任务,并执行当前任务d:
DiscardPoilcy
:不处理,丢弃掉
6)runnableTaskQueue
:任务队列 :用于保存等待执行任务的阻塞队列,一个元素的插入操作,必须要等待同时有一个元素的删除操作,否则插入操作就一直阻塞(反之亦然),简而言之,就是插入和删除操作必须配对执行,常见四个阻塞队列:
1.ArrayBlockingQueue:基于数组的有界阻塞队列
2.LinkedBlockingQueue:基于链表的无界阻塞队列
3.内置线程池
FixedThreadPool
,SingleThreadPool
都采取次队列,synchronousQueue
:一个不存元素的无界阻塞队列4.PriorityBlockingQueue:基于优先级的阻塞队列(优先级队列底层是二叉树);
当阻塞队列为无界的链表时,
FututerTask
:可以保证多线程场景下,任务只会被一个线程执行一次。其他线程不再执行此任务。Future接口中的get()方法会阻塞当前线程,直到取到Callable的返回值。
4.1.3 方法
1)execute():用于提交不需要返回值的任务,所以无法判断任务是否被线程池执行成功
- submit():用于提交需要返回值额度任务。线程池会返回一个future类型的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值,get()方法会阻塞当前线程直到任务完成。重载方法get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完
4.1.4 使用线程池提交任务
(1) 使用 execute()方法
package ThreadPoolTest;
import java.util.concurrent.LinkedBlockingDeque;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
/**
* @Name:创建线程池,并提交任务使用executor()
* @Author:ZYJ
* @Date:2019-06-12-21:18
* @Description:
*/
public class ThreadPoolDemo {
public static void main(String[] args) {
RunnableThread runnableThread = new RunnableThread();
//创建线程池
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,5,2000,
TimeUnit.MILLISECONDS, new LinkedBlockingDeque<Runnable>());
for(int i=0;i<5;i++){
//使用execute()向线程提交任务
threadPoolExecutor.execute(runnableThread);
}
}
}
//实现类实现Runnable接口,覆写run()接口
class RunnableThread implements Runnable{
@Override
public void run() {
for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+","+i);
}
}
}
(2) 使用submit()方法
package ThreadPoolDemo;
import javax.security.auth.callback.Callback;
import java.util.concurrent.*;
/**
* @Name: 创建线程池 并使用submit()向线程提交任务
* @Author:ZYJ
* @Date:2019-06-17-19:48
* @Description:
*/
public class ThreadPoolTest {
public static void main(String[] args) {
ThreadPoolExecutor threadPoolExecutor = new ThreadPoolExecutor(3,
5,2000, TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>());
CallableThread callableThread = new CallableThread();
for(int i=0;i<5;i++){
//实例化 Future对象接收提交的返回值
Future<String> future = threadPoolExecutor.submit(callableThread);
try {
//调用Future类的get方法得到返回值
String str = future.get();
System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
//实现类实现CallableThread接口 覆写call类
class CallableThread implements Callable<String> {
@Override
public String call() throws Exception {
for(int i=0;i<50;i++){
System.out.println(Thread.currentThread().getName()+","+i);
}
return Thread.currentThread().getName()+"任务执行完毕!";
}
}
4.2 关闭线程池
4.2.1 方法 :
1)shutdown():首先将线程池的状态设置为STOP,然后尝试停止所有正在执行或暂停任务的线程,并返回等待执行任务的列表
2)shutdownNow();只是将状态设置为SHUTDOWN状态,然后中断所有没有正在执行的任务的线程
threadPoolExecutor.shutdown();
4.2.2 原理
遍历线程池中的工作线程,逐个调用interrupt()方法来中断线程,所以无法响应中断的任务永远无法停止。
线程池中的线程被包装为Worker工作线程,具备可重复执行任务的能力
4.3 :合理配置线程池
配置核心池以及最大线程池数量:System.out.println(Runtime.getRuntime().availableProcessors());查看处理器数量。
CPU密集型任务(大数运算,) :NCPU+1;
I/O密集型任务:2*NCPU;
5. Executor框架
在java中使用线程来异步执行任务,java线程的创建与销毁需要一定的开销,如果我们为每一个任务来创建一个新的线程来执行,这些线程的创建与销毁将消耗大量的计算资源。同时为每一个任务创建一个新新线程来执行,这种策略可能会使处于高负荷状态的应用最终崩溃。
Java的线程既是工作单元,也是执行机制。从JDK1.5开始,把工作单元与执行机制分离开。工作单元包括Runnable和 Callable,而执行机制由Executor框架提供
5.1 Executor框架的两级调度模型
java线程被一对一映射为本地操作系统线程。java线程启动时,会创建一个本地操作系统线程。当该java线程终止时,这个操作系统线程会被回收。操作系统会调度所有线程并将他们分配给可用的CPU。
在上层,java多线程程序通常应用分为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程;
在底层,操作系统内核将这些线程映射到硬件处理器上。
5.1 .四大内置线程池
Executors-线程池的工具(实现)类 他的核心类是ThreadPoolExecutor
,通过工具类Executors可以创建以下类型的线程池
5.1.1固定大小线程池
运用场景:实用于负载较重的服务器(配置较低),来满足资源分配的要求。可重用
**注意:**线程池若不关闭,会一直开启。使用完的线程会自动把线程归还给线程池,可以继续使用
public static ExecutorService newFixedThreadPool(int nThreads)//静态方法,通过类名调用
该方法的返回接口的实现类对象,使用ExecutorService接口接收
核心线程池大小与最大线程池大小相同,不允许有空闲线程出现,
public static ExecutorService newFixedThreadThreadPool(int nThread){
return new ThreadPoolExecutor(n Thread,nThread,TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
//使用无界队列
}
使用无界队列的影响
1)线程池中的线程数量不超过
corePoolSize
(基本线程池大小)
- 这时最大线程池是一个无效参数
3)线程保持活动时间(keepAliveTime)是一个无效参数
4)运行中的FixedThreadThreadPool(未执行方法shutdown()或shutdownNow()不会拒绝任务)
使用步骤:
1,使用线程池的实现类Executors里提供的静态方法newFixedThreadPool()
生产一个特定线程数量的线程池。
2,创建一个类实现Runnable接口,覆写run()方法,设置线程任务。
3,调用ExecutorService
中的方法submit()方法传递线程任务,开启线程、执行main()方法
4,调用Executor中的方法shutdown()销毁线程池,不建议使用
实例 : FixedThreadPool
的使用
package FixedThreadPoolTest;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/**
* @Name: FixedThreadPool的使用
* @Author:ZYJ
* @Date:2019-06-18-19:38
* @Description:
*/
public class FixedThreadPoolDemo {
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(5);
RunnableImpl runnable = new RunnableImpl();
for(int i=0;i<5;i++){
executorService.submit(runnable);
}
executorService.shutdown();
}
}
class RunnableImpl implements Runnable{
@Override
public void run() {
for(int j=0;j<10;j++){
System.out.println(Thread.currentThread().getName()+"执行任务");
}
}
}
5.1.2 单线程池-只有一个线程
运用场景:当多线程场景下,需要让人物串行执行时采用
public static ExecutorService newSingleThreadExecutor()
5.1.3 缓存线程池 (无大小限制的线程池):
应用场景:适用于负载较轻的服务器,或执行很多短期的异步任务。
public static ExecutorService newCachedThreadPool()
当任务提交速度大于线程执行速度,会不断创建新线程,有可能无限创建线程将内存写满。
当线程的执行速度大于任务提交速度,只会创建若干个有限线程。
5.1.4 定时调度池
public static ExecutorService newScheduledThreadPool()
executorService.schedule(Runnable command,long delay,TimeUnit unit);
延迟delay个时间单元后创建nThreads个线程执行command任务。
scheduleAtFixedRate(Runnbale command,long initialDelay,long period,TimeUnit unit)s
延迟delay个单元后每隔period单元时间就执行一次command任务
5.2 扩展问题
JDK1.7 任务分工Fork/Join 单机版的MapReduce
JDK1.8 StampedLock
性能优于ReentrantReadWriteLock
JUC:并发工具类
CountDownLatch
闭锁
CyclicBarrier
循环栅栏
Semophore
信号量Exchanger 交换器