lotus

贵有恒何必三更眠五更起 最无益只怕一日曝十日寒

  博客园  :: 首页  :: 新随笔  :: 联系 :: 订阅 订阅  :: 管理

 

转载。 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线程,是通过队列的阻塞操作与不断的任务处理来维持其生命的
当然除去异常退出外 
posted on 2019-05-23 20:16  白露~  阅读(215)  评论(0编辑  收藏  举报