• 005.聊聊线程与线程池


     

    转载。 https://blog.csdn.net/zhoumingp/article/details/52267575
    作为Java 搬砖人员,免不了要接触线程与线程池,今天就来聊聊线程与JDK里的线程池
     
    进入今天内容前,我们先思考下这么个问题:线程池里的线程是如何维持生命,不被GC掉?
     
    what is thread
    thread的使用
    thread的几种状态
    JDK线程池的使用
    线程池里的线程
     
    线程(英语:thread)是操作系统能够进行运算调度的最小单位
    一条线程指的是进程中一个单一顺序的控制流,一个进程中可以并发多个线程,每条线程并行执行不同的任务
    by wikipedia
     
    一般有两种方式来使用线程处理任务
    1.继承Thread,直接重写run方法,然后实例化Thread来启动线程
    2.实现Runnable接口,实现run方法,然后把Runnable作为参数入参实例化Thread来启动线程
     
    启动线程使用start方法,而不是run方法
    直接使用run方法相当于普通的对象方法调用,而没有创建线程来运行run方法
    可以对比下Thread的start方法和run方法源码,就可以发现猫腻
    public synchronized void start() {
        /**
         * This method is not invoked for the main method thread or "system"
         * group threads created/set up by the VM. Any new functionality added
         * to this method in the future may have to also be added to the VM.
         *
         * A zero status value corresponds to state "NEW".
         */
        if (threadStatus != 0)
            throw new IllegalThreadStateException();

        /* Notify the group that this thread is about to be started
         * so that it can be added to the group's list of threads
         * and the group's unstarted count can be decremented. */
        group.add(this);

        boolean started = false;
        try {
            start0();
            started = true;
        } finally {
            try {
                if (!started) {
                    group.threadStartFailed(this);
                }
            } catch (Throwable ignore) {
                /* do nothing. If start0 threw a Throwable then
                  it will be passed up the call stack */
            }
        }
    }
     
    public void run() {
        if (target != null) {
            target.run();
        }
    }
    start方法里会调用JVM本地方法start0 ,创建线程,后续将会由JVM来发起线程调度,去具体执行run方法
    而Thread的run方法就是对传入的Runnable对象的方法调用而已
     
    线程自身有4种状态(Java编程思想)
    1.new。线程创建后会短暂处于此状态
    2.Runnable。在此状态下,只要调度器把时间片分配给线程,线程就可以运行
    3.Blocked。线程能够运行,但有某个条件阻止它的运行
    4.Dead。处于死亡或终止状态的线程将不可再是可调度的,并且再也不会得到CPU时间,它的任务已经结束,或不再是可运行的
     
    也就是说,处于Dead的线程,接下来就等着被GC了
    所以,如果能够让线程不断的在Runnable、Blocked  运行状态间持续转换,就可以一直维持线程的“生命”了
     
     
    下面来聊下线程池,大概说下线程池的使用
    JDK线程池中一个构造方法
    关键的5个参数为corePoolSize、maximumPoolSize、workQueue、threadFactory、handler
    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler)
     
    通过线程池的处理流程分析,我们就能很好的理解这几个参数的意义
     
    1.当我们往线程池添加任务时,开始poolsize=0,需要创建线程进行任务处理
    1.1 不断的添加任务,也不断的创建线程,直到poolsize = corePoolSize
    2.当添加第 corePoolSize+1 个任务时,任务会被放到queue里,等待空闲线程进行处理
    2.1 一般我们会使用有界的队列,不停的添加任务到队列,直到队列满;
         使用无界队列就看内存什么时候耗尽了,这是一个非常危险的行为
    3.队列满的情况下,继续添加任务,这时,会重新开始创建线程进行任务处理
    3.1不断添加任务,直到poolsize = maximumPoolSize;这时停止线程创建
    4.当线程满负荷运行,依然有新任务过来,这时就需要拒绝策略了;
    一般有几种拒绝策略,如直接丢弃、由caller直接执行任务、抛出拒绝exception等
     
    通过threadFactory我们可以自定义thread的创建,比如重定义下线程的名字,方便识别
     
    了解了线程池的大概处理流程,就基本知道应该怎么使用线程池了;无非就具体参数值的设定,根据实际场景选择合适的参数组合
     
    下面来具体分析下线程池里的线程
    再回头开头提的那个问题:线程池里的线程是如何维持生命,不被GC掉?
     
    线程的执行方法本身非常好理解,不断的获取task,然后执行task
    看上去是个while循环,只要有task,线程就能不断执行了
    而如果没有task,线程就会完成使命,等待被回收了
    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的方法:getTask()
    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);

            // Are workers subject to culling?
            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;
            }
        }
    }
     
    getTask方法里是个死循环
    A  boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
     
    B            Runnable r = timed ?
                    workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                    workQueue.take();
     
    主要看这两行代码
    A  首先是判断是否需要考虑超时
    B  然后根据是否超时 来选择 从队列获取任务的方式
     
    超时判断主要考虑 wc > corePoolSize 这个,即poolsize 与 corePoolSize比较
    意思就是说,如果poolsize > corePoolSize ,说明当前线程可能是多余的空闲线程
         只会等待一定的间隔从队列获取任务数据
    否则就会直接调用队列的非超时获取方法,就会一直阻塞,直到有数据为止
    阻塞操作主要是加锁操作的阻塞
     
    因此,线程池里的corePool线程,是通过队列的阻塞操作与不断的任务处理来维持其生命的
    当然除去异常退出外 
  • 相关阅读:
    别再为了this发愁了------JS中的this机制
    offsetLeft,Left,clientLeft的区别
    下拉菜单的实现classList.add() classList.remove() class属性的添加和删除
    for循环
    html的基本数据类型(数字,字符串, 列表, 字典)
    定时器setInterval, innerText获取文本, charAt()获取单个字符串, substring(1, content.length)获取范围内的字符串, 实现字符串的滚动效果
    定时器 setInterval(‘function()’, 2000)
    parseInt 和 parseFloat 实现字符串转换为数字
    javarscript在HTML中的调用方式 (直接调用 和文件调用)
    input文本框 放上图片img 通过padding relative和absolute 的实现
  • 原文地址:https://www.cnblogs.com/shoshana-kong/p/10914247.html
Copyright © 2020-2023  润新知