博客源码:here
〇、多线程和线程池
1.多线程的实现方式
(1)继承Thread类
package thread;
public class Mythread extends Thread{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" mythread...");
}
public static void main(String[] args) {
Thread t1=new Mythread();
Thread t2=new Mythread();
Thread t3=new Mythread();
t1.start();
t2.start();
t3.start();
}
}
(2)实现Runnable接口
package thread;
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+" mythread...");
}
public static void main(String[] args) {
MyRunnable r=new MyRunnable();
new Thread(r).start();
new Thread(r).start();
new Thread(r).start();
}
}
(3)实现Callable接口
package thread;
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class MyCallable implements Callable{
@Override
public String call() throws Exception {
System.out.println(Thread.currentThread().getName()+" mythread...");
return "success";
}
public static void main(String[] args) {
MyCallable myCallable=new MyCallable();
FutureTask<String> futureTask=new FutureTask<String>(myCallable);
new Thread(futureTask).start();
//获取执行结果
try {
String str=futureTask.get();//会调用call()的执行结果
System.out.println("执行结果:"+str);
} catch (Exception e) {
e.printStackTrace();
}
}
}
三种实现方式的区别
-
实现Runnable/Callable vs 继承Thread:
优点: 线程类只是实现了Runnable接口或Callable接口,还可以继承其他类,而继承Thread的方式不能再继承其他类。在这种方式下,多个线程可以共享同一个 target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。
缺点: 编程稍微复杂,如果要访问当前线程,则必须使用Thread.currentThread()方法;而继承Thread的方式不仅编程简单,直接使用 this 即可获得当前线程。 -
实现Runnable vs 实现Callable:
Callable的任务执行后可返回值,而Runnable的任务是不能返回值的;
Callable的方法可以抛出异常,Runnable的方法不可以;
运行Callable任务可以拿到一个Future对象,表示异步计算的结果,通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。
2.为什么使用线程池
- 线程池能够对线程进行统一分配,调优和监控:
降低资源消耗(线程无限制地创建,然后使用完毕后销毁)
提高响应速度(无须创建线程)
提高线程的可管理性
一、线程池基本工作原理
核心实现类ThreadPoolExecutor
(1)参数
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
- int corePoolSize: 线程池中的核心线程数
- int maximumPoolSize: 线程池中允许的最大线程数
- long keepAliveTime: 线程空闲时的存活时间
- TimeUnit unit: 上一个参数的时间单位
- BlockingQueue<Runnable> workQueue: 等待队列的类型,包括了ArrayBlockingQueue(基于数组结构的有界阻塞队列)、LinkedBlockingQueue(基于链表结构的阻塞队列)、SynchronousQueue(相当于限定大小为一的等待队列)、PriorityBlockingQueue(具有优先级的无界阻塞队)。
- ThreadFactory: 创建线程的工厂,通过自定义的线程工厂可以给每个新建的线程设置一个具有识别度的线程名。默认为DefaultThreadFactory。
- RejectedExecutionHandler handler: 拒绝策略,包括了AbortPolicy(直接抛出异常,默认策略)、CallerRunsPolicy(用调用者所在的线程来执行任务)、DiscardOldestPolicy(丢弃阻塞队列中靠最前的任务,并执行当前任务)、DiscardPolicy(直接丢弃任务)。也可以根据应用场景实现RejectedExecutionHandler接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务。
(2)继承层次
-
Executor接口:
Executor 提供了一种思想:将任务提交和任务执行进行解耦。 用户只需要提交任务,提供 Runnable 对象,将任务的运行逻辑提交到执行器(Executor)中;至于任务如何执行、调度,由Executor进行处理。 -
ExecutorService接口:
增加了一些能力: 扩展了可异步跟踪执行任务生成返回值 Future 的方法,如 submit() 等方法。提供了管控线程池生命周期的方法,如 shutDown(),shutDownNow() 等。 -
AbstractExecutorService抽象类:
将执行任务的流程串联了起来, 保证下层的实现只需关注一个执行任务的方法即可。 -
ThreadPoolExecutor:
实现最复杂的运行部分 ,一方面维护自身的生命周期,另一方面同时管理线程和任务,使两者良好的结合从而执行并行任务。
工作原理
二、线程池分类
通过调用 Executors类中的静态工厂方法可创建不同的线程池 ,这些线程池的内部实现原理都是相同的,仅仅是使用了不同的工作队列或线程池大小。
注意:实际上并不推荐通过Executors类来创建线程池,而要通过ThreadPoolExecutor方法自定义创建,原因是Executors类提供的几种线程池有不同的短板,容易引起OOM、浪费资源的问题;而且这几种线程池把参数限定了,缺少了灵活性。
newFixedThreadPool
固定大小的线程池:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
- 缺点:
线程空闲了也不会被销毁,带来资源的浪费;
由于使用了无界队列, 所以FixedThreadPool永远不会拒绝, 即饱和策略失效,有可能会出现OOM问题。
newSingleThreadExecutor
大小为1的线程池:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
- 缺点:
线程空闲了也不会被销毁,带来资源的浪费;
由于使用了无界队列, 所以FixedThreadPool永远不会拒绝, 即饱和策略失效,有可能会出现OOM问题。
newCachedThreadPool
线程池的线程数可达到Integer.MAX_VALUE,线程空闲一定时间后会被销毁,使用SynchronousQueue等待队列,不会出现OOM。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
三、线程池的使用
1.提交任务
(1)execute()
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ExecuteTest {
public static void main(String[] args) {
//创建一个线程池
ThreadPoolExecutor pool=new ThreadPoolExecutor(3, 3, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
pool.execute(new Task1());
pool.execute(new Task2());
pool.execute(new Task3());
pool.shutdown();
}
}
class Task1 implements Runnable{
@Override
public void run() {
System.out.println("正在执行Task1");
}
}
class Task2 implements Runnable{
@Override
public void run() {
System.out.println("正在执行Task2");
}
}
class Task3 implements Runnable{
@Override
public void run() {
System.out.println("正在执行Task3");
}
}
(2)submit()
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class SubmitTest {
public static void main(String[] args) throws ExecutionException, InterruptedException{
//创建一个线程池
ThreadPoolExecutor pool=new ThreadPoolExecutor(3, 3, 60, TimeUnit.SECONDS, new LinkedBlockingQueue<>());
//submit一个callable任务
Future<String> future=pool.submit(new Task(1));
//获取callable任务执行结果
while(!future.isDone()){
System.out.println("获取执行结果:"+future.get());
}
pool.shutdown();
}
}
class Task implements Callable<String>{
private int id;
public Task(int id){
this.id=id;
}
@Override
public String call() {
System.out.println("正在执行Task id:"+id);
return "success";
}
}
2.定时任务及周期性任务
使用TimerTask和Timer实现周期定时任务
创建一个定时任务,每隔2s执行一次。
package schedule;
import java.text.SimpleDateFormat;
import java.util.*;
public class TimerTest {
public static final SimpleDateFormat FORMAT=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
//创建一个TimerTask
TimerTask timerTask=new TimerTask() {
@Override
public void run() {
System.out.println("执行任务:"+FORMAT.format(new Date()));
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
};
//创建Timer执行周期定时任务
System.out.println("现在是:"+FORMAT.format(new Date()));
Timer timer=new Timer();
//从现在开始,每间隔2s执行一次timerTask
timer.schedule(timerTask,new Date(), 2000);
}
}
- 缺点:
①如果有多个TimerTask对象,Timer对象执行它们是在同一个线程里,会导致定时任务不准确。
②Timer线程并不捕获异常,所有TimerTask抛出的未检查的异常都会终止Timer线程。
ScheduledThreadPoolExecutor的替换Timer
使用ScheduledThreadPoolExecutor替换Timer可以解决单线程和异常导致终止的问题:
package schedule;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ScheduledThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
public class ScheduledThreadPoolExecutorTest {
public static final SimpleDateFormat FORMAT=new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");
public static void main(String[] args) {
//创建多个TimerTask
TimerTask timerTask1=new TimerTask() {
@Override
public void run() {
System.out.println("执行任务1:"+FORMAT.format(new Date()));
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
};
TimerTask timerTask2=new TimerTask() {
@Override
public void run() {
System.out.println("执行任务2:"+FORMAT.format(new Date()));
try{
Thread.sleep(3000);
}catch(InterruptedException e){
e.printStackTrace();
}
}
};
//创建ScheduledThreadPoolExecutor执行周期定时任务
System.out.println("现在是:"+FORMAT.format(new Date()));
ScheduledThreadPoolExecutor pool=new ScheduledThreadPoolExecutor(2);
pool.scheduleAtFixedRate(timerTask1, 0, 1000, TimeUnit.MILLISECONDS);
pool.scheduleAtFixedRate(timerTask2, 0, 2000, TimeUnit.MILLISECONDS);
}
}
3.关闭线程池
(1)线程池的状态
(2)如何正确关闭线程池
代码粘贴自here
//调用shutdown()方法关闭线程池
pool.shutdown();
try{
//等待60秒
if (!pool.awaitTermination(60, TimeUnit.SECONDS)){
//调用shutdownNow取消正在执行的任务
pool.shutdownNow();
//再次等待60秒,如果还未结束,可以再次尝试,或者直接放弃
if(!pool.awaitTermination(60, TimeUnit.SECONDS)){
System.err.println("线程池任务未正常执行结束");
}
}
}catch(InterruptedException ex){
//重新调用shutdownNow
pool.shutdownNow();
}