005.聊聊线程与线程池

来源:互联网 发布:linux怎么改文件夹名字 编辑:程序博客网 时间:2024/06/05 02:07
作为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(wttask);
                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(taskthrown);
                }
            } finally {
                task = null;
                w.completedTasks++;
                w.unlock();
            }
        }
        completedAbruptly = false;
   finally {
        processWorkerExit(wcompletedAbruptly);
    }
}

下面看下具体的获取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 > || workQueue.isEmpty())) {
            if (compareAndDecrementWorkerCount(c))
                return null;
            continue;
        }

        try {
            Runnable r = timed ?
                workQueue.poll(keepAliveTimeTimeUnit.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(keepAliveTimeTimeUnit.NANOSECONDS) :
                workQueue.take();

主要看这两行代码
A  首先是判断是否需要考虑超时
B  然后根据是否超时 来选择 从队列获取任务的方式

超时判断主要考虑 wc > corePoolSize 这个,即poolsize 与 corePoolSize比较
意思就是说,如果poolsize > corePoolSize ,说明当前线程可能是多余的空闲线程
     只会等待一定的间隔从队列获取任务数据
否则就会直接调用队列的非超时获取方法,就会一直阻塞,直到有数据为止
阻塞操作主要是加锁操作的阻塞

因此,线程池里的corePool线程,是通过队列的阻塞操作与不断的任务处理来维持其生命的
当然除去异常退出外 
0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 快来完事游泳了怎么办 游泳时来月经了怎么办 经期第7天游泳了怎么办 来月经已经游了泳怎么办 月经来了要游泳怎么办 三个月宝宝趴着不会抬头怎么办 我的月经不完怎么办 游泳时怎么办能浮出水面 游泳时眼镜起雾怎么办 练瑜伽手臂变粗怎么办 孕妇喝了芬达怎么办 宫口开了但头高怎么办 整天坐着肚子越来越大怎么办 坐久了屁股变大怎么办 屁股久坐的黑印怎么办 练瑜伽小腿变粗怎么办 练瑜伽腿粗了怎么办 练完瑜伽腿粗怎么办 肚子大屁股大腿粗怎么办 甲状腺偏大弥漫性欠均匀怎么办 大腿后侧拉伤了怎么办 腰和臀部都疼怎么办 瑜伽后弯后腰疼怎么办? 吃了大蒜肝痛怎么办 练瑜伽前躯不行怎么办 月子里碰冷水了怎么办 月子手碰凉水了怎么办 月子里碰了冷水怎么办 顺产后下面会痛怎么办 产后肚子瘦不下来怎么办 出月子后臀部很疼怎么办 做月子时臀部疼怎么办 月子臀部大腿疼困怎么办 产后抱孩子腰疼怎么办 剖腹产后5年小肚子很大怎么办 压力大工作忙瘦了怎么办? 哺乳乳房一大一小怎么办 哺乳期乳房一大一小怎么办 上班之后奶少了怎么办 出了月子还流恶露怎么办? 剖腹产2年刀疤痒怎么办