别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

目录

一、CountDownLatch/CyclicBarrier/Semaphore使用过吗?

1.1 CountDownLatch

1.2 CyclicBarrier

1.3 Semaphore

二、阻塞队列知道吗?

2.1 队列+阻塞队列

2.2 为什么用?有什么好处?

2.3 BlockingQueue的核心方法

2.4 架构梳理+种类分析

2.4.1 架构介绍

2.4.2 种类分析

2.5 用在哪里

2.5.1 生产者消费者模式

三 线程池用过吗?ThreadPoolExecutor的理解?

3.1 为什么用线程池,优势

3.2 线程池如何使用?

3.2.1 架构说明

3.2.2 编码实现

3.2.3 ThreadPoolExecutor

3.3 线程池的几个重要参数介绍?7大参数

3.4 线程池的底层工作原理?

四 线程池用过吗?生产上如何设置合理参数

4.1 线程池的拒绝策略

4.1.1 是什么?

4.1.2 JDK内置的拒绝策略

4.1.3 以上内置拒绝策略均实现了RejectedExecutionHandler接口

4.2 工作中单一的/固定数的/可变的三种创建线程池的方法,用哪个多?超级大坑

4.3 在工作中是如何使用线程池的,是否自定义过线程池使用

4.4 合理配置线程池你是如何考虑的?

4.4.1 CPU密集型

4.4.2 IO密集型

五 死锁编码及定位分析

5.1 是什么

5.2 代码

5.3 解决


一、CountDownLatch/CyclicBarrier/Semaphore使用过吗?

1.1 CountDownLatch

先来一个班长锁门的小例子,教室里面一共有6位同学+1位班长在上自习,晚上只有等到6位同学都走了之后班长才能走,并且关灯锁门。

import java.util.concurrent.CountDownLatch;

/**
 * @author haitao.you
 * @date 2021/1/15
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "\t 上完自习,离开教室");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }

        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t *************************班长最后关门走人");

    }
}

执行结果:

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

demo2 结合使用枚举,模拟六国被灭过程代码

import java.util.concurrent.CountDownLatch;

/**
 * @author haitao.you
 * @date 2021/1/15
 */
public class CountDownLatchDemo {
    public static void main(String[] args) throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "\t 国,被灭");
                countDownLatch.countDown();
            }, CountryEnum.forEach_CountryEnum(i).getRetMessage()).start();
        }

        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t *************************秦帝国,一统华夏");

        System.out.println();
        System.out.println();

        System.out.println(CountryEnum.ONE);
        System.out.println(CountryEnum.ONE.getRetCode());
        System.out.println(CountryEnum.ONE.getRetMessage());
    }

    public static void closeDoor() throws InterruptedException {
        CountDownLatch countDownLatch = new CountDownLatch(6);

        for (int i = 1; i <= 6; i++) {
            new Thread(()->{
                System.out.println(Thread.currentThread().getName() + "\t 上完自习,离开教室");
                countDownLatch.countDown();
            }, String.valueOf(i)).start();
        }

        countDownLatch.await();
        System.out.println(Thread.currentThread().getName() + "\t *************************班长最后关门走人");
    }
}

枚举类:

public enum CountryEnum {
    ONE(1, "齐"), TWO(2, "楚"), THREE(3, "燕"), FOUR(4, "赵"), FIVE(5, "魏"), SIX(6, "韩");

    private Integer retCode;
    private String retMessage;

    public Integer getRetCode() {
        return retCode;
    }

    public void setRetCode(Integer retCode) {
        this.retCode = retCode;
    }

    public String getRetMessage() {
        return retMessage;
    }

    public void setRetMessage(String retMessage) {
        this.retMessage = retMessage;
    }

    CountryEnum(Integer retCode, String retMessage) {
        this.retCode = retCode;
        this.retMessage = retMessage;
    }

    public static CountryEnum forEach_CountryEnum(int index) {
        CountryEnum[] myArray = CountryEnum.values();
        for (CountryEnum element : myArray) {
            if (index == element.getRetCode()) {
                return element;
            }
        }

        return null;
    }
}

执行结果:

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

1.1.1 让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒

1.1.2 CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,调用线程会被阻塞。其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),当计数器的值变为零时,因调用await方法被阻塞的线程会被唤醒,继续执行。

1.2 CyclicBarrier

CyclicBarrier 的字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有被屏障拦截的线程才会继续干活,线程进入屏障通过CyclicBarrier的await()方法。

demo 集齐7颗龙珠,召唤神龙

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

/**
 * @author haitao.you
 * @date 2021/1/18
 */
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        //CyclicBarrier(int parties, Runnable barrierAction)
        CyclicBarrier cyclicBarrier = new CyclicBarrier(7, () -> {
            System.out.println("******召唤神龙");
        });
        for (int i = 1; i <=7; i++) {
            final int tempInt = i;
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"\t 收集到第:"+tempInt+"龙珠");
                try {
                    cyclicBarrier.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }, String.valueOf(i)).start();
        }
    }
}

执行结果:

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

1.3 Semaphore

信号量主要用于两个目的,一个是用于多个共享资源的互斥使用,另一个用于并发线程数的控制。

demo 6个车抢三个车位

import java.util.concurrent.Semaphore;
import java.util.concurrent.TimeUnit;

/**
 * @author haitao.you
 * @date 2021/1/18
 */
public class SemaphoreDemo {
    public static void main(String[] args) {
        Semaphore semaphore = new Semaphore(3); //模拟3个停车位

        for (int i = 1; i <= 6; i++) { //模拟6部汽车
            new Thread(()->{
                try {
                    semaphore.acquire();
                    System.out.println(Thread.currentThread().getName() + "\t抢到车位");
                    try { TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }
                    System.out.println(Thread.currentThread().getName() + "\t停车3秒后离开车位");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }finally {
                    semaphore.release();
                }

            }, String.valueOf(i)).start();
        }
    }
}

执行结果:

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

二、阻塞队列知道吗?

2.1 队列+阻塞队列

阻塞队列,顾名思义,首先它是一个队列,而一个阻塞队列在数据结构中锁起的作用大致如下图所示:

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

1)当阻塞队列是空时,从队列中获取元素的操作将会被阻塞。

     当阻塞队列是满时,往队列里添加元素的操作将会被阻塞。

2)试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。

     同样

     试图往已满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程从列中移除一个或者多个元素或者完全清空队列后使队列重新变得空闲起来并后续新增

2.2 为什么用?有什么好处?

在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒

为什么需要BlockingQueue

好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了

在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。

2.3 BlockingQueue的核心方法

方法类型 抛出异常 特殊值 阻塞 超时
插入 add(e) offer(e) put(e) offer(e,time,unit)
移除 remove() poll() take() poll(time,unit)
检查 element() peek() 不可用 不可用

 

抛出异常

  • 当阻塞队列满时,再往队列里面add插入元素会抛IllegalStateException: Queue full
  • 当阻塞队列空时,再往队列Remove元素时候回抛出NoSuchElementException

特殊值

  • 插入方法,成功返回true 失败返回false
  • 移除方法,成功返回元素,队列里面没有就返回null

一直阻塞

  • 当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产线程直到put数据or响应中断退出
  • 当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用

超时退出

  • 当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程就会退出

2.4 架构梳理+种类分析

2.4.1 架构介绍

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

2.4.2 种类分析

  • ArrayBlockingQueue :由数组结构组成的有界阻塞队列。

  • LinkedBlockingQueue :由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队列。

  • PriorityBlockingQueue :支持优先级排序的*阻塞队列。

  • DelayQueue:使用优先级队列实现的延迟*阻塞队列。

  • SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。                                     

  •                                      -----理论:SynchronousQueue没有容量。与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue。每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。

  •  

  • LinkedTransferQueue:由链表结构组成的*阻塞队列。

  • LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

代码demo:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;

/**
 * @author haitao.you
 * @date 2021/1/18
 * ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO(先进先出)原则对元素进行排序
 * LinkedBlockingQueue:一个基于链表结构的阻塞队列,此队列按FIFO(先进先出)排序元素,吞吐量通常要高于ArrayBlockingQueue.
 * SynchronousQueue:一个不存储元素的阻塞队列。每个插入操作必须等到另一个线程调用移除操作,否则插入操作一直处于阻塞状态,吞吐量通常要高
 *
 * 1 队列
 *
 * 2 阻塞队列
 *   2.1 阻塞队列有没有好的一面
 *
 *   2.2 不得不阻塞,你如何管理
 *
 *
 *
 */
public class BlockingQueueDemo {
    public static void main(String[] args) {

        BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);

//        System.out.println(blockingQueue.add("a"));
//        System.out.println(blockingQueue.add("b"));
//        System.out.println(blockingQueue.add("c"));

//        System.out.println(blockingQueue.element()); //检查队列空不空,以及队首元素是谁

//        System.out.println(blockingQueue.add("d")); //队列已满,继续添加,报java.lang.IllegalStateException: Queue full异常

        BlockingQueue<String> synchronousQueue = new SynchronousQueue<>();

        new Thread(()->{
            try {
                System.out.println(Thread.currentThread().getName() + "\t put 1");
                synchronousQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "\t put 2");
                synchronousQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "\t put 3");
                synchronousQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"AAA").start();

        new Thread(()->{
            try {
                //暂停一会儿线程
                try { TimeUnit.SECONDS.sleep(5); }catch (InterruptedException e){ e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName()+"\t"+synchronousQueue.take());

                try { TimeUnit.SECONDS.sleep(5); }catch (InterruptedException e){ e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName()+"\t"+synchronousQueue.take());

                try { TimeUnit.SECONDS.sleep(5); }catch (InterruptedException e){ e.printStackTrace(); }
                System.out.println(Thread.currentThread().getName()+"\t"+synchronousQueue.take());

            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"BBB").start();


    }
}

其中,同步队列执行结果如下:

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

2.5 用在哪里

2.5.1 生产者消费者模式

1)传统版 ProdConsumer_TraditionDemo

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

2.0版本代码:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

class ShareData{
    private int number = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();

    public void increment() throws InterruptedException {
        lock.lock();
        try{
            //判断
            while (number!=0){
                condition.await();
            }
            //干活
            number++;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            //通知唤醒
            condition.signalAll();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }

    public void decrement() throws InterruptedException {
        lock.lock();
        try{
            //判断
            while (number==0){
                condition.await();
            }
            //干活
            number--;
            System.out.println(Thread.currentThread().getName() + "\t" + number);
            //通知唤醒
            condition.signalAll();
        }catch(Exception e){
            e.printStackTrace();
        }finally{
            lock.unlock();
        }
    }


}
/**
 * @author haitao.you
 * @date 2021/1/19
 *
 * 题目:一个初始值为零的变量,两个线程对其交替操作,一个加1一个减1,来5轮
 *
 * 1      线程         操作          资源类
 * 2      判断         干活          通知
 * 3      防止虚假唤醒
 *
 */
public class ProdConsumer_TraditionDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    shareData.increment();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"AA").start();

        new Thread(()->{
            for (int i = 0; i < 5; i++) {
                try {
                    shareData.decrement();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        },"BB").start();

    }
}

执行结果如下:(生产一个消费一个,生产一个消费一个)

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

2)阻塞版 ProdConsumer_BlockQueueDemo

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;

class MyResource{
    private volatile boolean FLAG = true;//默认开启,进行生产+消费
    private AtomicInteger atomicInteger = new AtomicInteger();
    BlockingQueue<String> blockingQueue = null;

    public MyResource(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
        System.out.println(blockingQueue.getClass().getName());
    }

    public void myProd() throws Exception{
        String data = null;
        boolean retValue;
        while (FLAG){
            data = atomicInteger.incrementAndGet()+"";
            retValue = blockingQueue.offer(data,2L, TimeUnit.SECONDS);
            if (retValue){
                System.out.println(Thread.currentThread().getName() + "\t 插入队列" + data + "成功");
            }else{
                System.out.println(Thread.currentThread().getName() + "\t 插入队列" + data + "失败");
            }
            TimeUnit.SECONDS.sleep(1);
        }
        System.out.println(Thread.currentThread().getName() + "\t大老板叫停了,表示FLAG=false,生产动作结束");
    }

    public void myConsumer() throws Exception{
        while (FLAG){
            String result = null;
            result = blockingQueue.poll(2l,TimeUnit.SECONDS);
            if (null==result||result.equalsIgnoreCase("")){
                FLAG = false;
                System.out.println(Thread.currentThread().getName() + "\t 超过2秒钟没有取到蛋糕,消费退出");
                System.out.println();
                System.out.println();
                return;
            }
            System.out.println(Thread.currentThread().getName() + "\t 消费队列蛋糕" + result + "成功");
        }
    }

    public void stop() throws Exception{
        this.FLAG = false;
    }
}
/**
 * @author haitao.you
 * @date 2021/1/19
 *
 * volatile/CAS/atomicInteger/BlockQueue/线程交互/原子引用
 */
public class ProdConsumer_BlockQueueDemo {
    public static void main(String[] args) throws Exception {
        MyResource myResource = new MyResource(new ArrayBlockingQueue<>(3));

        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t 生产线程启动");
            try {
                myResource.myProd();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"Prod").start();


        new Thread(()->{
            System.out.println(Thread.currentThread().getName() + "\t 消费线程启动");
            try {
                myResource.myConsumer();
            }catch (Exception e){
                e.printStackTrace();
            }
        },"Consumer").start();

        try { TimeUnit.SECONDS.sleep(5); }catch (InterruptedException e){ e.printStackTrace(); }
        System.out.println();
        System.out.println();
        System.out.println();

        System.out.println("5秒钟时间到,大老板main线程叫停,活动结束");

        myResource.stop();


    }
}

执行结果如下:

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

实现多线程的第三种方式,实现Callable接口 Demo

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
import java.util.concurrent.TimeUnit;

class MyThread implements Callable<Integer>{

    @Override
    public Integer call() throws Exception {
        System.out.println("*************come in Callable");
        try { TimeUnit.SECONDS.sleep(3); }catch (InterruptedException e){ e.printStackTrace(); }
        return 1024;
    }
}

/**
 * @author haitao.you
 * @date 2021/1/21
 */
public class CallableDemo {
    public static void main(String[] args) throws ExecutionException, InterruptedException {

        //两个线程,一个main线程,一个是AAfutureTask

        //FutureTask(Callable<V> callable)
        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
        new Thread(futureTask, "AA").start();
        //new Thread(futureTask, "BB").start(); 多个线程来抢一个futrueTask只计算一次

        System.out.println(Thread.currentThread().getName()+"**********************");
        int result01 = 100;

//        while (!futureTask.isDone()){
//
//        }
        int result02 = futureTask.get(); //要求获得Callable线程的计算结果,如果没有计算完成就要去强求,会导致阻塞,直到计算完成

        System.out.println("********result:"+(result01+result02));
    }
}

三 线程池用过吗?ThreadPoolExecutor的理解?

3.1 为什么用线程池,优势

线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。

他的主要特点为:线程复用;控制最大并发数;管理线程。

第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。

第二:提高响应速度。当任务到达时,任务可以不需要等到线程创建就能立即执行。

第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一分配,调优和监控。

3.2 线程池如何使用?

3.2.1 架构说明

Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors(辅助工具类),ExecutorService,ThreadPoolExecutor这几个类。

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

3.2.2 编码实现

(1) 了解

 1.Executors.newScheduledThreadPool()

 2. java8新增 Executors.newWorkStealingPool(int) 使用目前机器上可用的处理器作为它的并行级别

(2)重点

1 Executors.newFixedThreadPool(int) -- 执行长期的任务,性能好很多

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

主要特点如下:

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。

newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue;

2 Executors.newSingleThreadExecutor() -- 一个任务一个任务执行的场景

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

3 Executros.newCachedThreadPool() 适用:执行很多短期异步的小程序或者负载较轻的服务器

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

主要特点如下:

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。

newCachedThreadPool 将corePoolSize设置为0,将maximunPoolSize设置为 Integer.MAX_VALUE,使用的 SynchronousQueue ,也就是说来了任务就创建线程运行,当线程空闲超过60s,就销毁

3.2.3 ThreadPoolExecutor

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

3.3 线程池的几个重要参数介绍?7大参数

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

1. corePoolSize:线程池中的常驻核心线程数

 (1)在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行任务,近似理解为今日当值线程

 (2)当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;

2. maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1

3. keepAliveTime:多余的空闲线程的存活时间。

    当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止。

    默认情况下:只有当线程池中的线程数大于corePoolSize时keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize。

4. unit:keepAliveTime的单位。

5. workQueue:任务队列,被提交但尚未被执行的任务。

6. threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可。

7. handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数(maximumPoolSize)时如何来拒绝请求执行的runnable的策略。

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

3.4 线程池的底层工作原理?

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

1.在创建了线程池后,等待提交过来的任务请求。

2.当调用execute()方法添加一个请求任务时,线程池会做如下判断:

   2.1 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;

   2.2 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;

   2.3 如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;

   2.4 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。 

3. 当一个线程完成任务时,它会从队列中取下一个任务来执行。

4. 当一个线程无事可做超过一定时间(keepAliveTime)时,线程池会判断:

    如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。

    所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。

四 线程池用过吗?生产上如何设置合理参数

4.1 线程池的拒绝策略

4.1.1 是什么?

等待队列已经排满了,再也塞不下新任务了。同时,线程池中的max线程也达到了,无法继续为新任务服务。这时候我们就需要拒绝策略机制合理的处理这个问题。

4.1.2 JDK内置的拒绝策略

AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行。

CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。

DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。

DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。

4.1.3 以上内置拒绝策略均实现了RejectedExecutionHandler接口

4.2 工作中单一的/固定数的/可变的三种创建线程池的方法,用哪个多?超级大坑

答案是一个都不用,我们生产上只能使用自定义的

Executors中JDK已经给你提供了,为什么不用?工程实践上的经验(来自阿里Java开发手册)

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

4.3 在工作中是如何使用线程池的,是否自定义过线程池使用

import java.util.concurrent.*;

/**
 * @author haitao.you
 * @date 2021/1/21
 * 第4种获得使用java多线程的方式:线程池
 */
public class MyThreadPoolDemo {
    public static void main(String[] args) {
        //自定义线程池
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,
                5,
                1L,
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<Runnable>(3),
                Executors.defaultThreadFactory(),
                new ThreadPoolExecutor.DiscardPolicy());
        try {
            for (int i = 0; i < 20; i++) {
                threadPool.execute(() -> {
                    System.out.println(Thread.currentThread().getName() + "\t 办理业务");
                });
            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool.shutdown();
        }
    }


    public static void threadPoolInit() {
        //ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5个处理线程
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();//一池1个处理线程
        ExecutorService threadPool = Executors.newCachedThreadPool();

        //模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
        try {
            for (int i = 0; i < 10; i++) {
                threadPool.execute(()->{
                    System.out.println(Thread.currentThread().getName() + "\t办理业务");
                });
                //暂停一会儿线程
                //try { TimeUnit.MILLISECONDS.sleep(200); }catch (InterruptedException e){ e.printStackTrace(); }

            }
        }catch (Exception e){
            e.printStackTrace();
        }finally {
            threadPool.shutdown();
        }
    }
}

4.4 合理配置线程池你是如何考虑的?

4.4.1 CPU密集型

CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。

CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上,无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些。

CPU密集型任务配置尽可能少的线程数量:

一般公式:CPU核数+1个线程的线程池

4.4.2 IO密集型

1、由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2

2、IO密集型,即该任务需要大量的IO,即大量的阻塞。

     在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。

     所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。

     IO密集型时,大部分线程都阻塞,故需要多配置线程数:

     参考公式:CPU核数 / (1 - 阻塞系数)             阻塞系数在0.8~0.9之间

     比如8核CPU:8 / (1-0.9) = 80 个线程数

五 死锁编码及定位分析

5.1 是什么

死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

5.2 代码

import java.util.concurrent.TimeUnit;

class Source implements Runnable {
    private String lockA;
    private String lockB;

    public Source(String lockA, String lockB) {
        this.lockA = lockA;
        this.lockB = lockB;
    }

    @Override
    public void run() {
        synchronized (lockA) {
            System.out.println(Thread.currentThread().getName() + "\t 线程获得锁" + lockA + "尝试获得锁" + lockB);
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (lockB) {
                System.out.println(Thread.currentThread().getName() + "线程获得锁" + lockB + "尝试获得锁" + lockA);
            }
        }
    }
}

/**
 * @author haitao.you
 * @date 2021/1/23
 */
public class DeadLockDemo {
    public static void main(String[] args) {
        String lockA = "lockA";
        String lockB = "lockB";
        new Thread(new Source(lockA,lockB),"ThreadAAA").start();
        new Thread(new Source(lockB,lockA),"ThreadBBB").start();
        /**
         * linux   ps -ef | grep xxxxx    ls -l
         * 
         * window下的java运行程序  也有类似ps的查看进程的命令,但是目前我们需要查看的只是java
         *        jps = java ps           jps -l
         * 
         */

    }
}

5.3 解决

1、jps命令定位进程号

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

2、jstack找到死锁查看

别忘记奔跑-CountDownLatch/CyclicBarrier/Semaphore 阻塞队列 线程池 死锁编码及定位

上一篇:UMD、CommonJS、ES Module、AMD、CMD模块的写法


下一篇:javascript – 是否有可用作库的V8 CommonJS实现?