Executor of Concurrent Programming (3)

Posted by Darkpower on Thu, 16 May 2019 08:11:34 +0200

Preface:

We've learned about the top-level blocking queues of concurrent packages. This article introduces the blocking queue-based implementers.

Executor structure

1: Executor interface:

public interface Executor{

   void execute(Runnable command);

}

Top-level interface design is good, single responsibility principle, the actuator only does one thing, that is, to perform the Runnable task!

2: Executors class:

This class is often used to create thread pools. Let's look at how the underlying implementation works. To understand this kind of implementation, we first need to understand ThreadPoolExecutor, and in fact, the Ali Programming Protocol requires us to create thread pools with ThreadPoolExecutor rather than Executors classes for more accurate configuration.

Membership variables of the ThreadPoolExecutor class:

    //The high three represents the state and the low 29 represents the number of valid threads, so the thread pool can accommodate up to 29-1 threads.

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));

    private static final int COUNT_BITS = Integer.SIZE - 3;

    //Maximum Bit Number of Threads

    private static final int CAPACITY  = (1 << COUNT_BITS) - 1;

    //Five states of thread pool

    private static final int RUNNING    = -1 << COUNT_BITS;

    private static final int SHUTDOWN  =  0 << COUNT_BITS;

    private static final int STOP      =  1 << COUNT_BITS;

    private static final int TIDYING    =  2 << COUNT_BITS;

    private static final int TERMINATED =  3 << COUNT_BITS;

    // Packing thread status and number of threads, and unpacking and restoring two values

    private static int runStateOf(int c)    { return c & ~CAPACITY; }

    private static int workerCountOf(int c)  { return c & CAPACITY; }

    private static int ctlOf(int rs, int wc) { return rs | wc; }

    //Blocking queues for storing tasks executed by threads

    private final BlockingQueue<Runnable> workQueue;

    //To save threads in the thread pool, accessing the Set requires a mainLock lock

    private final HashSet workers = new HashSet();

    //A thread factory for creating threads, which calls the addWorker production line

    private volatile ThreadFactory threadFactory;

    //Minimum number of surviving threads saved in thread pool

    private volatile int corePoolSize;

    //Maximum number of threads surviving in thread pool

    private volatile int maximumPoolSize;

    //Processing strategy when thread pool is saturated or thread pool is closed, default is to reject tasks

    private static final RejectedExecutionHandler defaultHandler = new AbortPolicy();

Two key fields: workerCount and runState, which represent the number of valid threads in the current thread pool, and runState, which represent the status of the current thread pool. Other field annotations have been explained. Let's focus on the worker, which is what the threads that actually work look like:

private final class Worker extends AbstractQueuedSynchronizer implements Runnable{

        private static final long serialVersionUID = 6138294804551838833L;

        //Thread is the real worker here.

        final Thread thread;                             

        Runnable firstTask;

        volatile long completedTasks;

        Worker(Runnable firstTask) {

            setState(-1);

            this.firstTask = firstTask;

            this.thread = getThreadFactory().newThread(this);

        }

       //Method of calling Run method in Thread

        public void run() {

            runWorker(this);

        }

        // 0 represents unlocked state and 1 represents locked state. This is a familiar resource in the AQS framework.

        protected boolean isHeldExclusively() {

            return getState() != 0;

        }

        protected boolean tryAcquire(int unused) {

            if (compareAndSetState(0, 1)) {

                setExclusiveOwnerThread(Thread.currentThread());

                return true;

            }

            return false;

        }

        protected boolean tryRelease(int unused) {

            setExclusiveOwnerThread(null);

            setState(0);

            return true;

        }

        public void lock()        { acquire(1); }

        public boolean tryLock()  { return tryAcquire(1); }

        public void unlock()      { release(1); }

        public boolean isLocked() { return isHeldExclusively(); }

        void interruptIfStarted() {

            Thread t;

            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {

                try {

                    t.interrupt();

                } catch (SecurityException ignore) {

                }

            }

        }

    }

The above code is a panorama of the Worker class, which is actually the son of the AQS class. It implements lock and unlock methods customized and sets up the state resources of the exclusive AQS. The most important thing is that it combines Thread, the Thread of the Worker is produced by the ThreadFactory, and the thread execution is realized through the run method.

Knowing these worker s, we have to think about how to put them in the pool:

private boolean addWorker(Runnable firstTask, boolean core) {

        retry:

        for (;;) {

            int c = ctl.get();

            int rs = runStateOf(c);

            // Check if queue empty only if necessary.

            if (rs >= SHUTDOWN &&

                ! (rs == SHUTDOWN &&

                  firstTask == null &&

                  ! workQueue.isEmpty()))

                return false;

            for (;;) {

                int wc = workerCountOf(c);

                if (wc >= CAPACITY ||

                    wc >= (core ? corePoolSize : maximumPoolSize))

                    return false;

                if (compareAndIncrementWorkerCount(c))

                    break retry;

                c = ctl.get();  // Re-read ctl

                if (runStateOf(c) != rs)

                    continue retry;

                // else CAS failed due to workerCount change; retry inner loop

            }

        }

        //I'm a dividing line. My top line is to determine whether the current thread pool meets the requirement of adding threads. Not enough, of course.

        boolean workerStarted = false;

        boolean workerAdded = false;

        Worker w = null;

        try {

            w = new Worker(firstTask);

            final Thread t = w.thread;

            if (t != null) {

                final ReentrantLock mainLock = this.mainLock;

                mainLock.lock();

                try {

                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||(rs == SHUTDOWN && firstTask == null)) {

                        if (t.isAlive())

                            throw new IllegalThreadStateException();

                        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;

    }

private void addWorkerFailed(Worker w) {

        final ReentrantLock mainLock = this.mainLock;

        mainLock.lock();

        try {

            if (w != null)

                workers.remove(w);

            decrementWorkerCount();

            tryTerminate();

        } finally {

            mainLock.unlock();

        }

    }

Don't be frightened by the lengthy code above. Above the splitter line is the condition to determine whether the current thread pool state satisfies the requirement of adding new threads. Here is the code for adding threads, and the following code is well understood: first, call the Worker constructor to create a thread using its internal ThreadFactory and stuff the task to the new thread; then add the model worker of the new thread to the inner worker's Set and change some properties of the thread pool, because the process is not thread-safe (workers). It's actually HashSet) so it uses a lock; at last it calls the thread's start method to start the thread. Thread startup must be the run method to execute Runnable. Where is the run method? Take a good look at the Worker class (which implements Runnable). If the addition fails, it's okay to lock the set set set set of the operation worker.

Thread pool model

Well, here is our thread pool model. Now workers have it. Workers have been added to the pool. Now workers should be allowed to work. The way workers work is:

final void runWorker(Worker w) {

        Thread wt = Thread.currentThread();

        Runnable task = w.firstTask;

        w.firstTask = null;

        w.unlock(); // allow interrupts

        boolean completedAbruptly = true;

        try {

            while (task != null || (task = getTask()) != null) {

                w.lock();

                if ((runStateAtLeast(ctl.get(), STOP) ||

                    (Thread.interrupted() &&

                      runStateAtLeast(ctl.get(), STOP))) &&

                    !wt.isInterrupted())

                    wt.interrupt();

                try {

                    beforeExecute(wt, task);

                    Throwable thrown = null;

                    try {

                        task.run();

                    } catch (RuntimeException x) {

                        thrown = x; throw x;

                    } catch (Error x) {

                        thrown = x; throw x;

                    } catch (Throwable x) {

                        thrown = x; throw new Error(x);

                    } finally {

                        afterExecute(task, thrown);

                    }

                } finally {

                    task = null;

                    w.completedTasks++;

                    w.unlock();

                }

            }

            completedAbruptly = false;

        } finally {

            processWorkerExit(w, completedAbruptly);

        }

    }

DougLea is really annoying. It's so long code that it's still clear as it grows. After the thread start, the run method in the Worker class is executed, and the runWorker method is actually called inside the run method. The key to this method is the while loop, which is executed when the first Task or task taken from BlockingQueue is not empty. This method leaves us two hooks, beforeExecute and afterExecute. If we implement a subclass, we can customize these two methods to ourselves. The real part of execution is task.run().

In the code above, we focus on the getTask () method:

private Runnable getTask() {

        boolean timedOut = false; // Did the last poll() time out?

        for (;;) {

            int c = ctl.get();

            int rs = runStateOf(c);

            // Check if queue empty only if necessary.

            if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) {

                decrementWorkerCount();

                return null;

            }

            int wc = workerCountOf(c);

            boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;

            if ((wc > maximumPoolSize || (timed && timedOut))

                && (wc > 1 || workQueue.isEmpty())) {

                if (compareAndDecrementWorkerCount(c))

                    return null;

                continue;

            }

            try {

                Runnable r = timed ?workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : workQueue.take();

                if (r != null)

                    return r;

                timedOut = true;

            } catch (InterruptedException retry) {

                timedOut = false;

            }

        }

    }

The key to this method lies in the following sentence: Runnable r = timed? WorkQueue. poll (keep AliveTime, TimeUnit. NANOSECONDS): workQueue. take (); this sentence is the method of blocking queue elements, so this method is thread-safe.

OK, the above is the implementation of ThreadPool Executor. However, the most commonly used api for this kind of external exposure is the construction method:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue workQueue,

                              ThreadFactory threadFactory,

                              RejectedExecutionHandler handler) {

        if (corePoolSize < 0 ||

            maximumPoolSize <= 0 ||

            maximumPoolSize < corePoolSize ||

            keepAliveTime < 0)

            throw new IllegalArgumentException();

        if (workQueue == null || threadFactory == null || handler == null)

            throw new NullPointerException();

        this.corePoolSize = corePoolSize;

        this.maximumPoolSize = maximumPoolSize;

        this.workQueue = workQueue;

        this.keepAliveTime = unit.toNanos(keepAliveTime);

        this.threadFactory = threadFactory;

        this.handler = handler;

    }

There are many construction methods, each of which can implement different thread pools. The Alibaba Programming Protocol requires us to customize a certain number of thread pools directly by using the construction method.

To be continued...

Topics: Programming