1.1 线程池的引入
1.1.1 不使用线程池
/*
不使用线程池
*/
public class ThreadPoolDemo1 {
public static void main(String[] args) throws InterruptedException {
// 创建减法计数器
CountDownLatch downLatch = new CountDownLatch(10000);
// 获取当前系统时间的毫秒值
long start = System.currentTimeMillis();
// 1. 创建线程任务
Runnable task = new Runnable() {
@Override
public void run() {
System.out.println("任务执行了...");
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 每当执行完一个线程任务,减法计数器减1
downLatch.countDown();
}
};
// 2. 创建线程
for (int i = 0; i < 10000; i++) {
new Thread(task).start();
}
// 当减法计数器中的值减少到0之前,就会处于阻塞状态
downLatch.await();
long end = System.currentTimeMillis();
System.out.println("10000个线程的新建,运行,死亡所花时间"+(end - start));// 570
}
}
运行结果如下图:
1.1.2 使用线程池
/*
使用线程池
*/
public class ThreadPoolDemo2 {
public static void main(String[] args) throws InterruptedException {
// 1. 创建线程池对象
ExecutorService threadPool = Executors.newFixedThreadPool(10);
// 2.创建减法计数器
CountDownLatch downLatch = new CountDownLatch(10000);
// 3. 获取当前系统所花时间
long start = System.currentTimeMillis();
// 4. 开启10000个线程
for (int i = 0; i < 10000; i++) {
threadPool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"执行中...");
downLatch.countDown();
}
});
}
// 等待一万个线程执行完毕之后,关闭线程池
threadPool.shutdown();
// 当减法计数器中的值减少到0之前,就会处于阻塞状态
downLatch.await();
// 获取系统当前时间
long end = System.currentTimeMillis();
System.out.println("10000个线程的新建,运行,死亡所花时间"+(end - start));// 570
}
}
运行结果如下图所示:
.1.3 案例结果
使用线程池的效果比没使用线程池的效果完全不在一个数量级,为什么呢,上面那东西是啥呢,那么接下来我们继续
1.2 线程池的概念
在前面的章节中,我们使用线程的时候就去创建一个线程,这样实现起来非常简便,但是就会有一个问题:
如果并发的线程数量很多,并且每个线程都是执行一个时间很短的任务就结束了,这样频繁创建线程就会大大降低系统的效率,因为系统启动一个新线程的成本是比较高的,它涉及到与操作系统的交互,频繁创建线程和销毁线程都需要时间。
在这种情况下,使用线程池可以很好的提供性能,尤其是当程序中需要创建大量生存期很短暂的线程时,更应该考虑使用线程池。
线程池在系统启动时即创建大量空闲的线程,程序将一个Runnable对象传给线程池,线程池就会启动一条线程来执行该对象的run方法,当run方法执行结束后,该线程并不会死亡,而是再次返回线程池中成为空闲状态,等待执行下一个Runnable对象的run方法。
除此之外,使用线程池可以有效地控制系统中并发线程的数量,但系统中包含大量并发线程时,会导致系统性能剧烈下降,甚至导致JVM崩溃。而线程池的最大线程数参数可以控制系统中并发的线程不超过此数目。
合理利用线程池能够带来三个好处:
-
降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
-
提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。
-
提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
在JDK1.5之前,开发者必须手动的实现自己的线程池,从JDK1.5之后,Java内支持线程池。与多线程并发的所有支持的类都在java.lang.concurrent包中,我们可以使用里面的类控制多线程的执行。
多线程图解:
1.3 Runnable与Callable详解
我们目前已经学习了创建线程的两种方式,一种是直接继承Thread类,另外一种就是实现Runnable接口。
但是这两种方式都有一个缺陷就是:在执行完任务之后无法获取执行结果,并且在run方法中不允许抛出异常。
接下来,我们就深入的学习一下Runnable接口与Callable接口。
1.3.1 Runnable接口
先说一下java.lang.Runnable吧,它是一个接口,在它里面只声明了一个run()方法,在run()方法中封装的都是线程任务。
public interface R