1.进程与线程
1.1.进程线程概念与比较
进程
:操作系统(OS)中一个程序的执行周期称为一个进程
线程
:进程中的一个任务就称为一个线程,一个进程中包含N个线程
序号 | 进程 | 线程 |
---|---|---|
1. | 进程是资源分配的最小单位 | 线程是程序执行的最小单位 |
2. | 进程有自己的独立地址空间 ,每启动一个进程,系统就会为它分配地址空间,建立数据表来维护代码段、堆栈段和数据段,这种操作非常昂贵 | 线程是共享进程中的数据 的,使用相同的地址空间 ,因此CPU切换一个线程的花费远比进程要小很多 ,同时创建一个线程的开销也比进程要小很多 |
3. | 进程之间的通信需要以通信的方式(IPC,进程间通信 )进行 | 线程之间的通信更方便,同一进程下的线程共享 全局变量、静态变量等数据 |
4. | 多进程程序更健壮 ,一个进程死掉并不会对另外一个进程造成影响 ,因为进程有自己独立的地址空间 | 多线程程序只要有一个线程死掉,整个进程也死掉了 ,那么进程内其他线程也就死掉了 |
1.2.线程状态
2.Java多线程的实现
2.1.继承Thread类实现多线程
2.1.1.正确启动多线程
java.lang.Thread是一个线程操作的核心类
,新建一个线程最简单的方法就是直接继承Thread类,而后覆写该类中的run()方法
,示例如下:
class MyThread extends Thread{
private String title ;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.title + ",i = " + i);
}
}
}
此时去调用run()方法:
class MyThread extends Thread{
private String title ;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.title + ",i = " + i);
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("thread1") ;
MyThread myThread2 = new MyThread("thread2") ;
MyThread myThread3 = new MyThread("thread3") ;
myThread1.run();
myThread2.run();
myThread3.run();
}
}
//输出
//thread1,i = 0
//thread1,i = 1
//thread1,i = 2
//thread1,i = 3
//thread1,i = 4
//thread1,i = 5
//thread1,i = 6
//thread1,i = 7
//thread1,i = 8
//thread1,i = 9
//thread2,i = 0
//thread2,i = 1
//thread2,i = 2
//thread2,i = 3
//thread2,i = 4
//thread2,i = 5
//thread2,i = 6
//thread2,i = 7
//thread2,i = 8
//thread2,i = 9
//thread3,i = 0
//thread3,i = 1
//thread3,i = 2
//thread3,i = 3
//thread3,i = 4
//thread3,i = 5
//thread3,i = 6
//thread3,i = 7
//thread3,i = 8
//thread3,i = 9
很明显此时只是做了一个打印顺序,和多线程没有关系,实际上,线程启动一律调用Thread类的public synchronized void start()方法
,直接调用run()方法其实就相当于调用了一个普通方法,正确启动多线程示例如下:
class MyThread extends Thread{
private String title ;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println(this.title + ",i = " + i);
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("thread1") ;
MyThread myThread2 = new MyThread("thread2") ;
MyThread myThread3 = new MyThread("thread3") ;
myThread1.start();
myThread2.start();
myThread3.start();
}
}
//输出
//thread1,i = 0
//thread3,i = 0
//thread2,i = 0
//thread2,i = 1
//thread2,i = 2
//thread3,i = 1
//thread1,i = 1
//thread1,i = 2
//thread1,i = 3
//thread1,i = 4
//thread3,i = 2
//thread2,i = 3
//thread3,i = 3
//thread1,i = 5
//thread3,i = 4
//thread2,i = 4
//thread3,i = 5
//thread1,i = 6
//thread3,i = 6
//thread3,i = 7
//thread3,i = 8
//thread3,i = 9
//thread2,i = 5
//thread2,i = 6
//thread2,i = 7
//thread1,i = 7
//thread2,i = 8
//thread2,i = 9
//thread1,i = 8
//thread1,i = 9
此时所有的线程对象变为了交替执行
2.1.2.start()方法解析
首先我们可以看看start方法的源码:
public synchronized void start() {
/**
* This method is not invoked for the main method thread or "system"
* group threads created/set up by the VM. Any new functionality added
* to this method in the future may have to also be added to the VM.
*
* A zero status value corresponds to state "NEW".
*/
if (threadStatus != 0)
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0();
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
从上述我们可以看到:
- start()方法中,threadStatus不为0时抛出IllegalThreadStateException异常,是一个RunTimeException(非受查异常),这里的threadStatus是Thread中的一个属性(private volatile int threadStatus = 0),默认为0表示线程未被启动过,由此可以看出,
每一个线程对象只能够启动一次
- 另外,在start()方法中调用了start0()方法,而这个方法是一个只声明而未实现的方法同时使用native关键字(原生方法)进行定义,
实际上该方法最终要调用Java线程的run方法
2.2.Runnable接口实现多线程
Thread类的核心功能是进行线程的启动
,如果一个类为了实现多线程直接去继承Thread类
就会有单继承局限,在java中又提供有另外一种实现模式:Runnable接口
public interface Runnable {
/**
* When an object implementing interface <code>Runnable</code> is used
* to create a thread, starting the thread causes the object's
* <code>run</code> method to be called in that separately executing
* thread.
* <p>
* The general contract of the method <code>run</code> is that it may
* take any action whatsoever.
*
* @see java.lang.Thread#run()
*/
public abstract void run();
}
利用Runable接口实现线程主体类
class MyThread implements Runnable {
private String title ;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {
for (int i = 0; i < 10 ; i++) {
System.out.println(this.title+",i = " + i);
}
}
}
虽然解决了单继承局限问题,但是此时没有start()方法被继承了,那么此时就需要关注Thread类提供的构造方法:
public Thread(Runnable target)
该方法可以接收Runnable接口对象,此时启动多线程:
class MyThread implements Runnable {
private String title ;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {
for (int i = 0; i < 10 ; i++) {
System.out.println(this.title+",i = " + i);
}
}
}
public class Test {
public static void main(String[] args) {
MyThread myThread1 = new MyThread("thread1") ;
MyThread myThread2 = new MyThread("thread2") ;
MyThread myThread3 = new MyThread("thread3") ;
new Thread(myThread1).start();
new Thread(myThread2).start();
new Thread(myThread3).start();
}
}
//输出
//thread1,i = 0
//thread1,i = 1
//thread1,i = 2
//thread1,i = 3
//thread1,i = 4
//thread1,i = 5
//thread2,i = 0
//thread2,i = 1
//thread2,i = 2
//thread2,i = 3
//thread2,i = 4
//thread2,i = 5
//thread2,i = 6
//thread2,i = 7
//thread2,i = 8
//thread2,i = 9
//thread1,i = 6
//thread1,i = 7
//thread1,i = 8
//thread1,i = 9
//thread3,i = 0
//thread3,i = 1
//thread3,i = 2
//thread3,i = 3
//thread3,i = 4
//thread3,i = 5
//thread3,i = 6
//thread3,i = 7
//thread3,i = 8
//thread3,i = 9
这个时候就启动了多线程,要知道的是,多线程的启动永远都是Thread类的start()方法
,这个时候需要注意的是,对于此时的Runnable接口对象可以采用匿名内部类或者Lambda表达式来定义
//使用匿名内部类进行Runnable对象创建
public class Test {
public static void main(String[] args) {
new Thread(new Runnable() {
@Override
public void run() {
System.out.println("Hello World");
}
}).start();
}
}
//使用Lamdba表达式进行Runnable对象创建
public class TestDemo {
public static void main(String[] args) {
Runnable runnable = () -> {};
new Thread(runnable).start();
}
}
Thread与Runnable区别
- 1.首先从使用形式来讲,明显使用Runnable实现多线程要比继承Thread类要好,因为可以避免但继承局限
- 2.Thread类是Runnable接口的实现类
- 3.多线程的处理上使用的就是代理设计模式,其中Thread类完成资源调度、系统分配等工作辅助线程业务主类,自定义的线程负责真实业务实现
- 4.使用Runnable实现的多线程的程序类可以更好的描述出程序共享的概念(并不是说Thread不能)
//使用Thread实现数据共享(产生若干线程进行同一数据的处理操作)
class MyThread extends Thread {
private int ticket = 10 ; // 一共10张票
private String title ;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {
while(this.ticket>0){
System.out.println(title + "剩余票数:"+this.ticket -- );
}
}}
public class Test {
public static void main(String[] args) {
new MyThread("一号").start();
new MyThread("二号").start();
new MyThread("三号").start();
}
}
//三号剩余票数:10
//三号剩余票数:9
//三号剩余票数:8
//三号剩余票数:7
//三号剩余票数:6
//三号剩余票数:5
//二号剩余票数:10
//三号剩余票数:4
//一号剩余票数:10
//二号剩余票数:9
//二号剩余票数:8
//二号剩余票数:7
//二号剩余票数:6
//二号剩余票数:5
//二号剩余票数:4
//一号剩余票数:9
//一号剩余票数:8
//三号剩余票数:3
//三号剩余票数:2
//三号剩余票数:1
//一号剩余票数:7
//二号剩余票数:3
//二号剩余票数:2
//二号剩余票数:1
//一号剩余票数:6
//一号剩余票数:5
//一号剩余票数:4
//一号剩余票数:3
//一号剩余票数:2
//一号剩余票数:1
很明显,此时启动三个线程实现卖票处理。结果变为了卖各自的票。
//使用Runnable实现共享
class MyThread implements Runnable {
private int ticket = 10 ; // 一共10张票
private String title ;
public MyThread(String title) {
this.title = title;
}
@Override
public void run() {
while(this.ticket>0){
System.out.println(title + "剩余票数:"+this.ticket -- );
}
}
}
public class Test {
public static void main(String[] args) {
MyThread mt = new MyThread("一号") ;
new Thread(mt).start();
new Thread(mt).start();
}
}
//一号剩余票数:10
//一号剩余票数:8
//一号剩余票数:7
//一号剩余票数:6
//一号剩余票数:9
//一号剩余票数:4
//一号剩余票数:3
//一号剩余票数:5
//一号剩余票数:2
//一号剩余票数:1
2.3.Callable实现多线程(有返回值)
从JDK1.5开始追加了新的开发包:java.uti.concurrent,这个开发包主要是进行高并发编程使用的
,包含很多在高 并发操作中会使用的类
。在这个包里定义有一个新的接口Callable
public interface Callable<V> {
/**
* Computes a result, or throws an exception if unable to do so.
*
* @return computed result
* @throws Exception if unable to compute a result
*/
V call() throws Exception;
}
Runnable中的run()方法没有返回值,它的设计也遵循了主方法的设计原则:线程开始了就别回头。但是很多时候
需要一些返回值,例如某些线程执行完成后可能带来一些返回结果,这种情况下就只能利用Callable来实现多线
程,使用Callable定义线程主体类
class MyThread implements Callable<String> {
private int ticket = 10 ; // 一共10张票
@Override
public String call() throws Exception {
while(this.ticket>0){
System.out.println("剩余票数:"+this.ticket -- );
}
return "票卖完了,下次吧。。。" ;
}
}
不管何种情况,如果要想启动多线程只有Thread类中的start()方法
所以启动多线程的操作如下:
class MyThread implements Callable<String> {
private int ticket = 10 ; // 一共10张票
@Override
public String call() throws Exception {
while(this.ticket>0){
System.out.println("剩余票数:"+this.ticket -- );
}
return "票卖完了,下次吧。。。" ;
}
}
public class Test {
public static void main(String[] args) throws Exception {
FutureTask<String> task = new FutureTask<String >(new MyThread()) ;
new Thread(task).start();
new Thread(task).start();
System.out.println(task.get());
}
}
//剩余票数:10
//剩余票数:9
//剩余票数:8
//剩余票数:7
//剩余票数:6
//剩余票数:5
//剩余票数:4
//剩余票数:3
//剩余票数:2
//剩余票数:1
//票卖完了,下次吧。。。
以上形式主要是为了取得线程的执行结果。
2.4.线程池实现多线程
Java中的线程池是运用场景最多的并发框架
,几乎所有需要异步或者并发执行任务的程序都可以使用线程池
。开发中使用线程池的优点
如下:
降低资源消耗
:通过重复利用已创建的线程降低线程创建和销毁带来的消耗提高响应速度
:当任务到达时,任务可以不需要等待线程创建就能立即执行提高线程的可管理性
:使用线程池可以统一进行线程分配、调度和监控
2.4.1.线程池的继承关系
2.4.2.线程池的执行流程
当向线程池提交了一个任务之后,线程池是如何处理这个任务的呢?下面来看线程池的执行流程
如下:
- 第一步:
判断核心线程池是否已满
,如果未满,创建一个新的工作线程来执行这个任务,并且任务执行完将其添加到核心线程池,如果已满,判断是否有空闲线程,有的话将分配给空闲线程,否则执行第二步 - 第二步:
判断工作队列(阻塞队列BlockingQueue)是否已满
,如果工作队列未满,将任务存储在工作队列中,等待空闲的线程调度,如果工作队列已满,执行第三步 - 第三步:
判断当前线程池线程数量是否已达到最大值
,如果没有达到最大值,创建一个新的线程来执行任务(所创建的线程不在核心线程池),否则执行第四步 - 第四步:
调用饱和策略
来执行此任务
ThreadPoolExecutor执行execute()方法
的执行流程如下:
- 1)如果当前运行的线程少于corePoolSize,则创建新线程来执行任务(注意,执行这一步骤需要获取
全局锁
) - 2)如果运行的线程等于或多于
corePoolSize
,则将任务加入BlockingQueue - 3)如果无法将任务加入
BlockingQueue
(队列已满),则创建新的线程来处理任务(注意,执行这一步骤需要获取全局锁
) - 4)如果创建新线程将使当前运行的线程超出
maximumPoolSize
,任务将被拒绝,并调用
RejectedExecutionHandler.rejectedExecution()方法(饱和策略
)
ThreadPoolExecutor采取上述步骤的总体设计思路,是为了在执行execute()方法时,尽可能地避免获取全局锁(那将会是一个严重的可伸缩瓶颈)。在ThreadPoolExecutor完成预热之后(当前运行的线程数大于等于corePoolSize),几乎所有的execute()方法调用都是执行步骤2,而步骤2不需要获取全局锁
2.4.3.饱和策略(RejectedExecutionHandler)
public interface RejectedExecutionHandler {
/**
* Method that may be invoked by a {@link ThreadPoolExecutor} when
* {@link ThreadPoolExecutor#execute execute} cannot accept a
* task. This may occur when no more threads or queue slots are
* available because their bounds would be exceeded, or upon
* shutdown of the Executor.
*
* <p>In the absence of other alternatives, the method may throw
* an unchecked {@link RejectedExecutionException}, which will be
* propagated to the caller of {@code execute}.
*
* @param r the runnable task requested to be executed
* @param executor the executor attempting to execute this task
* @throws RejectedExecutionException if there is no remedy
*/
void rejectedExecution(Runnable r, ThreadPoolExecutor executor);
}
AbortPolicy
:无法处理新任务抛出异常(JDK默认采用此策略)
public static class AbortPolicy implements RejectedExecutionHandler {
/**
* Creates an {@code AbortPolicy}.
*/
public AbortPolicy() { }
/**
* Always throws RejectedExecutionException.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
* @throws RejectedExecutionException always
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
throw new RejectedExecutionException("Task " + r.toString() +
" rejected from " +
e.toString());
}
}
CallerRunsPolicy
:使用调用者所在线程来处理任务
public static class CallerRunsPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code CallerRunsPolicy}.
*/
public CallerRunsPolicy() { }
/**
* Executes task r in the caller's thread, unless the executor
* has been shut down, in which case the task is discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
r.run();
}
}
}
DiscardOldestPolicy
:丢弃队列中最近的一个任务并执行当前任务
public static class DiscardOldestPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardOldestPolicy} for the given executor.
*/
public DiscardOldestPolicy() { }
/**
* Obtains and ignores the next task that the executor
* would otherwise execute, if one is immediately available,
* and then retries execution of task r, unless the executor
* is shut down, in which case task r is instead discarded.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
if (!e.isShutdown()) {
e.getQueue().poll();
e.execute(r);
}
}
}
}
DisCardPolicy
:不处理任务,丢弃任务,也不报异常
public static class DiscardPolicy implements RejectedExecutionHandler {
/**
* Creates a {@code DiscardPolicy}.
*/
public DiscardPolicy() { }
/**
* Does nothing, which has the effect of discarding task r.
*
* @param r the runnable task requested to be executed
* @param e the executor attempting to execute this task
*/
public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {
}
}
2.4.4.线程池的使用
2.4.4.1.手工创建线程池
通过new一个ThreadPoolExecutor就可以实现自定义线程池:
public ThreadPoolExecutor(int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
RejectedExecutionHandler handler)
其参数如下:
- 1)
corePoolSize
:线程池的基本大小,当提交一个任务到线程池时,线程池会创建一个线程来执行任务,即使其他空闲的基本线程能够执行新任务也会创建线程,等到需要执行的任务数大于线程池基本大小时就不再创建
。如果调用了线程池的prestartAllCoreThreads()方法,线程池会提前创建并启动所有基本线程 - 2)
runnableTaskQueue
:任务队列,用于保存等待执行的任务的阻塞队列。可以选择以下几种阻塞队列
1.ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序
2.LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量通常要高于ArrayBlockingQueue。静态工厂方法Executors.newFixedThreadPool()使用了这个队列
3.SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高于Linked-BlockingQueue,静态工厂方法Executors.newCachedThreadPool使用了这个队列
4.PriorityBlockingQueue:一个具有优先级的无限阻塞队列
- 3)
maximumPoolSize
:线程池最大数量,线程池允许创建的最大线程数。如果队列满了,并且已创建的线程数小于最大线程数,则线程池会再创建新的线程执行任务。值得注意的是,如果使用了无界的任务队列这个参数就没什么效果 - 4)
keepAliveTime
:线程活动保持时间,线程池的工作线程空闲后,保持存活的时间。所以,如果任务很多,并且每个任务执行的时间比较短,可以调大时间,提高线程的利用率。 - 5)
TimeUnit
:线程活动保持时间的单位,可选的单位有天(DAYS)、小时(HOURS)、分钟(MINUTES)、毫秒(MILLISECONDS)、微秒(MICROSECONDS,千分之一毫秒)和纳秒
(NANOSECONDS,千分之一微秒) - 6)RejectedExecutionHandler:饱和策略,当队列和线程池都满了,说明线程池处于饱和状态,那么必须
采取一种策略处理提交的新任务
这个策略默认情况下是AbortPolicy,表示无法处理新任务时抛出异常。在JDK 1.5中Java线程池框架提供了以下4种策略:
- AbortPolicy:直接抛出异常(默认采用此策略)
- CallerRunsPolicy:使用调用者所在线程来运行任务
- DiscardOldestPolicy:丢弃队列里最近的一个任务,并执行当前任务
- DiscardPolicy:不处理,丢弃掉
手工创建一个线程池示例如下:
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(3,5,2000,TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>());
2.4.4.2.向线程池提交任务
可以使用两个方法向线程池提交任务:
- execute():用于提交
不需要返回值的任务
,所以无法判断任务是否被线程池执行成功
- submit()
使用execute()方法示例如下:
class RunnableThread implements Runnable {
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "、" + i);
}
}
}
public class Test {
public static void main(String[] args){
RunnableThread runnableThread = new RunnableThread();
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(4,5,2000,TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>());
for (int i = 0; i < 5; i++) {
threadPoolExecutor.execute(runnableThread);
}
}
}
submit()方法用于提交需要返回值的任务
,线程池会返回一个future类型
的对象,通过这个future对象可以判断任务是否执行成功,并且可以通过future的get()方法来获取返回值
,get()方法会阻塞当前线程直到任务完成,而使用get(long timeout,TimeUnit unit)方法则会阻塞当前线程一段时间后立即返回,这时候有可能任务没有执行完。使用submit()方法如下:
class CallableThread implements Callable<String> {
@Override
public String call() throws Exception {
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName() + "、" + i);
}
return Thread.currentThread().getName()+"任务执行完毕";
}
}
public class Test {
public static void main(String[] args){
CallableThread callableThread = new CallableThread();
ThreadPoolExecutor threadPoolExecutor =
new ThreadPoolExecutor(3,5,2000,TimeUnit.MILLISECONDS,
new LinkedBlockingDeque<Runnable>());
for (int i = 0; i < 5; i++) {
Future<String> future = threadPoolExecutor.submit(callableThread);
try {
String str = future.get();
System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
}
2.4.4.3.关闭线程池
可以通过调用线程池的shutdown
或shutdownNow
方法来关闭线程池。它们的原理是遍历线程池中的工作线程,然后逐个调用线程的interrupt方法来中断线程,所以无法响应中断的任务可能永远无法终止
。但是它们存在一定的区别:
- shutdownNow首先将线程池的状态设置成STOP,然后尝试停止所有的正在执行或暂停任务的线程,并返回等待执行任务的列表
- shutdown只是将线程池的状态设置成SHUTDOWN状态,然后中断所有没有正在执行任务的线程
只要调用了这两个关闭方法中的任意一个,isShutdown
方法就会返回true。当所有的任务都已关闭后,才表示线程池关闭成功,这时调用isTerminaed
方法会返回true。至于应该调用哪一种方法来关闭线程池,应该由提交到线程池的任务特性决定
,通常调用shutdown方法来关闭线程池,如果任务不一定要执行完,则可以调用shutdownNow方法
2.4.4.4.Executor框架
在Java中,使用线程来异步执行任务,Java线程的创建与销毁需要一定的开销,如果我们为每一个任务创建一个新线程来执行,这些线程的创建与销毁将消耗大量的计算资源
。同时,为每一个任务创建一个新线程来执行,这种策略可能会使处于高负荷状态的应用最终崩溃。Java的线程既是工作单元,也是执行机制。从JDK5开始,把工作单元与执行机制分离开来。工作单元包括Runnable和Callable,而执行机制由Executor框架提供
2.4.4.4.1.Executor框架的两级调度模型
Java线程(java.lang.Thread)被一对一映射为本地操作系统线程,Java线程启动时会创建一个本地操作系统线程,当该Java线程终止时,这个操作系统线程也会被回收。操作系统会调度所有线程并将它们分配给可用的CPU:
- 在
上层
,Java多线程程序通常把应用分解为若干个任务,然后使用用户级的调度器(Executor框架)将这些任务映射为固定数量的线程 - 在
底层
,操作系统内核将这些线程映射到硬件处理器上
2.4.4.4.2.Executor框架的结构与成员
2.4.4.4.3.ThreadPoolExecutor详解
Executor框架最核心的类是ThreadPoolExecutor,它是线程池的实现类,其内置的四大线程池:
- 固定大小线程池FixedThreadPool
固定大小线程池适用于为了满足客户资源管理需求,而需要限制当前线程数量的应用,适用于负载较重的服务器
- 单线程池SingleThreadPool
SingleThreadExecutor适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景
- 缓存线程池CachedThreadPool
CachedThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者负载较轻的服务器
- 定时调度池ScheduledThreadPoolExecutor(int nThread)
ScheduledThreadPoolExecutor主要用来在给定的延迟之后运行任务,或者定期执行任务
2.4.4.4.3.1.FixedThreadPool详解
FixedThreadPool被称为可重用固定线程数的线程池
:
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>());
}
FixedThreadPool使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE)。使用无界队列作为工作队列会对线程池带来如下影响:
- 当线程池中的线程数达到corePoolSize后,新任务将在无界队列中等待,因此线程池中 的线程数不会超过corePoolSize
- 由于1,使用无界队列时maximumPoolSize将是一个无效参数
- 由于1和2,使用无界队列时keepAliveTime将是一个无效参数
- 由于使用无界队列,运行中的FixedThreadPool(未执行方法shutdown()或shutdownNow())不会拒绝任务(不会调用RejectedExecutionHandler.rejectedExecution方法)
使用FixedThreadPool示例:
public class Test {
public static void main(String[] args){
ExecutorService executorService =
Executors.newFixedThreadPool(5);
for (int i = 0; i < 5; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
System.out.println(Thread.currentThread().getName()+"、"+j);
}
}
});
}
executorService.shutdown();
}
}
固定大小线程池适用于为了满足客户资源管理需求,而需要限制当前线程数量的应用,适用于负载较重的服务器
2.4.4.4.3.2.SingleThreadPoolExecutor详解
SingleThreadExecutor是使用单个worker线程的Executor
:
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()));
}
SingleThreadExecutor的corePoolSize
和maximumPoolSize
被设置为1
,其他参数与FixedThreadPool相同。SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列(队列的容量为Integer.MAX_VALUE),单线程池使用范例如下:
public class Test {
public static void main(String[] args){
ExecutorService executorService =
Executors.newSingleThreadExecutor();
for (int i = 0; i < 5; i++) {
executorService.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
System.out.println(Thread.currentThread().getName()+"、"+j);
}
}
});
}
executorService.shutdown();
}
}
SingleThreadExecutor适用于需要保证顺序地执行各个任务;并且在任意时间点,不会有多个线程是活动的应用场景
2.4.4.4.3.3.CachedThreadPool详解
CachedThreadPool是一个会根据需要创建新线程的线程池:
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
CachedThreadPool的corePoolSize被设置为0,即corePool为空,maximumPoolSize被设置为Integer.MAX_VALUE,即maximumPool是无界
的,这里把keepAliveTime设置为60L,意味着CachedThreadPool中的空闲线程等待新任务的最长时间为60秒
,空闲线程超过60秒后将会被终止
注意:FixedThreadPool和SingleThreadExecutor使用无界队列LinkedBlockingQueue作为线程池的工作队列,CachedThreadPool使用没有容量的SynchronousQueue作为线程池的工作队列,但CachedThreadPool的maximumPool是无界的,这意味着,如果主线程提交任务的速度高于maximumPool中线程处理任务的速度时,CachedThreadPool会不断创建新线程,极端情况下,CachedThreadPool会因为创建过多线程而耗尽CPU和内存资源,所以
缓冲线程池根据需要创建新线程,当提交任务速度快于执行任务速度,缓存线程池不会创建新线程
使用缓冲线程池示例:
public class Test {
public static void main(String[] args){
ExecutorService executorService =
Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
executorService.submit(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
System.out.println(Thread.currentThread().getName()+"、"+j);
}
}
});
}
executorService.shutdown();
}
}
CachedThreadPool的execute()方法的执行流程如下:
- 1.首先执行SynchronousQueue.offer(Runnable task)。如果当前maximumPool中有空闲线程正在执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),那么主线程执行offer操作与空闲线程执行的poll操作配对成功,主线程把任务交给空闲线程执行,execute()方法执行完成;否则执行下面的步骤2
- 2.当初始maximumPool为空,或者maximumPool中当前没有空闲线程时,将没有线程执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS)。这种情况下,步骤1将失败,此时CachedThreadPool会创建一个新线程执行任务,execute()方法执行完成
- 3.在步骤2中新创建的线程将任务执行完后,会执行SynchronousQueue.poll(keepAliveTime,TimeUnit.NANOSECONDS),这个poll操作会让空闲线程最多在SynchronousQueue中等待60秒钟。如果60秒钟内主线程提交了一个新任务(主线程执行步骤1),那么这个空闲线程将执行主线程提交的新任务,否则,这个空闲线程将终止,由于空闲60秒的空闲线程会被终止,因此长时间保持空闲的CachedThreadPool不会使用任何资源。
CachedThreadPool是大小无界的线程池,适用于执行很多的短期异步任务的小程序,或者负载较轻的服务器
2.4.4.4.3.4.ScheduledThreadPoolExecutor详解
ScheduledThreadPoolExecutor继承自ThreadPoolExecutor,功能与Timer类似,但ScheduledThreadPoolExecutor功能更强大、
更灵活,Timer对应的是单个后台线程,而ScheduledThreadPoolExecutor可以在构造函数中指定多个对应的后台线程数,ScheduledThreadPoolExecutor的执行流程
如下:
DelayQueue是一个无界队列,所以ThreadPoolExecutor的maximumPoolSize在ScheduledThreadPoolExecutor中没有什么意义(设置maximumPoolSize的大小没有什么效果)。ScheduledThreadPoolExecutor的执行主要分为两大部分:
- 当调用ScheduledThreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法时,会向ScheduledThreadPoolExecutor的DelayQueue添加一个实现了RunnableScheduledFutur接口的ScheduledFutureTask
- 线程池中的线程从DelayQueue中获取ScheduledFutureTask,然后执行任务。ScheduledThreadPoolExecutor为了实现周期性的执行任务,对ThreadPoolExecutor做了如下的修改:
1.使用DelayQueue作为任务队列
2.获取任务的方式不同
3.执行周期任务后,增加了额外的处理
使用定时调度池范例:
public class Test {
public static void main(String[] args){
ScheduledExecutorService executorService =
Executors.newScheduledThreadPool(5);
for (int i = 0; i < 5; i++) {
executorService.scheduleAtFixedRate(new Runnable() {
@Override
public void run() {
for (int j = 0; j < 10; j++) {
System.out.println(Thread.currentThread().getName()+"、"+j);
}
}
},2,3,TimeUnit.SECONDS);
}
}
}
ScheduledThreadPoolExecutor主要用来在给定的延迟之后运行任务,或者定期执行任务