获取多线程的多种方式
Java获取多线程的方式总共有四种,在Jdk5之前只有两种,Jdk5之后新增到了四种。
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 从线程池中获取
1.继承Thread类
资源类继承Thread类,重写父类的run方法。
class MyThread extends Thread{
@Override
public void run() {
// TODO Auto-generated method stub
super.run();
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyThread());
thread.start();
}
2.实现Runnable接口
资源类实现Runnable接口,实现接口中的run方法。
class MyThread1 implements Runnable{
@Override
public void run() {
// TODO Auto-generated method stub
}
}
public static void main(String[] args) {
Thread thread = new Thread(new MyThread1());
thread.start();
}
3.实现Callable接口
通过Jdk可以查看Thread类的构造方法,发现根本没有入参是Callable类的,那么需要如何才能使用呢?
我们可以通过Java多态的思想,找到一个类既能关联Callable又能关联Runnable。
可以发现Runnable接口下有一个FutureTask实现类。
FutureTask的构造方法中就包含了我们需要使用到的Callable接口。
FutureTask在线程启动时,会创建一个新的线程与主分支线程分开,在底层进行call方法里的操作得到结果,在主线程中
获取结果可以通过
futureTask.get()
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 1024;
}
}
public static void main(String[] args) throws InterruptedException, ExecutionException {
//创建FutureTask对象
FutureTask futureTask = new FutureTask<>(new MyThread2());
//创建线程
new Thread(futureTask, "A").start();
//获取计算结果
System.out.println(futureTask.get());
}
那么使用FutureTask相比于实现Runnable接口有什么区别呢?
众所周知,在多线程环境中,最不希望就是见到线程阻塞。
Runnable
在实现Runnable接口的情况下,如果多个线程中有一个线程执行时间过长时,整个程序就会阻塞住,只有等到线程执行完才能继续执行。
FutureTask
在使用FutureTask的情况下,如果多个线程中有一个线程执行时间过长时,FutureTask底层会启动一个新线程去执行,与主线程并行执行,等到执行完毕后,从新线程中得到执行结果与主线程结果合并即可。
4.从线程池中获取
线程池是一种池化技术,在以前的学习中,肯定也了解过c3p0与dbcp,这两个是数据库连接池,也是一种池化技术。
原理都类似,将事前创建好的多个线程放入连接池中,每次有线程访问都通过线程池获取线程,使用完毕后归还,避免了创建和关闭线程的开销。
在Jdk的api文档中,Executor接口是线程池最顶级的父级接口,其定义了一个接收Runnable对象的方法executor。
其下还有ExecutorService子接口,一般使用的是ExecutorService接口,因为子接口的功能比父接口多,其提供了生命周期管理的方法,返回Future对象,以及可跟踪一个或多个异步任务执行状况返回Future的方法
有接口就肯定有实现类,Jdk中提供了Executors类,其本质就是new了一个ThreadPoolExecutor对象。
常用的构造方法
static ExecutorService newFixedThreadPool(int nThreads)
创建一个线程池,该线程池重用固定数量的从共享无界队列中运行的线程。
static ExecutorService newSingleThreadExecutor()
创建一个使用从无界队列运行的单个工作线程的执行程序。
static ExecutorService newCachedThreadPool()
创建一个根据需要创建新线程的线程池,但在可用时将重新使用以前构造的线程(可扩容)。
但是阿里巴巴开发手册中提到:【强制】线程池不允许使用 Executors 去创建。
Executors 返回的线程池对象的弊端如下:
FixedThreadPool、SingleThreadExecutor:允许的请求阻塞队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM;
我们来看看该方法的源码,可以看出方法调用了ThreadPoolExecutor方法。
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
可以看出他们都有一个相同的LinkedBlockingQueue队列,而这个阻塞队列就是罪魁祸首,默认长度为Integer.MAX_VALUE。
new LinkedBlockingQueue()
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
而CachedThreadPool由于是可伸缩的线程池,其长度是不固定的,所以在初始化的时候使用Integer.MAX_VALUE,并不好。
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
线程池的7大参数
ThreadPoolExecutor方法
在ThreadPoolExecutor方法中调用了对象本身的构造函数,我们再来看看。
- corePoolSize:线程池中的常驻核心线程数
- maximumPoolSize:线程池中能容纳同时执行的最大线程数,必须大于等于1
- keepAliveTime:多余的空闲线程存活时间
- unit:keepAliveTime的时间单位
- workQueue:提交但未被执行的任务队列(阻塞队列)
- threadFactory:线程池中生成工作线程的线程工厂
- handler:拒绝策略
拒绝策略
- CallerRunsPolicy - 当触发拒绝策略,只要线程池没有关闭的话,则使用调用线程直接运行任务。一般并发比较小,性能要求不高,不允许失败。但是,由于调用者自己运行任务,如果任务提交速度过快,可能导致程序阻塞,性能效率上必然的损失较大
- AbortPolicy - 丢弃任务,并抛出拒绝执行 RejectedExecutionException 异常信息。线程池默认的拒绝策略。必须处理好抛出的异常,否则会打断当前的执行流程,影响后续的任务执行。
- DiscardPolicy - 直接丢弃,其他啥都没有
- DiscardOldestPolicy - 当触发拒绝策略,只要线程池没有关闭的话,丢弃阻塞队列 workQueue 中最老的一个任务,并将新任务加入