适用场景
像Web服务器、数据库服务器、文件服务器、邮件服务器等服务器应用程序都是处理来自某些远程来源的大量短小的任务。服务器经常出现的情况是:单个任务处理的时间很短而请求的数量巨大。
简单的构建方式
每当一个请求到达就为其创建一个新线程,然后在新线程中为其服务。
优点:适用于原型开发
缺点:用于服务器存在诸多问题,为每个请求创建一个新线程的开销很大;花费在创建和销毁新线程上的时间和资源比花在处理实际的用户请求上的时间和资源更多;除了创建和销毁线程的开销之外,活动的线程也消耗资源,在JVM中创建过多的线程会导致系统过度消耗内存而用完内存或者"切换过度"。
线程池的作用
作用:主要用来解决线程生命周期开销问题和系统资源不足的问题。通过对多个任务重用线程,线程创建的开销就分摊到多个任务上了,而且由于在请求到达时线程已经存在,所以消除了创建线程带来的延迟。
优点:能立即为请求服务,提高了响应速度;可以通过调整线池中线程的数目防止出现资源不足的情况。
缺点:使用线程池构建的应用程序和其他多线程应用程序一样容易遭受并发错误,如同步错误和死锁;还容易遭受特定于线程池的其他少数风险,如与池有关的死锁,资源不足和线程泄漏、请求过载。
并发错误:线程池和其他并发机制依靠外套wait()和notify()方法,如果编码不正确,那么可能丢失通知,导致线程保持空闲状态。
死锁:一般的死锁,满足死锁的四个必要条件;池死锁,线程池中的所以线程都在执行已阻塞的等待队列中另一任务的执行结果的任务,而这个任务因为没有可以占用的线程而无法运行。
资源不足:如果线程池过大,被线程消耗的资源可能会严重影响系统性能;虽然线程之间切换的调度开销很小,但如果有很多线程,那么切换可能会严重地影响程序的性能。
线程泄漏:当从池中除去一个线程以执行任务,而任务结束后线程没有返回线程池。当任务抛出一个RuntimeException或一个Error时,如果池类没有捕获他们,那么线程只会退出线程池大小将永久减一,发生次数过多将导致线程池为空,系统将停止,没有线程可供使用;有些线程会永远等待某些资源或者用户输入,而这些资源不能保证变得可用,或者用户已经离开,如果一个线程永久的消耗着,那么相当于从池中除去,应该要么只给予它们自己的线程,要么只让它们等待有限的时间。
请求过载:请求压垮服务器。可以简单地抛弃请求,依靠更高级别的协议稍后重试请求;也可以用服务器暂时很忙的响应来拒绝请求。
线程池的创建
一、创建单线程的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 创建只有一个线程的线程池
*/
public class SingleThreadPool {
public static void main(String[] args) {
ExecutorService ex = Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
Runnable runnable = () -> System.out.println(Thread.currentThread().getName());
ex.execute(runnable);
}
ex.shutdown();
}
}
二、创建固定数量的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 创建一个可重用的,有固定数量的线程池
* 每次提交一个任务就提交一个线程,直到线程达到线城池大小,就不会创建新线程了
* 线程池的大小达到最大后达到稳定不变,如果一个线程异常终止,则会创建新的线程
*/
public class FixedSizeThreadPool {
private static final int POOL_SIZE = 2;
public static void main(String[] args) {
ExecutorService ex = Executors.newFixedThreadPool(POOL_SIZE);
for (int i = 0; i < 3; i++) {
Runnable runnable = () -> System.out.println(Thread.currentThread().getName());
ex.execute(runnable);
}
ex.shutdown();
}
}
三、创建数量不固定的线程池
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/*
* 具有缓冲功能的线程池,系统根据需要创建线程,线程会被缓冲到线程池中
* 如果线程池大小超过了处理任务所需要的线程线程池就会回收空闲的线程池
* 当处理任务增加时,线程池可以增加线程来处理任务,线程池不会对线程的大小进行限制
* 线程池的大小依赖于操作系统
*/
public class NotFixedThreadPool {
public static void main(String[] args) {
ExecutorService ex = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
Runnable runnable = () -> System.out.println(Thread.currentThread().getName());
ex.execute(runnable);
}
ex.shutdown();
}
}
四、创建定时线程池
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/*
* 创建一个线程池,大小可以设置,此线程支持定时以及周期性的执行任务定时任务
*/
public class ScheduledThreadPool {
private static final int POOL_SIZE = 2;
public static void main(String[] args) {
ScheduledExecutorService ex = Executors.newScheduledThreadPool(POOL_SIZE);
Runnable runnable = () -> System.out.println(Thread.currentThread().getName());
// 参数2:隔多长时间开始执行线程 参数3:执行周期 参数4:时间单位
ex.scheduleAtFixedRate(runnable, 3, 1, TimeUnit.MILLISECONDS);
}
}