并发编程之Java 线程池

1.线程的理解

用户线程(ULT):用户程序实现,不依赖操作系统核心,应用提供创建,同步,调度和管理线程的函数来控制用户线程。不需要用户态/核心态切换,速度快。内核对ULT无感知,线程阻塞则进程阻塞。
内核线程(KLT):系统内核管理线程,内核保存线程的状态和上下文信息,线程阻塞不会引起进程阻塞。在多处理器系统上,多线程再多处理器上并行运行。线程的创建,调度和管理由内核完成,效率比ULT要慢,比进程操作快。
在这里插入图片描述

2.Java线程与系统内核线程

Java线程创建是依赖于系统内核,通过JVM调用系统库创建内核线程,内核线程与Java-Thread是1:1的映射关系。
在这里插入图片描述

3.线程的执行原理解析

线程是稀缺资源,他的创建与销毁是一个相对偏重且消耗资源的操作,而java线程依赖于内核线程,创建线程需要进行操作系统状态切换,为了避免资源过度消耗需要设法重用线程执行多个任务。线程池就是一个线程缓存,负责对线程进行统一分配,调优与监控。

什么时候使用线程池?

单个任务处理时间比较短
需要处理的任务数量很大

线程池的优势

重用存在的线程,减少线程的创建,消亡的开销,提高性能
提高响应速度,当任务到达的时候,任务可以不需要等待线程创建就能立即执行。
提高线程的可管理性,可统一管理,调优与监控。

/**
 * @author yhd
 * @createtime 2020/9/5 10:26
 */
public class DemoG {
    public static void main(String[] args) {
        ThreadPoolExecutor pool = new ThreadPoolExecutor(
                2,
                3,
                5,
                TimeUnit.SECONDS,
                new ArrayBlockingQueue<>(2),
                Executors.defaultThreadFactory(),
                //new ThreadPoolExecutor.AbortPolicy()   //抛异常
                //new ThreadPoolExecutor.CallerRunsPolicy()  //main	线程办理业务!
                //new ThreadPoolExecutor.DiscardOldestPolicy()  //就处理能处理的,剩下的老的直接丢了。
                new ThreadPoolExecutor.DiscardPolicy()  //如果新来的处理不了,直接就扔了。
        );
        for (int i = 0; i < 9; i++) {
            pool.execute(new Lalala(i));
        }
        pool.shutdown();
    }
}
class Lalala implements Runnable{
    private int flag;
    public Lalala(int flag){
        this.flag=flag;
    }

    @Override
    public void run() {
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("线程"+flag+"正在执行任务。。。。");
    }
}

在这里插入图片描述

阻塞队列

FIFO
1.在任意时刻,不管并发有多高,永远只有一个线程能够进行队列的入队和出队操作!线程安全的队列。
2.队列分为有界队列和无界队列。
队列满,只能进行出队操作,所有入队的操作必须等待,也就是被阻塞。
队列空,只能进行入队操作,所有出队的操作必须等待,也就是被阻塞。

线程池7大核心参数

int corePoolSize, 线程池中的常驻核心线程数
int maximumPoolSize, 能够容纳同时执行的最大线程数,必须大于1
long keepAliveTime, 多余空闲线程存活时间
TimeUnit unit, 上个参数的单位
BlockingQueue<Runnable> workQueue, 任务队列,被提交但是尚未执行的任务
ThreadFactory threadFactory, 表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认 RejectedExecutionHandler handler,拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大连接数时,如何来拒绝请求执行的runnable的策略。

线程池和的执行流程

1、在创建了线程池后,线程池中的线程数为零。

2、当调用execute ()方法添加一个请求任务时,线程池会做出如下判断:

2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
2.3如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4如果队列满了且正在运行的线程数量大于或等于max imumPoolSize,那么线程池会启动饱和拒绝策略来执行。

3、 当一个线程完成任务时,它会从队列中取下一个任务来执行。

4、当一个线程无事可做超过一定的时间(keepAliveTime) 时,线程会判断:

如果当前运行的线程数大于corePoolSize.那么这个线程就被停掉。
所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
在这里插入图片描述
排个坑,假如现在核心线程都在处理任务,等待队列也满了,再来任务肯定会用非核心线程执行,但是当他执行完的时候,他不会执行等待队列的任务,因为等待队列的任务都在等待着核心线程来执行

线程池是如何实现复用的

老规矩,一言不合追源码~~~~
在这里插入图片描述

    private void processWorkerExit(Worker w, boolean completedAbruptly) {
        if (completedAbruptly) // If abrupt, then workerCount wasn't adjusted
            decrementWorkerCount();

        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            completedTaskCount += w.completedTasks;
            workers.remove(w);
        } finally {
            mainLock.unlock();
        }

        tryTerminate();

        int c = ctl.get();
        if (runStateLessThan(c, STOP)) {
            if (!completedAbruptly) {
                int min = allowCoreThreadTimeOut ? 0 : corePoolSize;
                if (min == 0 && ! workQueue.isEmpty())
                    min = 1;
                if (workerCountOf(c) >= min)
                    return; // replacement not needed
            }
            addWorker(null, false);
        }
    }

从源码可以看到,runWorker()方法里面的finally里面执行了processWorkerExit()方法,点进去看看这个方法:
这个方法里先判断线程池的状态,状态是running或者shutdown的时候,才满足条件,然后给他设置一个超时时间,判断如果当前工作线程数>核心线程数,直接就返回线程池。

4.java.util.concurrent.Executors

这是JUC提供的一个工具类帮助我们获取线程池。

常用线程池

   `1.Executors.newFixedThreadPool();`

执行长期任务性能好,创建一个线程池,一池有N个固定的线程,有固定的线程数的线程
2.Executors.newSingleThreadExecutor();
一个任务一个任务的执行,一池一线程
3.Executors.newCachedThreadPool();
执行很多短期异步任务,线程池根据需要创建新线程,但在先前构建的线程可用时将重用它们。可扩容,遇强则强。
4.newScheduledThreadPool();
执行定时任务的池子

    public ScheduledThreadPoolExecutor(int corePoolSize,
                                       ThreadFactory threadFactory) {
        super(corePoolSize, Integer.MAX_VALUE, 0, NANOSECONDS,
              new DelayedWorkQueue(), threadFactory);
    }

相当于无限创建线程了。

实际用的

自己手写。原因:
线程池不允许使用Executors去创建,而是通过ThreadPoolExecutor的方式,规避资源消耗。
说明:Executors返回的线程池对象的弊端如下:
1)FixedThreadPool和SingleThreadPool;
允许的请求队列长度为Integer的最大值,可能会堆积大量的请求,从而导致OOM。
2)CachedThreadPool和ScheduledThreadPool
允许的创建线程数量为Integer最大值,可能会创建大量的线程,从而导致OOM。
详情参考《阿里开发手册-泰山版》

5.线程池的五种状态

1.running
能接受新的任务以及处理已经添加的任务
2.shutdown
不接受新的任务,可以处理已经添加的任务
3.stop
不接受新任务,不处理已经添加的任务,并且中断正在处理的任务
4.tidying
所有的任务已经终止,ctl记录的任务数量为0,ctl负责记录线程池的运行状态与活动线程数量
5.terminated
线程池彻底终止,则线程池转变为terminated状态。
线程池的底层实际上是用integer的高3位来表示这个线程池的状态

    private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0));
    //由这个源码可见,他是吧int的高三位记录线程池的生命状态,低29位记录当前工作线程数。
    private static final int COUNT_BITS = Integer.SIZE - 3;
    //COUNT_BITS:32-3=29
    private static final int CAPACITY   = (1 << COUNT_BITS) - 1;
	
    // runState is stored in the high-order bits
    private static final int RUNNING    = -1 << COUNT_BITS;
    //-1=1111 1111 1111 1111 1111 1111 1111 1111
    //RUNNING =111 
    private static final int SHUTDOWN   =  0 << COUNT_BITS;
    //000
    private static final int STOP       =  1 << COUNT_BITS;
    //001
    private static final int TIDYING    =  2 << COUNT_BITS;
    //010
    private static final int TERMINATED =  3 << COUNT_BITS;
    //011

    // Packing and unpacking ctl
    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; }

在这里插入图片描述

pool.shutdown(),pool.shutdownNow();

    public void shutdown() {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(SHUTDOWN);
            interruptIdleWorkers();
            onShutdown(); // hook for ScheduledThreadPoolExecutor
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
    }
    private void interruptIdleWorkers() {
        interruptIdleWorkers(false);
    }
    private void interruptIdleWorkers(boolean onlyOne) {
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            for (Worker w : workers) {
                Thread t = w.thread;
                if (!t.isInterrupted() && w.tryLock()) {
                    try {
                        t.interrupt();
                    } catch (SecurityException ignore) {
                    } finally {
                        w.unlock();
                    }
                }
                if (onlyOne)
                    break;
            }
        } finally {
            mainLock.unlock();
        }
    }

    public List<Runnable> shutdownNow() {
        List<Runnable> tasks;
        final ReentrantLock mainLock = this.mainLock;
        mainLock.lock();
        try {
            checkShutdownAccess();
            advanceRunState(STOP);
            interruptWorkers();
            tasks = drainQueue();
        } finally {
            mainLock.unlock();
        }
        tryTerminate();
        return tasks;
    }
    private List<Runnable> drainQueue() {
        BlockingQueue<Runnable> q = workQueue;
        ArrayList<Runnable> taskList = new ArrayList<Runnable>();
        q.drainTo(taskList);
        if (!q.isEmpty()) {
            for (Runnable r : q.toArray(new Runnable[0])) {
                if (q.remove(r))
                    taskList.add(r);
            }
        }
        return taskList;
    }

他俩都是直接打断正在执行的任务(PS:shutdown其实是尝试停止所有的正在执行或暂停任务的线程),但是shutdown会分出一个线程将等待队列的任务执行完,而shutdownNow是直接将等待队列的任务都不执行了,给你返回回来。
在这里插入图片描述

    /**
     * Interrupts threads that might be waiting for tasks (as
     * indicated by not being locked) so they can check for
     * termination or configuration changes. Ignores
     * SecurityExceptions (in which case some threads may remain
     * uninterrupted).
     *
     * @param onlyOne If true, interrupt at most one worker. This is
     * called only from tryTerminate when termination is otherwise
     * enabled but there are still other workers.  In this case, at
     * most one waiting worker is interrupted to propagate shutdown
     * signals in case all threads are currently waiting.
     * Interrupting any arbitrary thread ensures that newly arriving
     * workers since shutdown began will also eventually exit.
     * To guarantee eventual termination, it suffices to always
     * interrupt only one idle worker, but shutdown() interrupts all
     * idle workers so that redundant workers exit promptly, not
     * waiting for a straggler task to finish.
     */

这是shutdown里面的一段注释,大概就是尝试打断当前正在执行的任务,但是要拿出一个线程执行等待队列的任务,当只有这一个线程并且任务执行完了,才会退出。

execute()和submit()

    public void execute(Runnable command) {
        if (command == null)
            throw new NullPointerException();
        /*
         * Proceed in 3 steps:
         *
         * 1. If fewer than corePoolSize threads are running, try to
         * start a new thread with the given command as its first
         * task.  The call to addWorker atomically checks runState and
         * workerCount, and so prevents false alarms that would add
         * threads when it shouldn't, by returning false.
         *
         * 2. If a task can be successfully queued, then we still need
         * to double-check whether we should have added a thread
         * (because existing ones died since last checking) or that
         * the pool shut down since entry into this method. So we
         * recheck state and if necessary roll back the enqueuing if
         * stopped, or start a new thread if there are none.
         *
         * 3. If we cannot queue task, then we try to add a new
         * thread.  If it fails, we know we are shut down or saturated
         * and so reject the task.
         */
        int c = ctl.get();
        if (workerCountOf(c) < corePoolSize) {
            if (addWorker(command, true))
                return;
            c = ctl.get();
        }
        if (isRunning(c) && workQueue.offer(command)) {
            int recheck = ctl.get();
            if (! isRunning(recheck) && remove(command))
                reject(command);
            else if (workerCountOf(recheck) == 0)
                addWorker(null, false);
        }
        else if (!addWorker(command, false))
            reject(command);
    }

源码的注释写的太友好了,我都懒得翻译成中文了。

        /**
     * Checks if a new worker can be added with respect to current
     * pool state and the given bound (either core or maximum). If so,
     * the worker count is adjusted accordingly, and, if possible, a
     * new worker is created and started, running firstTask as its
     * first task. This method returns false if the pool is stopped or
     * eligible to shut down. It also returns false if the thread
     * factory fails to create a thread when asked.  If the thread
     * creation fails, either due to the thread factory returning
     * null, or due to an exception (typically OutOfMemoryError in
     * Thread.start()), we roll back cleanly.
     *
     * @param firstTask the task the new thread should run first (or
     * null if none). Workers are created with an initial first task
     * (in method execute()) to bypass queuing when there are fewer
     * than corePoolSize threads (in which case we always start one),
     * or when the queue is full (in which case we must bypass queue).
     * Initially idle threads are usually created via
     * prestartCoreThread or to replace other dying workers.
     *
     * @param core if true use corePoolSize as bound, else
     * maximumPoolSize. (A boolean indicator is used here rather than a
     * value to ensure reads of fresh values after checking other pool
     * state).
     * @return true if successful
     */
    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
            }
        }

        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 {
                    // Recheck while holding lock.
                    // Back out on ThreadFactory failure or if
                    // shut down before lock acquired.
                    int rs = runStateOf(ctl.get());

                    if (rs < SHUTDOWN ||
                        (rs == SHUTDOWN && firstTask == null)) {
                        if (t.isAlive()) // precheck that t is startable
                            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;
    }

在这里插入图片描述
这是一个内部类,再看他里面的方法:

        Worker(Runnable firstTask) {
            setState(-1); // inhibit interrupts until runWorker
            this.firstTask = firstTask;
            this.thread = getThreadFactory().newThread(this);
        }

        /** Delegates main run loop to outer runWorker  */
        public void run() {
            runWorker(this);
        }

由此可见,实际上调用线程工厂创建的线程是在worker的构造器里面创建的,他传递的是当前对象,这样就会执行他的run方法,run里面执行的方法传递的还是他的当前对象。

    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 pool is stopping, ensure thread is interrupted;
                // if not, ensure thread is not interrupted.  This
                // requires a recheck in second case to deal with
                // shutdownNow race while clearing interrupt
                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);
        }
    }

实际上里面执行的是task.run()


在这里插入图片描述
在这里插入图片描述
其实一追源码,我去?submit底层原来也是调用的execute,但是人家还返回了一个FutureTask句柄,我们拿到这个句柄以后就可以通过他的get方法获取结果,这个get方法是阻塞式的,当获取到结果就可以把结果交给需要的线程然后唤醒他继续执行。
在这里插入图片描述

FutureTask

追一追futureTask的源码~~~~
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
在这里插入图片描述
由源码可知,FutureTask类实现了Runnable接口。所以FutureTask类一定实现了run方法,我们去查看他的run(),run()大概的流程就是,定义一个叫做result的变量接收结果,他会判断你是否已经执行了run方法,如果执行了,把result的值设置进去,所以调用get才可以获取到值。这里面执行的是c的call方法,c是什么?c=callable,看一下构造器,发现callable实际上是一个成员变量,将构造器传进来的callable接口赋值给了成员变量。
假如传递的是一个Runnable接口呢?
继续追源码~~~

    public FutureTask(Runnable runnable, V result) {
        this.callable = Executors.callable(runnable, result);
        this.state = NEW;       // ensure visibility of callable
    }
    public static <T> Callable<T> callable(Runnable task, T result) {
        if (task == null)
            throw new NullPointerException();
        return new RunnableAdapter<T>(task, result);
    }
    static final class RunnableAdapter<T> implements Callable<T> {
        final Runnable task;
        final T result;
        RunnableAdapter(Runnable task, T result) {
            this.task = task;
            this.result = result;
        }
        public T call() {
            task.run();
            return result;
        }
    }

实际上它是将runnable接口转化成了callable接口,只不过返回值为空而已


join()

先来看个demo

    public static void main(String[] args) throws InterruptedException {

        final ArrayList<Integer> list = new ArrayList<>();

        for (int i = 0; i < 10000; i++){
            Thread thread = new Thread(() -> {
                list.add(new Random().nextInt());
            });
            thread.start();
            //thread.join();
        }
        System.out.println(list.size());

    }
    输出结果竟然小于10000,当我把注释的join方法打开,输出结果就等于一万了,为啥呢?
    首先小于10000是因为主线程结束了,但是还有几个线程没执行完,也就是还没添加到list。所以list的size小于10000.

为啥加了join又等于10000了呢?追一下源码~~~~

    public final void join() throws InterruptedException {
        join(0);
    }
    public final synchronized void join(long millis)
    throws InterruptedException {
        long base = System.currentTimeMillis();
        long now = 0;

        if (millis < 0) {
            throw new IllegalArgumentException("timeout value is negative");
        }

        if (millis == 0) {
            while (isAlive()) {
                wait(0);
            }
        } else {
            while (isAlive()) {
                long delay = millis - now;
                if (delay <= 0) {
                    break;
                }
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

从源码可以看出,join()底层时调用了wait()的,也就是主线程会等待所有线程执行结束之后(准确的说应该是执行到join()的地方)在继续执行。


补充一个概念:句柄
句柄实际上是C语言的,Java中用句柄来表示一个引用或者指针,java万事万物皆对象,大部分对对象的操作都是通过操作引用来操作对象的,我们把这个引用就叫做句柄。

猜你喜欢

转载自blog.csdn.net/weixin_45596022/article/details/108415626