Java线程池原理

线程池种类

(1)newFixedThreadPool:固定线程数量的线程池

​ 可控制线程最大并发数(同时执行的线程数);超出的线程会在队列中等待。

(2)newCachedThreadPool:支持最大为Integer.MAX_VALUE的线程数量

​ 线程数无限制;有空闲线程则复用空闲线程,若无空闲线程则新建线程;一定程序减少频繁创建/销毁线程,减少系统开销。

(3)newSingleThreadExecutor:线程数量为1的FixedThreadPool

​ 有且仅有一个工作线程执行任务;所有任务按照指定顺序执行,即遵循队列的入队出队规则。

(4)newScheduledThreadPool:支持定时以指定周期循环执行任务。

线程池构造参数

1、corePoolSize
线程池中的核心线程数。当提交一个任务时,线程池创建一个新线程执行任务,直到当前线程数等于corePoolSize;如果当前线程数为corePoolSize,继续提交的任务被保存到阻塞队列中,等待被执行。

2、maximumPoolSize

​ 线程池中允许的最大线程数。如果当前阻塞队列满了,且继续提交任务,则创建新的线程执行任务,前提是当前线程数小于maximumPoolSize。

3、keepAliveTime

​ 线程空闲时的存活时间。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,keepAliveTime参数也会起作用,直到线程池中的线程数为0。

4、unitkeepAliveTime参数的时间单位。

5、workQueue
任务缓存队列,用来存放等待执行的任务。如果当前线程数为corePoolSize,继续提交的任务就会被保存到任务缓存队列中,等待被执行。
BlockingQueue有以下三种选择:

(1)SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态。因此,如果线程池中始终没有空闲线程(任务提交的平均速度快于被处理的速度),可能出现无限制的线程增长。

(2)LinkedBlockingQueue:基于链表结构的阻塞队列,如果不设置初始化容量,其容量为Integer.MAX_VALUE,即为无界队列。因此,如果线程池中线程数达到了corePoolSize,且始终没有空闲线程(任务提交的平均速度快于被处理的速度),任务缓存队列可能出现无限制的增长。

(3)ArrayBlockingQueue:基于数组结构的有界阻塞队列,按FIFO排序任务。

6、threadFactory线程工厂,创建新线程时使用的线程工厂。

7、RejectedExecutionHandler

​ RejectedExecutionHandler:当运行线程数已达到maximumPoolSize,队列也已经装满时会调用该参数拒绝任务。
(1)AbortPolicy:默认的拒绝策略,抛弃任务,抛出RejectedExecutionException异常。
(2)CallerRunsPolicy:将任务回退到调用者,只用调用者所在线程来运行任务(调用execute方法的线程执行该任务)。
(3)DiscardOldestPolicy:丢弃队列里最老的一个任务,并执行当前任务;如果线程等待队列是一个优先级队列,那么将抛弃优先级最高的任务。
(4)DiscardPolicy:不处理,不抛弃异常,丢弃掉。
​ 也可以实现RejectedExecutionHandler接口自定义策略,如记录日志或持久化不能处理的任务。

线程池原理

任务提交的方式分为:execute()和submit(),后者提交的任务可以获取返回的结果,前者不可以。

1、执行线程的流程

(1)如果正在运行的线程数 < coreSize,马上创建线程执行该task,不排队等待;

(2)如果正在运行的线程数 >= coreSize,把该task放入阻塞队列;

(3)如果队列已满且正在运行的线程数 < maximumPoolSize,创建新的线程执行该task;

(4)如果队列已满且正在运行的线程数 >= maximumPoolSize,线程池调用handler的reject方法拒绝本次提交。

2、Worker

addWorker()

(1)原子性的增加workerCount。

(2)将用户给定的任务封装成为一个worker,并将此worker添加进workers集合中。

(3)启动worker对应的线程。

(4)若线程启动失败,回滚worker的创建动作,即从workers中移除新添加的worker,并原子性的减少workerCount。

构造函数

Worker(Runnable firstTask) {
            //设置AQS的state为-1,在执行runWorker()方法之前阻止线程中断
            setState(-1);
            //初始化第一个任务
            this.firstTask = firstTask;
            //利用指定的线程工厂创建一个线程,注意,参数是Worker实例本身this
            //也就是当执行start方法启动线程thread时,真正执行的是Worker类的run方法
            this.thread = getThreadFactory().newThread(this);
        }

runWorker()、线程复用

(1)运行第一个任务firstTask之后,循环调用getTask()方法获取任务,不断从任务缓存队列获取任务并执行;

(2)获取到任务之后就对worker对象加锁,保证线程在执行任务的过程中不会被中断,任务执行完会释放锁;

(3)在执行任务的前后,可以根据业务场景重写beforeExecute()和afterExecute()等Hook方法;

(4)执行通过getTask()方法获取到的任务;

(5)线程执行结束后,调用processWorkerExit()方法执行结束线程的一些清理工作。

中断线程方法interruptIfStarted(),shutdownnow方法调用的interruptWorkers方法中断所有线程,interruptWorkers获取全局锁mian.lock,用interruptIfStarted中断所有worker。

void interruptIfStarted() {
            Thread t;
            //AQS状态大于等于0,worker对应的线程不为null,且该线程没有被中断
            if (getState() >= 0 && (t = thread) != null && !t.isInterrupted()) {
                try {
                    t.interrupt();
                } catch (SecurityException ignore) {
                }
            }
        }

getTask()

​ 用来不断地从任务缓存队列获取任务并交给线程执行。

(1)线程池处于RUNNING状态,阻塞队列不为空,返回成功获取的task对象

(2)线程池处于SHUTDOWN状态,阻塞队列不为空,返回成功获取的task对象

(3)线程池状态大于等于STOP,返回null,回收线程

(4)线程池处于SHUTDOWN状态,并且阻塞队列为空,返回null,回收线程

(5)worker数量大于maximumPoolSize,返回null,回收线程

(6)线程空闲时间超时,返回null,回收线程

processWorkerExit()

​ 负责执行结束线程的一些清理工作

processWorkerExit()方法中主要调用了tryTerminate()方法

​ tryTerminate()方法的作用是尝试终止线程池,它会在所有可能终止线程池的地方被调用,满足终止线程池的条件有两个:线程池状态为STOP,或者为SHUTDOWN且任务缓存队列为空;工作线程数量为0。

​ 满足了上述两个条件之后,tryTerminate()方法获取全局锁,设置线程池运行状态为TIDYING,之后执行terminated()钩子方法,最后设置线程池状态为TERMINATED。

​ 至此,线程池运行状态变为TERMINATED,工作线程数量为0,workers已清空,且workQueue也已清空,所有线程都执行结束,线程池的生命周期到此结束。

并行递归

//顺序递归
void sequentialRecursive(List<Node<Integer>> nodes, Collection<Integer> results){
    for (Node<Integer> node:nodes) {
        results.add(node.compute());
        sequentialRecursive(nodes.getChildren(),results);
    }
}
//并行递归,遍历仍是串行的,执行compute计算是并行的,可减少计算总时间
void parallelRecursive(final Executor executor,List<Node<Integer>> nodes, 	               Collection<Integer> results){
    for (Node<Integer> node:nodes) {
        executor.execute(new Runnable() {
            @Override
            public void run() {
                results.add(node.compute());
            }
        });
        sequentialRecursive(executor, node.getChildren(), results);
    }
}

Thread.join,使用wait实现

// millis表示调用此线程的线程等待的时间
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) {
            // 只要当前线程活着,调用wait(0),表示主线程循环等待0秒,即一直等待。
            while (isAlive()) {
                wait(0);
            }
        } else {
            // wait()的作用是让当前线程等待,而这里的当前线程是指当前运行的线程。虽然是调用子线程的wait()方法,但是它是通过主线程去调用的;所以休眠的是主线程,而不是子线程
            // 在当前线程存活的条件下
            while (isAlive()) {
                long delay = millis - now;
                // 等待的时间超过millis后,退出循环,当前线程和主线程重新到竞态状态
                if (delay <= 0) {
                    break;
                }
                // 主线程等待delay时间
                wait(delay);
                now = System.currentTimeMillis() - base;
            }
        }
    }

​ wait()的作用是让“当前线程”等待,而这里的“当前线程”是指当前运行的线程。虽然是调用子线程的wait()方法,但是它是通过“主线程”去调用的;所以,休眠的是主线程,而不是“子线程”。Thread t只是一个对象,isAlive()判断当前对象(例子中的t对象)是否存活,wait()阻塞的是当前执行的线程。

​ Join方法实现是通过wait(),当main线程调用t.join时候,main线程会获得线程对象t的锁(wait 意味着拿到该对象的锁),调用该对象的wait(),直到该对象唤醒main线程 ,比如退出后。这就意味着main 线程调用t.join时,必须能够拿到线程t对象的锁。

awaitTermination

(1)如果等待的时间超过指定的时间,但是线程池中的线程运行完毕,那么awaitTermination()返回true。执行分线程已结束。

(2)如果等待的时间超过指定的时间,但是线程池中的线程未运行完毕,那么awaitTermination()返回false。不执行分线程已结束。

(3)如果等待时间没有超过指定时间,等待。可以用awaitTermination()方法来判断线程池中是否有继续运行的线程。

死锁

死锁:两个或两个以上的线程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力作用,它们都将无法推进下去。此时称系统处于死锁状态或系统产生了死锁,这些永远在互相等待的线程称为死锁进程。

饥饿:指阻塞队列中的某个线程可能因为优先级或者随机性问题一直无法获取锁,长时间等待。

活锁:多个线程获取不到资源,就放开已获得的资源,重试。相当于系统空转,一直在做无用功。

检测死锁

(1)tryLock(long time),一般当线程等待获取锁的时间超时后,返回失败信息,表示阻塞。

(2)当死锁发生时,JVM通常处于挂起状态,thread dump可以给出静态稳定的信息,查找死锁只需要查找有问题的线程。Java虚拟机死锁发生时,从操作系统上观察,虚拟机的CPU占用率为零,很快会从 top或prstat的输出中消失。这时可以收集thread dump,查找"waiting for monitor entry"的thread,如果大量thread都在等待给同一个地址上锁(因为对于Java,一个对象只有一把锁),这说明很可能死锁发生了。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值