【Java并发编程实战】——Java线程池ThreadPoolExecutor(一)

为何需要线程池?
创建或者关闭线程需要时间,且线程本身需要占用内存资源,因此生产环境线程的数量必须得到控制,盲目的创建线程对系统性能有影响。

什么是线程池?
为了让线程复用、提高响应速度、统一维护管理线程,在线程池中维护几个常用的线程,当需要时从池子中取出一个空闲线程,当完成工作时将线程退回到线程池中。

Java中已提供些线程池?
Executors 框架提供了多个方法来创建线程池,我们也可以自己新建一个 ThreadPoolExecutor 线程池。下面列举几个常见的创建线程池的方法。

public static ExecutorService newFixedThreadPool(int nThreads);
public static ExecutorService newSingleThreadExecutor();
public static ExecutorService newCachedThreadPool();
public static ScheduledExecutorService newSingleThreadScheduledExecutor(int corePoolSize);
  • newFixedThreadPool 返回一个固定线程数量的线程池。初始化线程池的时候线程就已创建,有新任务提交时,如果线程池中有空闲线程则执行此任务,若没有,则此任务进任务队列保存。
  • newSingleThreadExecutor 返回一个只有一个线程的线程池。所有提交的任务按照FIFO的顺序执行。
  • newCachedThreadPool 返回一个可根据实际情况调整线程数量的线程池。若没有线程可用,有任务提交就创建一个线程去执行。线程如果长时间没有任务执行,会经过特定的时间后自动被移除。
  • newSingleThreadScheduledExecutor 返回一个单一线程可定时调度任务的线程池。

设置线程池的大小
按照《并发编程实战》一书中的介绍,线程池的最优大小等于:

CPU的数量 * CPU期望的利用率 * (1 + 任务的等待时间/计算时间)

举一个简单的例子:初始化核心线程数为3的固定线程池,启动十个线程,每个线程模拟执行1s

public class TestThreadPool {
    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        for (int i = 0; i < 10; i++) {
            executorService.submit(new MyThread());
        }
    }

    static class MyThread implements Runnable {
        @Override
        public void run() {
            System.out.println(Thread.currentThread().getName() 
            	+ " threadId = " + Thread.currentThread().getId());
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

打印结果

pool-1-thread-1 threadId = 12
pool-1-thread-2 threadId = 13
pool-1-thread-3 threadId = 14
pool-1-thread-1 threadId = 12
pool-1-thread-3 threadId = 14
pool-1-thread-2 threadId = 13
pool-1-thread-3 threadId = 14
pool-1-thread-1 threadId = 12
pool-1-thread-2 threadId = 13
pool-1-thread-3 threadId = 14

可以看到上面的结果:三个线程被复用了,因线程睡眠了一秒,每次都只会有三个线程同时执行。

先了解下线程池是什么设计类结构的,看下他们之间的关系
在这里插入图片描述
各自提供的方法
在这里插入图片描述
接口类 Executor 定义了一个接口 execute() :在未来某个时间执行给定的 command。这个命令可能会在新线程中执行,也有可能在线程池中的线程或者正调用中的线程执行,这取决于 Executor 的实现类。

/**
 * Executes the given command at some time in the future.  The command
 * may execute in a new thread, in a pooled thread, or in the calling
 * thread, at the discretion of the {@code Executor} implementation.
 *
 * @param command the runnable task
 * @throws RejectedExecutionException if this task cannot be
 * accepted for execution
 * @throws NullPointerException if command is null
 */
void execute(Runnable command);

ExecutorService 被设计用来管理线程池:submit 方法通过创建并返回一个 Future 接口扩展了 Executor.execute(Runnable),Future 能够被用来取消执行或者等待执行完成。

//有序的关闭线程池,之前提交的任务依旧执行,但是不接受新任务。如果已经关闭,再次调用无影响。
void shutdown();
//立即关闭线程池,尝试关闭所有执行中的线程,不处理等待中的任务,并返回等待中的任务列表。
//不保证一定能够停止执行中的任务,例如通过 Thread.interrupt 中断线程,线程不响应中断的就不会被终止。
List<Runnable> shutdownNow();

//线程池已经被关闭返回 true
boolean isShutdown();
//如果所有任务完成关闭后返回 true,注意除非先调用 shutdown 或者 shutdownNow,否者 isTerminated 永远不会返回 true
boolean isTerminated();
//在一个关闭请求之后阻塞等待所有任务完成执行,若所有任务执行完成返回 true,如果超时返回 false,执行中断阻塞。
boolean awaitTermination(long timeout, TimeUnit unit)
    throws InterruptedException;

//提交一个带返回值的任务,并返回包含了运行结果的 Future,可以通过调用 Future.get() 等待获取返回值
<T> Future<T> submit(Callable<T> task);
<T> Future<T> submit(Runnable task, T result);
Future<?> submit(Runnable task);

//执行给定的任务,当所有任务完成时,返回保持任务状态和结果的 Future 列表。
<T> List<Future<T>> invokeAll(Collection<? extends Callable<T>> tasks)
    throws InterruptedException;

抽象类 AbstractExecutorService 用来提供 ExecutorService 的默认实现。此类通过调用 newTaskFor 方法返回的一个 RunnableFuture 接口来实现 submit、invokeAny 和 invokeAll ,默认使用 FutureTask 作为返回的实例。

//使用 FutureTask 包装了 Runnable 或 Callable
protected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {
    return new FutureTask<T>(runnable, value);
}
protected <T> RunnableFuture<T> newTaskFor(Callable<T> callable) {
    return new FutureTask<T>(callable);
}

//调用 execute(RunnableFuture) 来提交任务,并返回 RunnableFuture
public Future<?> submit(Runnable task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<Void> ftask = newTaskFor(task, null);
    execute(ftask);
    return ftask;
}
public <T> Future<T> submit(Runnable task, T result) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task, result);
    execute(ftask);
    return ftask;
}
public <T> Future<T> submit(Callable<T> task) {
    if (task == null) throw new NullPointerException();
    RunnableFuture<T> ftask = newTaskFor(task);
    execute(ftask);
    return ftask;
}

public <T> T invokeAny(Collection<? extends Callable<T>> tasks,
                       long timeout, TimeUnit unit)
    throws InterruptedException, ExecutionException, TimeoutException {
    return doInvokeAny(tasks, true, unit.toNanos(timeout));
}

关于 FutureTask ,见FutureTask详解

正式分析线程池源码之前,回顾下线程的基本特性。

线程的状态,看下 Thread 类内部的枚举

public enum State {
	//新建
	NEW,
	//可运行,不区分运行中和等待CPU调度
	RUNNABLE,
	//阻塞,等待获取监视器锁
	BLOCKED,
	//等待
	WAITING,
	//等待一定的时间后自动恢复
	TIMED_WAITING,
	//结束
	TERMINATED;
}

线程中断相关

//中断线程,只是设置一个中断标识而已,有调用者决定是否中断
public void interrupt(){
	...
	//调用本地方法设置中断状态
	interrupt0();
}
private native void interrupt0();

//判断是否中断,并清除中断状态
public static boolean interrupted() {
    return currentThread().isInterrupted(true);
}

//判断是否中断
public boolean isInterrupted() {
    return isInterrupted(false);
}

//调用本地方法获取中断状态
private native boolean isInterrupted(boolean ClearInterrupted);

等待(wait)和通知(notify)线程
一个线程调用 object.wait() 方法,那么他就会进入 object 的等待队列。当其他线程调用 object.notify() 方法时,它就会从等待队列中随机选择一个线程并将其唤醒。调用这两个方法前都需要获取到对象的监视器。

等待线程结束 join
线程1调用 线程2的 join 方法,那么线程1一直阻塞,直到线程2执行完毕。

让出CPU执行条件 yield
暂时放弃CPU执行条件,之后还是会竞争CPU然后执行。

守护线程
一个Java程序里面如果只有守护线程,那么JVM将退出。调用线程的 setDaemon(true) 设置守护线程。

线程优先级
Java的线程优先级与操作系统有关,按照正常的逻辑,优先级数字越低的线程越能够获取到CPU,但无法保证一定这样。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值