线程池是一种基于池化技术的多线程管理机制,用于减少在创建和销毁线程上的开销,提高程序的性能,尤其是在处理大量短生命周期的异步任务时。
使用ThreadPoolExecutor
构造器,可以用ctrl p
来看每一个参数,构造函数的签名如下:
ThreadPoolExecutor(
int corePoolSize,
int maximumPoolSize,
long keepAliveTime,
TimeUnit unit,
BlockingQueue<Runnable> workQueue,
ThreadFactory threadFactory,
RejectedExecutionHandler handler
)
下面是每个参数的详细说明:
corePoolSize
:线程池的基本大小,即使线程是空闲的也会保留在池中的线程数。除非设置了allowCoreThreadTimeOut(boolean)
,否则即使线程空闲,核心线程也不会超时退出。maximumPoolSize
:线程池允许创建的最大线程数。当工作队列满了且已经创建的线程数小于最大线程数时,线程池会创建新的线程来处理任务。keepAliveTime
: 当线程数超过核心线程数时,这是超出核心线程数部分的线程在终止前可以保持空闲的最长时间。使用下面的unit参数来指定时间单位。unit
:keepAliveTime
参数的时间单位。 使用TimeUnit
枚举来指定时间单位,如imeUnit.SECONDS
。workQueue
:在执行任务之前用于保存任务的队列。这个队列将仅保存由execute
方法提交的Runnable
任务。常用的队列类型包括:LinkedBlockingQueue
,ArrayBlockingQueue
,SynchronousQueue
等。threadFactory
:一个创建新线程的工厂。这个工厂通过提供自定义的线程创建逻辑来创建新线程。如果没有特别的需求,可以使用默认的线程工厂。handler
:当无法接受新任务(因为线程池关闭或达到了最大线程数且工作队列已满)时,拒绝处理任务的处理器。常见的拒绝执行处理策略有:ThreadPoolExecutor.AbortPolicy
(抛出异常)、ThreadPoolExecutor.CallerRunsPolicy
(在调用者的线程中运行任务)、ThreadPoolExecutor.DiscardPolicy
(默默丢弃任务)、ThreadPoolExecutor.DiscardOldestPolicy
(丢弃队列中最旧的任务)。
ExecutorService pool=new ThreadPoolExecutor(3,5,8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
最大线程数-核心线程数=临时线程数量
- 线程池注意事项
- 核心线程和临时线程都在忙,任务队列也满了,新的任务过来的时候才会开始拒绝任务
- 新任务提交的时候发现核心线程都在忙,任务队列也满了,并且还可以创建临时线程,这时候就可以创建临时线程
- 临时线程什么时候创建:
- 什么时候会开始拒绝新任务
使用ThreadPoolExecutor
来处理Runnable
任务是多线程编程中一种非常高效的方法。以下是一个示例,展示如何定义和提交Runnable
任务到一个线程池中执行。
首先创建一个实现Runnable接口的MyRunnable类:
public class MyRunnable implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "输出666");
try {
Thread.sleep(Integer.MAX_VALUE);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
}
}
测试:
public class ThreadPoolTest1 {
public static void main(String[] args) {
// 1、通过ThreadPoolExecutor创建一个线程池对象。
ExecutorService pool=new ThreadPoolExecutor(3,5,8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());
Runnable target=new MyRunnable();
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target); // 线程池会自动创建一个新线程,自动处理这个任务,自动执行的!
pool.execute(target);
pool.execute(target);
pool.execute(target);
pool.execute(target);
// 到了临时线程的创建时机了
pool.execute(target);
pool.execute(target);
// 到了新任务的拒绝时机了!
pool.execute(target);
// pool.shutdown(); // 等着线程池的任务全部执行完毕后,再关闭线程池
// pool.shutdownNow(); // 立即关闭线程池!不管任务是否执行完毕!
}
}
Callable
接口类似于Runnable
,但它可以返回一个结果并能抛出异常。使用ThreadPoolExecutor
来处理Callable
任务可以让你异步地执行复杂的任务,并在完成后获取结果。
首先创建一个实现Callable
接口的MyCallable
类
//要使用泛型来指定返回的结果类型
public class MyCallable implements Callable<String> {
private int n;
public MyCallable(int n) {
this.n = n;
}
@Override
public String call() throws Exception {
int sum=0;
for (int i = 0; i < n; i++) {
sum+=i;
}
return Thread.currentThread().getName()+"结果是:"+sum;
}
}
测试:
public class ThreadPoolTest2 {
public static void main(String[] args) throws Exception {
ThreadPoolExecutor pool = new ThreadPoolExecutor(3, 5, 8, TimeUnit.SECONDS,
new ArrayBlockingQueue<>(4), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy());
// 使用线程处理Callable任务。
Future<String> f1 = pool.submit(new MyCallable(100));
Future<String> f2 = pool.submit(new MyCallable(200));
System.out.println(f1.get());
System.out.println(f2.get());
pool.shutdown();
}
}
public class Test {
public static void main(String[] args) {
//ctrl+shift+esc
// 计算密集型的任务:核心线程数量 = CPU的核数 + 1
// IO密集型的任务:核心线程数量 = CPU核数 * 2
ExecutorService pool = Executors.newFixedThreadPool(3);//// 创建固定线程数量的线程池
ExecutorService pool1 = Executors.newSingleThreadExecutor();//创建只有一个线程的线程池对象
ExecutorService pool2 = Executors.newCachedThreadPool();//创建一个线程数量随着任务增加而增加
}
}
- 正在运行的程序就是一个独立的进程
- 线程是属于进程的,一个进程中可以同时运行很多个线程
- 进程中的多个线程其实是并发和并行执行的
- 进程中的线程是由CPU负责调度执行的,但CPU能同时处理线程的数量有限,为了保证全部线程都能往前执行,CPU会轮询为系统的每个线程服务,由于CPU切换的速度很快,给我们的感觉这些线程在同时执行,这就是并发(感觉)
- 在同一个时刻上,同时有多个线程在被CPU调度执行,比如CPU有16核,则同一时间有16个线程在被执行,这16个线程在并行(真实)
线程的生命周期是指线程从创建到结束的整个过程。在Java中,线程生命周期可以通过多个状态来描述,这些状态在Thread.State
枚举中定义。理解这些状态对于高效地管理线程和解决多线程编程中的问题非常重要。
- 新建 (New):
- 当线程实例被创建,但
start()
方法还没有被调用时,线程处于这个状态。 - 示例:
Thread t = new Thread()
;
- 当线程实例被创建,但
- 可运行 (Runnable):
- 线程已经启动,并且已经具备运行条件,但是否正在运行取决于操作系统给线程的时间片及处理器的调度。
- 当调用了线程的
start()
方法后,线程进入此状态。 - 示例:
t.start()
; 此时,线程进入可运行状态。
- 阻塞 (Blocked):
- 线程正在等待一个监视器锁(进入一个同步块或方法)以进入或重新进入同步区域。
- 如果其他线程已经占有了锁,那么当前线程会被阻塞直到锁被释放。
- 等待 (Waiting):
- 线程通过调用
wait()
,join()
或LockSupport.park()
方法,进入等待状态。 - 在这种状态下,线程需要依赖其他线程的通知或中断来恢复到可运行状态。
- 示例:当一个线程在另一个线程对象上调用
join()
且后者没有完成执行时,前者进入等待状态。
- 线程通过调用
- 计时等待 (Timed Waiting):
- 类似于等待状态,但有最大等待时间。线程在这个状态下等待另一个线程的通知或一定时间的过去。
- 调用
sleep(long millis)
,wait(long timeout)
,join(long millis)
, 或LockSupport.parkNanos()
,LockSupport.parkUntil()
可以使线程进入此状态。
- 终止 (Terminated):
- 线程的运行结束,原因可能是
run()
方法执行完成后自然结束,或因为发生了异常而提前结束。 - 此状态表示线程已经完成执行。
- 线程的运行结束,原因可能是
- 新建→可运行:通过调用
start()
方法。 - 可运行→阻塞/等待/计时等待:根据线程调用的方法不同,如
wait()
会使线程进入等待状态,而sleep()
会使线程进入计时等待状态。 - 阻塞/等待/计时等待→可运行:获取了所需的监视器锁、其他线程调用了
notify()
/notifyAll()
,或等待时间结束。 - 任何状态到终止:线程的
run()
方法执行完毕或出现未捕获的异常。
原创文章,作者:guozi,如若转载,请注明出处:https://www.sudun.com/ask/87734.html