The basic use and principle of multithreading-thread pool

basic knowledge

The Executors framework is provided in jdk to obtain several types of thread pools, the classic four types of single (single case), fixed (fixed), cache (caching), and Schedule (timing). Working method (if not satisfied, go to the next step), create core thread---->queue storage--->create emergency thread--->reject Ce Yue.

The singleton and fixed thread pool queues use the LinkBlockQueue unbounded queue. When there are no available threads in the pool, they are put into the queue first. The cache uses a synchronous bounded queue. The queue is characterized by no capacity, that is, threads are created when there are tasks and will not be placed in the queue. Schedule uses a delay queue, which adds an execution priority attribute to the stored tasks.

       //ExecutorService executor = Executors.newFixedThreadPool(2); //2个线程 无界队列
       //ExecutorService executor = Executors.newSingleThreadExecutor(); //1个线程 无界队列
       //ExecutorService executor = Executors.newCachedThreadPool(); //5个线程 有界同步队列
       //ExecutorService executor = Executors.newScheduledThreadPool(3);

common method

submit (Callable task) -----> Execute the Callable task and return the Future object.

execute (Runnable tasj) ------> execute the Runbale task, no return value.

invokeAll (Collection<Callable> tasks)---->Execute batches of Callable tasks and return a List<Future> object, representing the execution result of each task.

invokeAny(Collection<Callable> tasks) ---->Execute one of the batch Callable tasks, and return an object object, representing the execution result of the task.

shutdown() ---> Stop the thread pool, if the task is already in the pool, it will be executed before stopping.

shutdownNow() ---> Stop the thread pool, if the task is already in the pool, it will be forced to stop.

 Underlying principle and custom thread pool implementation

Thread pool components, core thread size, maximum thread size, emergency thread expiration time, emergency thread time unit, blocking queue, rejection policy, thread factory name. According to these, a simple thread pool can be implemented locally, mainly for the design of blocking queues and thread classes.

Blocking queue :

        Design idea: The member variable has the queue size, the basic queue, when the queue is full, the external task should wait for the tasks in the queue to be taken out and executed before being put into the queue, otherwise it will be blocked. Similarly, when the queue task is empty, the thread pool will be closed after a certain period of time. In the process of executing tasks, locks are required to allow multiple threads to execute the same task, and two waitSets are required to release blocked tasks. So a lock and two WaitSets are needed. Method: enter the queue, exit the queue, and get the queue length. code show as below

/**
 * 阻塞队列
 * 设计 有界,有序,push及pull应保证线程安全
 */
@Slf4j
class MyBolckQueue<T> {
    private Deque<T> deque = new ArrayDeque<>();

    private ReentrantLock lock = new ReentrantLock();

    private Condition proSet = lock.newCondition();

    private Condition consumeSet = lock.newCondition();

    private int capCity;

    public MyBolckQueue(int capCity) {
        this.capCity = capCity;
    }

    /**
     * 拉取消息,如果消息为空,则消费者进入等待,如果消费了消息,则唤醒生产者队列可继续放任务
     *
     * @param
     */
    public T poll(long timeout, TimeUnit unit) {
        lock.lock();
        try {
            // 将 timeout 统一转换为 纳秒
            long nanos = unit.toNanos(timeout);
            while (deque.isEmpty()) {
                try {
                    // 返回值是剩余时间
                    if (nanos <= 0) {
                        return null;
                    }
                    nanos = consumeSet.awaitNanos(nanos);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            T t = deque.removeFirst();
            proSet.signal();
            return t;
        } finally {
            lock.unlock();
        }
    }

    /**
     * 存放任务,如果队列满了,则生产者等待。存入任务后需通知消费者消息
     *
     * @return
     */
    public void tryPut(RejectPolicy<T> rejectPolicy, T task) {
        lock.lock();
        try {
            // 判断队列是否满
            if (deque.size() == capCity) {
                rejectPolicy.reject(this, task);
            } else {  // 有空闲
                log.debug("加入任务队列 {}", task);
                deque.addLast(task);
                consumeSet.signal();
            }
            log.info("此时队列大小{}", deque.size());
        } finally {
            lock.unlock();
        }
    }

 thread class

        Design ideas: Member variables have Runable tasks, and methods can be rewritten with the run method. The constructor receives the task object.

class Worker extends Thread {
        private Runnable task;

        public Worker(Runnable task) {
            this.task = task;
        }

        @Override
        public void run() {
            //此处的run需要不断去执行新任务或者从队列中取任务
            while (task != null || (task = taskQueue.poll(1, timeUnit)) != null) {
                try {
                    log.debug("正在执行...{}", task);
                    task.run();
                } catch (Exception e) {
                    e.printStackTrace();
                } finally {
                    task = null;
                }
            }
            synchronized (workers) {
                log.debug("worker 被移除{}", this);
                workers.remove(this);
            }

        }
    }

thread pool class

    Design ideas: including thread collection, core thread size, timeout, time unit, rejection of overreach, and blocking queue. Among them, the thread basic object and the blocking queue have been designed, and the rejection policy is a functional interface, and the others are basic attributes. The method has the execute() method to execute the task, and the judgment logic is consistent with the logic of the JDK thread pool . code show as below

@Slf4j
class ThreadPool {
    private MyBolckQueue<Runnable> taskQueue;

    private HashSet<Worker> workers = new HashSet<>();

    private int coreSize;

    private TimeUnit timeUnit;

    private long expireTime;

    private RejectPolicy rejectPolicy;

    public ThreadPool(MyBolckQueue<Runnable> taskQueue, int coreSize, TimeUnit timeUnit, long expireTime, RejectPolicy rejectPolicy) {
        this.taskQueue = taskQueue;
        this.coreSize = coreSize;
        this.timeUnit = timeUnit;
        this.expireTime = expireTime;
        this.rejectPolicy = rejectPolicy;
    }

    /**
     * execute提交一个Runable任务
     *
     * @param task
     */
    public void excute(Runnable task) {
        //如果工作线程小于核心线程数,则可以创建一个线程对象,否则进入队列等待
        synchronized (workers) {
            //此处加锁,避免执行同一个任务
            if (workers.size() < coreSize) {
                Worker worker = new Worker(task);
                log.info("创建核心线程{}运行任务{}", worker, task);
                workers.add(worker);
                worker.start();
            } else {
                //尝试加入阻塞队列,如果不成功执行拒绝策越
                taskQueue.tryPut(rejectPolicy, task);
            }
        }
    }
}

Rejection strategy and test method 

@Slf4j
public class ExcutorPools {

    public static void main(String[] args) {
        MyBolckQueue objectMyBolckQueue = new MyBolckQueue<>(1);
        ThreadPool pool = new ThreadPool(
                objectMyBolckQueue,
                2,
                TimeUnit.SECONDS,
                1,
                (objectMyBolckQueue1, runnable1) -> {
                    log.info("结束等待{}", runnable1);
                }
        );
        for (int i = 0; i < 4; i++) {
            int j = i;
            pool.excute(() -> {
                log.debug("{}", j);
            });
        }
    }
}

@FunctionalInterface // 拒绝策略
interface RejectPolicy<T> {
    void reject(MyBolckQueue<T> queue, T task);
}

Test Results:

Tasks 0,1 create core threads to execute. Task 2 finds that there are already 2 core threads and enters the blocking queue. Task 3 finds that the queue is also full, and the execution rejects Ce Yue. After tasks 0 and 1 are executed, the tasks in the queue are executed. Finally, there is no task for a period of time, and the thread is destroyed.

 

Guess you like

Origin blog.csdn.net/weixin_42740540/article/details/124863789