java使用默认线程池踩过的坑(二)

本文深入探讨了Java中线程的状态变化及控制机制,详细分析了线程从新建到终止的各种状态及其转换过程。此外,还介绍了如何通过Thread类和ThreadPoolExecutor来管理和控制线程的运行。

云智慧(北京)科技有限公司 陈鑫

是的,一个线程不能够启动两次。那么它是怎么判断的呢?
public synchronized void start() {
/**
* A zero status valuecorresponds to state “NEW”. 0对应的是state NEW
*/
if (threadStatus!= 0) //如果不是NEW state,就直接抛出异常!
throw newIllegalThreadStateException();
group.add(this);
boolean started = false;
try {
start0(); // 启动线程的native方法
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch(Throwable ignore) {
}
}
}
恩,只有是NEW状态才能够调用native方法启动一个线程。好吧,到这里了,就普及也自补一下jvm里的线程状态:
所有的线程状态::
l NEW —— 还没有启动过
l RUNNABLE —— 正在jvm上运行着
l BLOCKED —— 正在等待锁/信号量被释放
l WAITING —— 等待其他某个线程的某个特定动作
l TIMED_WAITING —— A thread that iswaiting for another thread to perform an action for up to a specified waitingtime is in this state.
l TERMINATED —— 退出,停止
线程在某个时间点上只可能存在一种状态,这些状态是jvm里的,并不反映操作系统线程的状态。查一下Thread的API,没有对其状态进行修改的API。那么这条路是不通的吗?
仔细考虑一下……
如果把任务做成Runnable实现类,然后在把这个实现类丢进线程池调度器之前,利用此Runnable构造一个Thread,是不是这个Thread对象就能够控制这个runnable对象,进而控制在线程池中运行着的task了呢?非也!让我们看看Thread和ThreadPoolExecutor对Runnable的处理吧。
Thread
/* What will berun. */
private Runnabletarget;
结合上面的start()方法,很容易猜出,start0()会把target弄成一个线程来进行运行。
ThreadPoolExecutor
public void execute(Runnable command){
if (command== null)
thrownew NullPointerException();
int c =ctl.get();
if(workerCountOf(c) < corePoolSize) {
if (addWorker(command, true))
return;
c =ctl.get();
}
if(isRunning(c) && workQueue.offer(command)) {
intrecheck = ctl.get();
if (!isRunning(recheck) && remove(command))
reject(command);
else if(workerCountOf(recheck) == 0)
addWorker(null, false);
}
else if (!addWorker(command, false))
reject(command);
}

private boolean addWorker(RunnablefirstTask, boolean core) {

booleanworkerStarted = false;
booleanworkerAdded = false;
Worker w =null;
try {
finalReentrantLock mainLock = this.mainLock;
w = newWorker(firstTask);
finalThread t = w.thread;
if (t!= null) {
mainLock.lock();
try{
int c = ctl.get();
int rs = runStateOf(c);

               if (rs < SHUTDOWN ||
                   (rs == SHUTDOWN && firstTask == null)) {
                   if (t.isAlive()) // precheck that t is startable
                        throw newIllegalThreadStateException();

workers.add(w);
int s = workers.size();
if (s > largestPoolSize)
largestPoolSize =s;
workerAdded = true;
}
}finally {
mainLock.unlock();
}
if(workerAdded) {
t.start();
workerStarted = true;
}
}
} finally {
if (!workerStarted)
addWorkerFailed(w);
}
return workerStarted;
}
那么Worker又是怎样的呢?
Worker
private final class Worker
extendsAbstractQueuedSynchronizer
implementsRunnable
{
finalThread thread;
RunnablefirstTask;
volatilelong completedTasks;
Worker(Runnable firstTask) {
setState(-1); //调用runWorker之前不可以interrupt
this.firstTask = firstTask;
this.thread = getThreadFactory().newThread(this);
}
public voidrun() {
runWorker(this);
}
……
…….
voidinterruptIfStarted() {
Threadt;
if(getState() >= 0 && (t = thread) != null &&!t.isInterrupted()) {
try{
t.interrupt();
}catch (SecurityException ignore) {
}
}
}
}
可见worker里既包装了Runnable对象——task,又包装了一个Thread对象——以自己作为初始化参数,因为worker也是Runnable对象。然后对外提供了运行与停止接口,run()和interruptIfStarted()。回顾上面使用Thread的例子不禁有了新的领悟,我们把一个Thread对象交给ThreadPoolExecutor执行后,实际的调用是对Thread(FileTask())对象,我们暂时称之为workerWrapper。那么我们在池外进行FileTask.interrupt()操作影响的是FileTask对象,而不是workerWrapper。所以可能上面对于start()方法二次调用不是特别适当。更恰当的应该是在fileTask.interrupt()的时候就跑出异常,因为从来没有对fileTask对象执行过start()方法,这时候去interrupt就会出现错误。具体如下图:
无
分析到此,我们已经明确除了调用ThreadPoolExecutor了的interruptWorkers()方法别无其他途径操作这些worker了。
private void interruptWorkers() {
finalReentrantLock mainLock = this.mainLock;
mainLock.lock();
try {
for(Worker w : workers)
w.interruptIfStarted();
} finally {
mainLock.unlock();
}
}

### Java 默认线程池与自定义线程池 #### 默认线程池使用方式 Java 提供了 `Executors` 工厂类用于快速创建几种常见的线程池。这些预定义的线程池适用于特定类型的并发需求。 - **Fixed Thread Pool** 创建具有固定数量工作线程的线程池。当所有线程都在忙碌状态时,新任务将在队列中等待直到有空闲线程处理它们。 ```java ExecutorService fixedThreadPool = Executors.newFixedThreadPool(10); ``` - **Cached Thread Pool** 这种线程池会根据需要创建新的线程,但在先前构建的线程可用时将重用它们。对于执行大量短期异步任务的应用程序非常有用。 ```java ExecutorService cachedThreadPool = Executors.newCachedThreadPool(); ``` - **Scheduled Thread Pool** 支持延迟和定期执行任务的功能。可以用来安排命令在未来某个时间点运行一次或重复多次。 ```java ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5); ``` - **Single Thread Executor** 只有一个工作者线程;它确保所有的任务都是按顺序完成,并且不会同时发生竞争条件。 ```java ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor(); ``` 以上提到的各种默认线程池都有其特点,在某些情况下可能已经足够满足应用的需求[^3]。 #### 自定义线程池使用方式 然而,默认线程池并不总是最佳选择,特别是在面对复杂业务逻辑的情况下。这时可以通过继承 `ThreadPoolExecutor` 类来自定义更灵活的线程池实现: ```java import java.util.concurrent.*; public class CustomThreadPool { private final ThreadPoolExecutor executor; public CustomThreadPool(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit) { this.executor = new ThreadPoolExecutor( corePoolSize, maximumPoolSize, keepAliveTime, unit, new LinkedBlockingQueue<>(), new CustomThreadFactory(), // 定制化线程工厂 new ThreadPoolExecutor.CallerRunsPolicy() // 配置拒绝策略 ); } public Future<?> submit(Runnable task){ return executor.submit(task); } } ``` 在这个例子中,除了设置基本参数外还可以指定阻塞队列类型、线程工厂以及饱和政策等高级选项来更好地适应具体应用场景的要求[^2]。 #### 主要区别 主要的区别在于灵活性上。默认线程池提供了简单易用的方式来进行多线程编程,但对于一些特殊场景下的性能优化和支持不够充分。而通过自定义线程池,则可以根据实际项目的负载特性调整各个配置项,从而达到更好的资源利用率和服务质量保障效果[^1]。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值