为了发挥计算(多核)能力,就需要使用多线程;但线程本身也会占用部分资源;为了降低线程本身创建、销毁引起的开销,需要使用线程池来管理线程。
多线程
java中有几种实现多线程的方式:
- 继承Thread类
- 实现Runnable接口
- 实现Callable接口
- 使用线程池
继承Thread类
Thread类实现了Runnable接口,并定义一些重要属性:
- name:线程名称;
- priority:优先级,越大被优先调度的可能性越高(最大值为10,最小值为1,默认值为5);
- daemon:是否是后台线程;
继承Thread后,重写run方法,然后通过start类执行线程。
public class ThreadTest extends Thread {
@Override
public void run() {
System.out.println(getName() + ": in thread test");
}
public static void main(String[] args) {
for(int i=0; i<10; i++) {
ThreadTest thr = new ThreadTest();
thr.setName("thread-" + i);
thr.start();
}
}
}
实现Runnable接口
通过Runnable接口需要:
- 创建一个实现Runnable接口的类;
- 实现run方法;
- 创建类对象作为Thread的参数创建线程,然后调用start启动线程;
Thread thr = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("in runnable");
}
});
thr.start();
实现Callable接口
Callable可以在任务结束时获取一个返回值,并且可以抛出异常(Runnable不能):
- 实现Callable接口(需提供一个类型);
- 重写call方法(返回类型为上一步提供的类型);
- 创建ExecutorService服务;
- 创建任务对象,并提交;
- 通过get获取执行结果;
public class CallableTest {
static class CallThread implements Callable<String>{
@Override
public String call() throws Exception {
return "In callable";
}
}
public static void main(String[] args) throws ExecutionException, InterruptedException {
ExecutorService exeService = Executors.newFixedThreadPool(1);
CallThread callThr = new CallThread();
Future<String> futCall = exeService.submit(callThr);
String result = futCall.get();
System.out.println(result);
exeService.shutdown();
}
}
线程池
线程池的优势:
- 节省资源开销:重复利用线程池中线程;
- 提升对线程的管理:统一对线程分配与监控,避免了无节制地创建线程;
- 提供相应、减低系统开销;避免了每次的创建与销毁的开销;
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-fRpheslh-1617549498446)(E:\xugd\Ebooks\FangCloudV2\个人文件\EBooks-技术_编程语言\Blog(Markdown)]\pictures\Java\threadpool-mainflow.png)
几种常见线程池
Executor提供了几种常用工厂方法创建ExecutorService:
- FixedThreadPool(
newFixedThreadPool
):线程池大小固定,队列(LinkedBlockingQueue
)无界; - SingleThreadExecutor(
newSingleThreadExecutor
):线程池大小固定为1,队列(LinkedBlockingQueue
)无界; - CachedThreadPool(
newCachedThreadPool
):线程池无限大(MAX INT),等待队列(SynchronousQueue
)长度为1;
ThreadPoolExecutor
ThreadPoolExecutor是用于处理异步任务的接口,可理解为一个线程池加一个任务队列;提交到ExecutorService的任务会被放入队列中或直接被线程池中线程执行。
public ThreadPoolExecutor(int corePoolSize, //核心线程数量
int maximumPoolSize, //最大线程数量
long keepAliveTime, //线程KeepAlive时间,当线程池数量超过核心线程数量以后,idle时间超过这个值的线程会被终止
TimeUnit unit, //线程KeepAlive时间单位
BlockingQueue<Runnable> workQueue, //任务队列
ThreadFactory threadFactory, //创建线程的工厂对象
RejectedExecutionHandler handler) //任务被拒绝后调用的handler
任务队列
存放被添加到线程池中,但尚未被执行的任务;它一般分为直接提交队列、有界任务队列、无界任务队列、优先任务队列几种;
- 直接提交队列:SynchronousQueue是一个特殊的BlockingQueue,它没有容量;每执行一个插入操作就会阻塞,需要执行一个删除操作才会被唤醒。
- 有界的任务队列:可以使用ArrayBlockingQueue实现;有新的任务需要执行时,线程池会创建新的线程,直到创建的线程数量达到corePoolSize时,则会将新的任务加入到等待队列中;若等待队列已满(即超过ArrayBlockingQueue初始化的容量),则继续创建线程,直到线程数量达到maximumPoolSize设置的最大线程数量,若大于maximumPoolSize,则执行拒绝策略。
- 无界任务队列:可以使用LinkedBlockingQueue(不指定容量时)实现;当线程池的线程数达到corePoolSize后,就不会再增加了;若后续有新的任务加入,则直接进入队列等待。
- 优先任务队列:通过PriorityBlockingQueue实现,它其实是一个特殊的无界队列,它其中无论添加了多少个任务,线程池创建的线程数也不会超过corePoolSize的数量。
拒绝策略
一般创建线程池时,为防止资源被耗尽,任务队列都会选择创建有界任务队列;此模式下如果出现任务队列已满且线程池创建的线程数达到设置的最大线程数时,这时就需要RejectedExecutionHandler参数来设置合理的拒绝策略:
- AbortPolicy:该策略会直接抛出异常,阻止系统正常工作;
- CallerRunsPolicy:如果线程池的线程数量达到上限,该策略会把任务队列中的任务放在调用者的当前线程中运行;
- DiscardOledestPolicy:该策略会丢弃任务队列中最老的一个任务;
- DiscardPolicy:该策略会默默丢弃任务,不予任何处理。
ThreadFactory
线程池中的线程是由ThreadFactory来创建的,通过自定义ThreadFactory,可以按需要对线程池中创建的线程进行一些特殊的设置,如命名、优先级等。
自定义示例
通过setRejectedExecutionHandler可以设定拒绝策略;使用LinkedBlockingQueue(10)设定容量为10的任务队列;并通过自定义线程工厂来生成线程。
public class PoolExecutorTest {
private static ExecutorService _poolPost;
private int ThreadIndex = 0;
public void initPool() {
ThreadPoolExecutor executor = new ThreadPoolExecutor(2, 5,
1, TimeUnit.MINUTES,
new LinkedBlockingQueue<>(10),
//Executors.defaultThreadFactory()
new ThreadFactory() {
@Override
public Thread newThread(Runnable r) {
System.out.println("To create thread: " + (++ThreadIndex));
Thread thr = new Thread(r, "Thread-" + ThreadIndex);
return thr;
}
}
);
executor.setRejectedExecutionHandler(
new RejectedExecutionHandler() {
@Override
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
Runnable old = e.getQueue().poll();
System.out.println("Too many queue-stats, remove the oldest: " + r);
e.execute(r);
}
}
}
);
_poolPost = executor;
}
public void addTask(Runnable r){
_poolPost.submit(r);
}
public static void main(String[] args) {
PoolExecutorTest pool = new PoolExecutorTest();
pool.initPool();
for(int i=0;i <18; ++i){
pool.addTask(new Runnable() {
int nIndex = 0;
public Runnable setIndex(int i){
nIndex = i;
return this;
}
@Override
public void run() {
System.out.println("run start: " + nIndex + ", by " + Thread.currentThread().getName());
try{
Thread.sleep(1000);
}
catch (Exception ex){}
System.out.println("run stop: " + nIndex);
}
}.setIndex(i));
}
}
}
任务的执行情形如下:
- 添加0、1任务时,会创建线程(核心数量决定)直接执行任务;
- 添加2~11任务时,会被放入队列中,等待执行;
- 添加12~14任务时,会创建新的线程(最大线程数量决定)直接执行;
- 添加15~ 17任务时,会从队列中丢弃最先放入的(2~4)任务,然后把新任务添加到队列中。