引子
最近在学习Netty的过程中,发现NioEventLoop中涉及到很多java线程池相关的概念,为了能更好的理解这些内容,为此在回过头来专门去了解线程池的相关内容,在学习过程中,顺便在这篇博客中做下记录。
参考资料
- 《Java 并发编程:核心方法与框架》(这本书主要讲线程池相关的API)
- 《Java多线程编程核心技术》(这本书主要讲线程相关的API,这两本书都是一个作者写的,优点在于例子多,讲的浅显易懂,后续将会针对这本书做些阅读后的体会)
线程池
在JDK5中提供了线程池的支持,来支持高并发的访问处理,对线程对象进行复用。在线程池ThreadPool中支持线程对象管理(创建和销毁),使用池时只需要执行具体的任务即可,线程对象的处理都在池中被封装了。
Executor接口
- 与线程池相关的大部分类都基于此接口实现。它只有一个execute方法。
- ExecutorService是Executor的子接口,添加了一些其他方法定义,但还是不能实例化,它的唯一实现类为AbstractExecutorService,是个抽象类。
- AbstractExecutorService抽象类的子类有ThreadPoolExecutor类。
- 实现类ThreadPoolExecutor可进行实例化进而使用相关功能。
这个接口完整的继承关系如下图所示:
image.png
使用Executors工厂类创建线程池
上面讲到ThreadPoolExecutor作为接口实现类,但使用起来不是很方便,考虑到参数传递,线程并发数等参数设置,官方建议使用Executors工厂类来创建线程池对象。下面就来讨论Executors这个工厂类的一些常用API。
- newCachedThreadPool() 创建一个无界线程池。无界指的是线程池中的线程数量最大值为
Integer.MAX_VALUE(很大)。
ExecutorService executorService = Executors.newCachedThreadPool();
for
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("run!");
}
});
上述示例执行execute方法后从线程池中创建一个新线程。
- 线程池可以异步创建多个线程,示例如下:
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("run A!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end A!");
}
});
executorService.execute(new Runnable() {
@Override
public void run() {
System.out.println("run B!");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("end B!");
}
});
上述线程A和B几乎是异步同时创建,结果如下:
run A!
run B!
end A!
end B!
- 线程池对创建的线程对象支持复用。下面是个具体的例子:
public class Server {
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
for(int i=0;i<5;i++){
executorService.execute(new MyRunnable(Integer.toString(i)));
}
Thread.sleep(1000);
System.out.println("");
for(int i=0;i<5;i++){
executorService.execute(new MyRunnable(Integer.toString(i)));
}
}
}
public class MyRunnable implements Runnable {
private String username;
public MyRunnable(String username){
this.username = username;
}
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + " username="+ username + " begin" + System.currentTimeMillis());
System.out.println(Thread.currentThread().getName() + " username="+ username + " end" + System.currentTimeMillis());
}
}
上述示例中使用了分别两次创建了5个线程,如果线程池没有复用线程对象,那么实际打印处理应该有10个不一样的线程,而实际打印结果显示最终很多线程名称是重复的,即先后创建的线程是支持复用的。
pool-1-thread-3 username=2 begin1541329549485
pool-1-thread-3 username=2 end1541329549485
pool-1-thread-2 username=1 begin1541329549485
pool-1-thread-1 username=0 begin1541329549485
pool-1-thread-1 username=0 end1541329549485
pool-1-thread-4 username=3 begin1541329549485
pool-1-thread-4 username=3 end1541329549485
pool-1-thread-2 username=1 end1541329549485
pool-1-thread-5 username=4 begin1541329549485
pool-1-thread-5 username=4 end1541329549485
- 使用
Executors.newCachedThreadPool(ThreadFactory);
定制线程工厂,例如:
ExecutorService executorService = Executors.newCachedThreadPool(MythreadFactory);
其中MythreadFactory是ThreadFactory的实现类,实现的newThread()方法中可以对创建的线程进行定制,比如给线程名称加前缀等。
- 除了使用
Executors.newCachedThreadPool();
方法创建线程池外,还可以使用Executors.newFixedThreadPool(int);
创建有界的指定大小的线程池。 -
Executors.newFixedThreadPool(int,ThreadFactory);
创建有界的指定大小的线程池时,也可以对线程池进行定制 - 另外还可以使用
Executors.newSingleThreadExecutor();
方法创建单一线程池,即线程池中只有一个线程可以使用。示例如下:
ExecutorService executorService = Executors.newSingleThreadExecutor();
for(int i=0;i<3;i++){
executorService.execute(new MyRunnable(Integer.toString(i)));
}
这种情况下,三个execute内部执行的任务是由同一个线程完成的,所有任务是以队列的方式执行的。
输出结果如下:
pool-1-thread-1 username=0 begin1541330285203
pool-1-thread-1 username=0 end1541330285203
pool-1-thread-1 username=1 begin1541330285203
pool-1-thread-1 username=1 end1541330285203
pool-1-thread-1 username=2 begin1541330285203
pool-1-thread-1 username=2 end1541330285203
```。
总结:
上面讲到了使用Executors工厂类的newXXXThreadPool()方法来创建线程池,这些方法内部最终都是实例化了ThreadPoolExecutor方法,代码如下:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
网友评论