美文网首页
(转)main函数只有线程池 程序不退出的原因

(转)main函数只有线程池 程序不退出的原因

作者: 云中人山 | 来源:发表于2020-09-23 17:49 被阅读0次
    private static int balance = 0;
    
        public static  void addBalance(int amount,Lock lock){
            System.out.println("currentThread is "+Thread.currentThread().getName()+" try to get lock");
            lock.lock();
            try {
                System.out.println("currentThread is "+Thread.currentThread().getName()+" locked");
               balance+=amount;
    
            }finally {
                lock.unlock();
                System.out.println("currentThread is "+Thread.currentThread().getName()+" unlocked");
            }
        }
    
    
        public static void main(String[] args) throws InterruptedException {
            ThreadPoolExecutor executor = new ThreadPoolExecutor(0,10,0, TimeUnit.NANOSECONDS,
                    new ArrayBlockingQueue<Runnable>(100));
            Lock lock = new ReentrantLock();
            for(int i=0;i<100;i++){
    //            CompletableFuture.runAsync(()->addBalance(1,lock));
                executor.submit(()->addBalance(1,lock));
            }
            while(balance<100){
                System.out.println("Time waiting");
                Thread.sleep(1000);
            }
            System.out.println(balance);
        }
    

    之后main函数一直未退出
    dump下来未发现死锁啥的
    但是守护线程有9个

    搜了一波,结果如下

    • 线程池的创建的时候,第一次 submit 操作会创建 Worker 线程(负责去拿任务处理),该线程里写了一个死循环,所以这个 Worker 线程不会死
    • Worker 线程在创建的时候,被设置成了 非守护线程 , thread.setDaemon(false)
    • 早在 JDK1.5 的时候,就规定了当所有非守护线程退出时, JVM 才会退出, Main 方法主线程和 Worker 线程都是非守护线程,所以不会死。

    为什么Worker线程不会死
    梦开始的地方先从初始化开始

    //该方法利用多台实例化了一个ThreadPoolExecutor线程池,该线程池继承了一个抽象类AbstractExecutorService
    ExecutorService service = Executors.newFixedThreadPool(10);
    //调用了ThreadPoolExecutor.submit方法也就是父类的AbstractExecutorService.submit,该方法内部会去调用execute()方法
    service.submit(() -> System.out.println("Hello "));
    

    于是我们定位到ThreadPoolExecutor类的execute方法,我截取了部分如下,注意代码中我打注释的地方

    public void execute(Runnable command) {
        ...
            //如果工作线程还没有超过核心线程数
            if (workerCountOf(c) < corePoolSize) {
                //去添加工作线程
                if (addWorker(command, true))
                    return;
            }
        ...
    

    线程池把每一个运行任务的工作线程抽象成了Worker,我们定位到内部addWorker方法

     ...
                //新建一个Worker
                w = new Worker(firstTask);
                final Thread t = w.thread;
                if (t != null) {
                    //下面的操作是将线程添加到工作线程集合里
                    final ReentrantLock mainLock = this.mainLock;
                    mainLock.lock();
                    try {
                        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;
    
    

    这时候一个工作线程也就跑起来了,可以去执行任务了,我们定位到ThreadPoolExecutor的内部类Worker的run方法里

    //该类调用了runWorker方法
    public void run() {
                runWorker(this);
            }
    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,会看这个Worker有没有任务,如果没有就会去取,这里是一个死循环,然后我们定位到getTask()方法,看他是怎么取任务的
                while (task != null || (task = getTask()) != null) {
                    w.lock();
                    if ((runStateAtLeast(ctl.get(), STOP) ||
                         (Thread.interrupted() &&
                          runStateAtLeast(ctl.get(), STOP))) &&
                        !wt.isInterrupted())
                        wt.interrupt();
                ...
     
        }
    

    这里解释了,工作线程其实不会死(超时时间不在本期范围内),我们继续定位到内部的getTask()方法,看他是怎么取任务的

    private Runnable getTask() {
                ...
                //有没有设置核心线程超时时间(默认没有)当前工作的线程数大于了线程池的核心线城市
                boolean timed = allowCoreThreadTimeOut || wc > corePoolSize;
                ...
                try {
                    Runnable r = timed ?
                        workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) :
                        //调用workQueue的Take方法,WorkQueue默认是一个BlockingQueue,所以调用take方法会导致当前工作线程阻塞掉,指到拿到
                        workQueue.take();
                    //如果拿到任务就返回
                    if (r != null)
                        return r;
                    timedOut = true;
                    ...
    
    

    小结:
    这里想说的有两点:

    工作线程不会死(不设置线程存活时间,默认情况下),会一直拿任务,所以工作线程会一直活着
    工作线程拿任务的时候,默认情况下,因为用的是BlockingQueue的take()拿不到任务会阻塞

    Worker线程如何被设置成非守护线程
    首先我们来到ThreadPoolExecutor的构造方法里

    public ThreadPoolExecutor(int corePoolSize,
                                  int maximumPoolSize,
                                  long keepAliveTime,
                                  TimeUnit unit,
                                  BlockingQueue<Runnable> workQueue) {
            this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,
                 Executors.defaultThreadFactory(), defaultHandler);
        }
    

    构造器里传入了一个ThreadFactory也就是Executors.defaultThreadFactory(),用来产生工作线程,一步一步的点进去我们会定位到Executors内部类DefaultThreadFactory的newThread方法

    public Thread newThread(Runnable r) {
                Thread t = new Thread(group, r,
                                      namePrefix + threadNumber.getAndIncrement(),
                                      0);
                //关键代码是这里,把线程设置成了非守护线程
                if (t.isDaemon())
                    t.setDaemon(false);
                if (t.getPriority() != Thread.NORM_PRIORITY)
                    t.setPriority(Thread.NORM_PRIORITY);
                return t;
            }
    

    然后我们看ThreadPoolExector方法去new Worker()的时候

    Worker(Runnable firstTask) {
                setState(-1); // inhibit interrupts until runWorker
                this.firstTask = firstTask;
                //这里的ThreadPool,就是上面提到的那个生产非守护线程的线程工厂
                this.thread = getThreadFactory().newThread(this);
            }
    
    

    看上面的注释下面的内容,为什么是非守护线程就真相大白了。

    最后将最开始的代码中,线程池的核心线程数改成0,那么JVM就会自动退出了

    相关文章

      网友评论

          本文标题:(转)main函数只有线程池 程序不退出的原因

          本文链接:https://www.haomeiwen.com/subject/dymoyktx.html