【JUC源码】线程池:ThreadPoolExecutor(三)Worker 设计及源码分析

线程池系列:

Worker 设计思路

照理说,线程池就是维持着许多线程,然后每个线程都可以很多任务。 但是这里注意一个问题,如果我们创建一个线程时直接传入一个具体的 Runnable,比如

new Thread(new Runnable() {
    
    
            @Override
            public void run() {
    
    
                System.out.println("hello");
            }
        }).start();

那么,这个线程执行完打印 hello 这个任务就结束了,根本无法复用线程去执行多个任务。所以,需要不能在创建 Thread 时放入具体的任务,而是要将 Runnable 再封装一次,在它的 run 方法中调用一个获取任务的具体的方法,比如下面这样:

public class Work implements Runnable {
    
    
        @Override
        public void run() {
    
    
        	// runwork 方法才是真正执行任务的方法
            runwork();
        }
    }

main() {
    
    
	// 创建线程时传入 work,调用逻辑就是 Work#run() -> runwork() -> 具体Runnable的run()
	// 所以,只要保证 runwork 方法能一直获取任务,则该线程就能一直运行
	// 另外,在 runwork 获取任务时,还可以扩展线程回收策略,因为只要该方法返回了,该线程就该结束了
	new Thread(Work).start();
}

理解了我上面说的,ThreadPoolExecutor 的核心逻辑其实就差不多明了了。

Worker 源码分析

线程池中任务执行的最小单元。

ThreadPoolExecutor 的 Worker 比我上面说的做法更加巧妙,它不是以再封装 Runnable 为目的,而是封装 Thread 的同时,又实现了 Runnable 接口,从而达到了线程复用的目的(即 new Thread(this))。

另外,Worker 还保存了创建线程时的第一个任务 firstTask,但是 firstTask 执行完后,就会将它置为 null,然后再通过 getTask() 从任务的队列中不断获取新任务去执行。

private final class Worker
    extends AbstractQueuedSynchronizer
    implements Runnable
{
    
    
	// 执行当前 worker 的线程
	final Thread thread;
	// 创建该线程时的任务(第一个任务),执行完后=null
    Runnable firstTask;
	
	// 在创建 worker 的时候创建 Thread
	Worker(Runnable firstTask) {
    
    
		// 将AQS的状态设置为-1
		// 从后面的isLocked方法可以看到,state!=0 表示已经被加锁
	    setState(-1); // inhibit interrupts until runWorker
	    this.firstTask = firstTask;
		// !!!为了线程的复用,Worker本身实现了 Runnable,并且把自己作为任务传递给 thread。非常巧妙的设计!
	    this.thread = getThreadFactory().newThread(this);
	}
	
	// 调用逻辑:Woker#run -> runWorker() -> 具体Runnable的run()
	// runWorker 获取任务的方式有二:
	// 1.firstTask,执行完就置为 null
	// 2.getTask() 不断从从阻塞队列中获取
    public void run() {
    
    
        runWorker(this);
    }
}

PS:那要是创建的线程多了,好多线程都没有任务空闲下来了怎么办?
答:若一个线程迟迟等不到任务执行就会被回收,具体回收策略在 getTask() 方法中可以看到

另外,Worker 本身还实现了 AQS,所以其本身也是一个锁,其在执行任务的时候,会锁住自己,任务执行完成之后,会释放自己,保证了在一个线程执行任务时再被丢入别的任务。相关方法如下:

public void lock()        {
    
     acquire(1); }
public boolean tryLock()  {
    
     return tryAcquire(1); }
public void unlock()      {
    
     release(1); }
public boolean isLocked() {
    
     return isHeldExclusively();  // Lock methods    
                           
// 尝试加锁,CAS 赋值为 1,表示锁住
protected boolean tryAcquire(int unused) {
    
    
    if (compareAndSetState(0, 1)) {
    
    
        setExclusiveOwnerThread(Thread.currentThread());
        return true;
    }
    return false;
}

// 尝试释放锁,释放锁没有 CAS 校验,可以任意的释放锁
protected boolean tryRelease(int unused) {
    
    
    setExclusiveOwnerThread(null);
    setState(0);     
    return true; 
}

// 0 代表没有锁住,否则代表锁住(-1,1)
protected boolean isHeldExclusively() {
    
    
    return getState() != 0;
}

void interruptIfStarted() {
    
    
       Thread t;
       if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
    
    
           try {
    
    
               t.interrupt();
           } catch (SecurityException ignore) {
    
    
           }
       }
}                                        

猜你喜欢

转载自blog.csdn.net/weixin_43935927/article/details/113966417