本文原文所在:Java多线程小结
使用多线程能让我们更高效率的执行任务,是高并发的实现手段之一,以前基本一直都用的Runnable+Thread进行操作,现今做个小结。
线程的几种状态
大致分为如下几个状态:
- 新建(New),使用 new 创建 Thread 实例后。
- 运行(Runnable),执行了 run() 方法后。
- 等待(wait),执行了wait、sleep等方法。
- 阻塞(blocked),因为锁等原因阻塞。
- 结束(terminated),任务执行完毕。
线程间的状态转换示意图----摘自《深入理解 Java 虚拟机 第2版》
常用的几种实现
- Thread:
- Runnable
- Callable
- Executors线程池
继承Thread类:
多线程编程中,最简单和直接的方式便是继承 Thread 类,重写 run() 方法即可。如下所示:
//继承 Thread 类
public class Threed01 extends Thread
{
@Override
public void run() {
IntStream.range(10,50)
.forEach(a->System.out.print(a+"\t"));
}
}
//主类
public class Main
{
public static void main(String[]args)
{
Threed01 threed01=new Threed01();
threed01.start();
System.out.println("启动完毕");
}
}
实现Runnable接口:
说道 Java 多线程不得不说的就是Runnable接口,应该是接触多线程时最先接触的,该接口是一个函数式接口,只有一个方法run(),不需要参数,也没有返回值,我们在使用上面说的那种模式的时候就需要实现该接口,然后将其作为参数传递以创建 Thread 对象,然后启动线程。
- Runnable接口定义:
@FunctionalInterface
public interface Runnable {
public abstract void run();
}
- 示例:
public class MyRunable implements Runnable
{
@Override
public void run()
{
System.out.println("a thread");
}
}
//then
new Thread(new MyRunable()).start(); //start a thread
//lambda形式
new Thread(()->System.out.println("a thred")).start();
实现Callable :
Callable :
Runnable 接口不能返回值,所以有个与之对应的Callable接口,只有一个 call() 方法,并能返回一个值,返回实现Callable接口时传入的泛型对象。
- Callable 定义:
@FunctionalInterface
public interface Callable<V> {
V call() throws Exception;
}
- 示例:
Callable<String> myCall=()->"hello ,boy!";
FutureTask<String> ft = new FutureTask<>(myCall);
new Thread(ft).start();
System.out.println(ft.get()); //hello ,boy
通过上面的例子可以看出可以通过 Callable 的 call() 方法返回我们需要的值,不过上面这两种方式都需要我们手动创建去new Thread,很麻烦,而且不停的创建和销毁线程也很耗费资源,所以就有了线程池等解决方案。
Executor 线程池框架
1、通过 ExecutorService 创建线程池:
这是一个线程池管理工具,通过它我们可以提交 Runnable 或 Callable 对象。
涉及几大对象:
- Executor 接口:只有一个 execute 方法,参数类型为 Runnable。用于执行线程。
- Executors 类,封装了一系列静态方法,便于使用者轻松、简约的获取线程池,没一个获取线程池的静态方法的返回对象都是 ExecutorService 类型。因为具体实现类都是实现了 AbstractExecutorService 抽象类(实现了 ExecutorService)。
- ExecutorService 接口,继承了 Executor 接口
常用方法如下:
- excute(Runnable run):用于提交Runnable对象.
- submit(Runnable/Callable):用于提交 Runnable、Callable 对象,会返回一个Future对象,我们可以通过它获取 call 的返回值等。
- shutdown():关闭提交,执行后则不能再向其中提交任务,等待已执行的任务运行结束。
- shutdownNow():停止所有任务。
- isTerminated():检测是否执行完所有任务。
示例:
//建立线程池,单线程的
ExecutorService exec= Executors.newSingleThreadExecutor();
//Runnable对象
exec.execute(()->System.out.println("haha")); //输出haha
//Callable
Future<String> future = exec.submit(()->"wanderer");
System.out.println(future.get()); //阻塞直到返回值,输出wanderer
//关闭
exec.shutdown();//无法再提交任务
在上面的示例中,ExecutorService 我们是通过Executors获取的,通过名字我们可以知道获取的是只有一个工作线程的线程池,每个任务都只能依次执行,完成一个后再进行下一项,我们还可以获取别的种类的线程池,如 newCachedThreadPool、newScheduledThreadPool 等,具体详见源码或者api文档。
2、通过 ThreadPoolExecutor 类手动创建线程池:
在上面那种方式的创建中,我们打开 Executos 的源码,可以看到大多数线程池的实现类其实就是我们此处所说的 ThreadPoolExecutor 类,使用 ThreadPoolExecutor 创建线程池是很多大企业的编码规范,因为使用 Executos 创建线程池很有可能导致出现 OOM(内存溢出)。
Executors 创建线程池的部分方法截图:
打开 ThreadPoolExecutor 类,我们可以看到它实现了 AbstractExecutorService 抽象类,该类是 ExecutorService 的一个实现类。此处介绍它的基本使用。
打开源码,可以看到 ThreadPoolExecutor 的一些构造方法,参数emmm,较为多,这也是使用该类进行线程池手动创建的重点所在。该类共4个构造函数,参数最多的共7个,所以就拿它说事情。
//参数最多的构造函数源码
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler)
{
//*******************
}
- corePoolSize:核心线池大小
- maximumPoolSize:最大线程池大小
- keepAliveTime:除去核心线程数以外的线程最大空闲存活时间,也就是说,如果线程池中的线程数超过了核心线程池的大小,那么别的线程如果空闲没事儿做,那么在这个时间过后就会被销毁,以减少系统资源消耗。
- unit:一个枚举量,主要是指明上一个参数的时间单位,例如分钟、秒、小时之类的。
- workQueue:阻塞队列,用于保存工作线程的。例如在核心线程数满了,并且最大线程数没满,那么就将新提交的任务放入该队列;如果队列满了,且没达到最大线程数,那么创建新的线程执行新提交的任务;如果队列满了,并且线程池达到了最大线程数,那么就按参数中设置的拒绝策略来处理新提交的线程。
- threadFactory:线程工厂,主要用于定制化的去创建线程。
- handler:线程池和队列都满了的情况下的拒绝策略。如下,几个策略均是 ThreadPoolExecutor 的静态内部类:
- CallerRunsPolicy:使用调用者所在线程执行任务
- AbortPolicy:直接拒绝任务,并且会抛出异常
- DiscardPolicy:丢弃任务
- DiscardOldestPolicy:丢弃队列中存在时间最长的任务
吧啦吧啦了半天,现在直接来个实例先,如下:
public class Main2
{
public static void main(String[]args) throws InterruptedException {
//在下面的创建中,我们使用的阻塞队列长度为2(改为4,则下方不会拒绝)
//而最大线程池大小为6
//拒绝策略为直接丢弃
//而线程提交数为10,所以运行之后会发现输出只有8个任务的结果
//说明多余的两个被直接丢弃了,具体情况看下方输出
//如果拒绝策略换为直接拒绝,我们可以在输出看到异常
//非核心线程的空闲存活时间为5s
ThreadPoolExecutor poolExecutor=new ThreadPoolExecutor
(4,
6,
5,
TimeUnit.SECONDS,
new ArrayBlockingQueue<>(2),
new ThreadPoolExecutor.DiscardPolicy());
for (int i=0;i<10;i++)
{
int finalI = i;
poolExecutor.execute(()-> {System.out.println("heihei_"+ finalI);
try {
Thread.sleep(1000*5);
} catch (InterruptedException e) {
e.printStackTrace();
}
});
}
while (!poolExecutor.isTerminated())
{
System.out.println("active:"+poolExecutor.getActiveCount()+"\t\tqueen:"+poolExecutor.getQueue().size());
Thread.sleep(1000*2);
}
}
}
//输出
heihei_3
heihei_0
heihei_2
heihei_1
heihei_7
heihei_6
active:6 queen:2
active:6 queen:2
active:6 queen:2
heihei_4
heihei_5
active:2 queen:0
active:2 queen:0
active:0 queen:0
active:0 queen:0
active:0 queen:0
active:0 queen:0
以上就是关于 ThreadPoolExecutor 的一个简要使用介绍,只要线程池创建出来,其使用和 Executors 创建的线程池使用时类似的。