尚硅谷经典Java面试题第二季(二)

二十七、阻塞队列接口结构和实现类

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

                线程1往阻塞队列中添加元素,而线程2从阻塞队列中移除元素。

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

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

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

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

为什么用?有什么好处?

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

        为什么需要BlockingQueue

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

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

架构介绍

种类分析:

  • ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
  • LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为Integer.MAX_VALUE)阻塞队列。
  • PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
  • DelayQueue:使用优先级队列实现妁延迟无界阻塞队列。
  • SynchronousQueue:不存储元素的阻塞队列。
  • LinkedTransferQueue:由链表结构绒成的无界阻塞队列。
  • LinkedBlockingDeque:由链表结构组成的双向阻塞队列。

BlockingQueue的核心方法

方法类型

抛出异常

特殊值(Boolean)

阻塞

超时

插入

add(e)

offer(e)

put(e)

offer(e,time,unit)

移除

remove()

poll()

take()

poll(time,unit)

检查

element()

peek()

不可用

不可用

性质

抛出异常:

当阻塞队列满时:在往队列中add插入元素会抛出 IIIegalStateException:Queue full

当阻塞队列空时:再往队列中remove移除元素,会抛出NoSuchException

element()获取队首元素

特殊值:

插入方法,成功true,失败false

移除方法:成功返回出队列元素,队列没有就返回空

阻塞:

当阻塞队列满时,生产者继续往队列里put元素,队列会一直阻塞生产线程直到put数据or响应中断退出。

当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用。

超时退出:

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

二十八、阻塞队列之同步SynchronousQueue队列

        SynchronousQueue没有容量。

        与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue。

        每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。

package com.study.mainshi1;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.TimeUnit;
public class BlockingQueueDemo {
    public static void main(String[] args) throws InterruptedException {
        BlockingQueue<String> blockingQueue = new SynchronousQueue<>();
        new Thread(() -> {
            try {
                System.out.println(Thread.currentThread().getName() + "\t put 1");
                blockingQueue.put("1");
                System.out.println(Thread.currentThread().getName() + "\t put 2");
                blockingQueue.put("2");
                System.out.println(Thread.currentThread().getName() + "\t put 3");
                blockingQueue.put("3");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }, "AAA").start();
        new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5L);
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());

                TimeUnit.SECONDS.sleep(5L);
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());

                TimeUnit.SECONDS.sleep(5L);
                System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take());
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "BBB").start();
    }
}

二十九、线程通信之生产者消费者传统版

阻塞队列用在哪里?

  • 生产者消费者模式
    • 传统版(synchronized, wait, notify)
    • 阻塞队列版(lock, await, signal)
  • 线程池
  • 消息中间件

实现一个简单的生产者消费者模式

package com.study.mainshi1;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
class ShareData {//资源类
    private int num = 0;
    private Lock lock = new ReentrantLock();
    private Condition condition = lock.newCondition();
    public void increment() throws Exception {
        lock.lock();
        try {
            //1 判断
            while (num != 0) {
                //等待,不能生产
                condition.await();
            }
            //2 干活
            num++;
            System.out.println(Thread.currentThread().getName() + "\t" + num);
            //3 通知唤醒
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
    public void decrement() throws Exception {
        // 同步代码块,加锁
        lock.lock();
        try {
            // 判断
            while (num == 0) {
                // 等待不能消费
                condition.await();
            }
            // 干活
            num--;
            System.out.println(Thread.currentThread().getName() + "\t" + num);
            // 通知 唤醒
            condition.signalAll();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}
/**
 * 题目:一个初始值为0的变量,两个线程对其交替操作,一个加1一个减1,来5轮
 */
public class TraditionalProducerConsumerDemo {
    public static void main(String[] args) {
        ShareData shareData = new ShareData();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    shareData.increment();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "AA").start();
        new Thread(() -> {
            for (int i = 0; i < 5; i++) {
                try {
                    shareData.decrement();
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }, "BB").start();
    }
}

三十、Synchronized和Lock有什么区别

1.synchronized属于JVM层面,属于java的关键字

        monitorenter(底层是通过monitor对象来完成,其实wait/notify等方法也依赖于monitor对象 只能在同步块或者方法中才能调用 wait/ notify等方法)

        Lock是具体类(java.util.concurrent.locks.Lock)是api层面的锁

2.使用方法:

        synchronized:不需要用户去手动释放锁,当synchronized代码执行后,系统会自动让线程释放对锁的占用。

        ReentrantLock:则需要用户去手动释放锁,若没有主动释放锁,就有可能出现死锁的现象,需要lock() 和 unlock() 配置try catch语句来完成

3.等待是否中断

        synchronized:不可中断,除非抛出异常或者正常运行完成。

        ReentrantLock:可中断,可以设置超时方法

        设置超时方法,trylock(long timeout, TimeUnit unit)

        lockInterrupible() 放代码块中,调用interrupt() 方法可以中断

4.加锁是否公平

        synchronized:非公平锁

        ReentrantLock:默认非公平锁,构造函数可以传递boolean值,true为公平锁,false为非公平锁

5.锁绑定多个条件Condition

        synchronized:没有,要么随机,要么全部唤醒

        ReentrantLock:用来实现分组唤醒需要唤醒的线程,可以精确唤醒,而不是像synchronized那样,要么随机,要么全部唤醒

三十一、锁绑定多个条件Condition

实现场景

多线程之间按顺序调用,实现 A-> B -> C 三个线程启动,要求如下:
AA打印5次,BB打印10次,CC打印15次
紧接着
AA打印5次,BB打印10次,CC打印15次

来10轮

package com.atguigu.springcloud.practice;

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

class ShareResource {
    // A 1   B 2   C 3
    private int number = 1;
    // 创建一个重入锁
    private Lock lock = new ReentrantLock();
    // 这三个相当于备用钥匙
    Condition condition1 = lock.newCondition();
    Condition condition2 = lock.newCondition();
    Condition condition3 = lock.newCondition();

    public void print5() {
        lock.lock();
        try {
            // 判断
            while (number != 1) {
                // 不等于1,需要等待
                condition1.await();
            }
            for (int i = 0; i < 5; i++) {
                System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);
            }
            // 唤醒 (干完活后,需要通知B线程执行)
            number = 2;
            // 通知2号去干活了
            condition2.signal();
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print10() {
        lock.lock();
        try {
            // 判断
            while (number != 2) {
                // 不等于1,需要等待
                condition2.await();
            }

            // 干活
            for (int i = 0; i < 10; i++) {
                System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);
            }

            // 唤醒 (干完活后,需要通知C线程执行)
            number = 3;
            // 通知2号去干活了
            condition3.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void print15() {
        lock.lock();
        try {
            // 判断
            while (number != 3) {
                // 不等于1,需要等待
                condition3.await();
            }

            // 干活
            for (int i = 0; i < 15; i++) {
                System.out.println(Thread.currentThread().getName() + "\t " + number + "\t" + i);
            }

            // 唤醒 (干完活后,需要通知C线程执行)
            number = 1;
            // 通知1号去干活了
            condition1.signal();

        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

/**
 * 多线程之间按顺序调用,实现 A-> B -> C 三个线程启动,要求如下:
 * AA打印5次,BB打印10次,CC打印15次
 * 紧接着
 * AA打印5次,BB打印10次,CC打印15次
 * …
 * 来10轮
 */
public class SynchronizedAndReentrantLockDemo {
    public static void main(String[] args) {

        ShareResource shareResource = new ShareResource();
        int num = 10;
        new Thread(() -> {
            for (int i = 0; i < num; i++) {
                shareResource.print5();
            }
        }, "A").start();
        new Thread(() -> {
            for (int i = 0; i < num; i++) {
                shareResource.print10();
            }
        }, "B").start();
        new Thread(() -> {
            for (int i = 0; i < num; i++) {
                shareResource.print15();
            }
        }, "C").start();
    }
}

三十二、线程通信之生产者消费者阻塞队列版

package com.atguigu.springcloud.practice;

import org.apache.commons.lang3.StringUtils;

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

class MyResource {
    // 默认开启,进行生产消费
    // 这里用到了volatile是为了保持数据的可见性,也就是当FLAG修改时,要马上通知其它线程进行修改
    private volatile boolean FLAG = true;
    // 使用原子包装类,而不用number++
    private AtomicInteger atomicInteger = new AtomicInteger();
    // 这里不能为了满足条件,而实例化一个具体的SynchronousBlockingQueue
    BlockingQueue<String> blockingQueue = null;

    // 而应该采用依赖注入里面的,构造注入方法传入
    public MyResource(BlockingQueue<String> blockingQueue) {
        this.blockingQueue = blockingQueue;
        // 查询出传入的class是什么
        System.out.println(blockingQueue.getClass().getName());
    }

    public void myProducer() throws Exception {
        String data = null;
        boolean retValue;
        // 多线程环境的判断,一定要使用while进行,防止出现虚假唤醒
        // 当FLAG为true的时候,开始生产
        while (FLAG) {
            data = atomicInteger.incrementAndGet() + "";
            // 2秒存入1个data
            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 + "失败");
            }
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        System.out.println(Thread.currentThread().getName() + "\t 停止生产,表示FLAG=false,生产动作结束");
    }

    public void myConsumer() throws Exception {
        String result;
        // 当FLAG为true的时候,开始消费
        while (FLAG) {
            // 2秒存入1个data
            result = blockingQueue.poll(2L, TimeUnit.SECONDS);
            if (StringUtils.isNotEmpty(result)) {
                System.out.println(Thread.currentThread().getName() + "\t 消费队列:" + result + "成功");
            } else {
                FLAG = false;
                System.out.println(Thread.currentThread().getName() + "\t 消费失败,队列中已为空,退出");
                // 退出消费队列
                return;
            }
        }
    }

    /**
     * 停止生产的判断
     */
    public void stop() {
        this.FLAG = false;
    }
}

public class ProducerConsumerWithBlockingQueueDemo {
    public static void main(String[] args) {
        // 传入具体的实现类, ArrayBlockingQueue
        MyResource myResource = new MyResource(new ArrayBlockingQueue<String>(10));
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t 生产线程启动\n\n");
            try {
                myResource.myProducer();
                System.out.println("\n");
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "producer").start();
        new Thread(() -> {
            System.out.println(Thread.currentThread().getName() + "\t 消费线程启动");
            try {
                myResource.myConsumer();
            } catch (Exception e) {
                e.printStackTrace();
            }
        }, "consumer").start();
        // 5秒后,停止生产和消费
        try {
            TimeUnit.SECONDS.sleep(5);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("\n\n5秒中后,生产和消费线程停止,线程结束");
        myResource.stop();
    }
}

三十三、Callable接口

package com.atguigu.springcloud.practice;
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(Thread.currentThread().getName() + " come in Callable");
        TimeUnit.SECONDS.sleep(2);
        return 1024;
    }
}
public class CallableDemo {
    public static void main(String[] args) throws InterruptedException, ExecutionException {
        FutureTask<Integer> futureTask = new FutureTask<>(new MyThread());
        new Thread(futureTask, "AA").start();
        new Thread(futureTask, "BB").start();//多个线程执行 一个FutureTask的时候,只会计算一次
        // 输出FutureTask的返回值
        int result01 = 1;
        while (!futureTask.isDone()) {
        }
        //建议放在最后,要求获得Callable线程的计算结果,如果没有计算完成就要去强求,会导致堵塞,直到计算完成
        int result02 = futureTask.get();
        System.out.println("result FutureTask" + (result01 + result02));
    }
}

三十四、线程池使用及优势

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

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

优点:

        降低资源消耗。通过重复利用己创建的线程降低线程创建和销毁造成的消耗。
        提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
        提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。

三十五、线程池3个常用方式

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

        

  • Executors.newFixedThreadPool(int)
public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

主要特点如下:

  1. 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  2. newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue。
  • Executors.newSingleThreadExecutor()
public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

主要特点如下:

  1. 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
  2. newSingleThreadExecutor将corePoolSize和maximumPoolSize都设置为1,它使用的LinkedBlockingQueue。
  • Executors.newCachedThreadPool()
public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

主要特点如下:

        1. 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
        2. newCachedThreadPool将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。

package com.atguigu.springcloud.practice;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ThreadPoolDemo {
    public static void main(String[] args) {
        //ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池5个处理线程
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();//一池1个处理线程
        ExecutorService threadPool = Executors.newCachedThreadPool();//一池N个处理线程
        try {
            //模拟10个用户来办理业务,每个用户就是一个来自外部的请求线程
            for (int i = 0; i < 5; i++) {
                threadPool.execute(() -> System.out.println(Thread.currentThread().getName() + "\t办理业务"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

三十六、线程池7大参数入门简介(深入介绍)

package com.atguigu.springcloud.practice;

import java.util.concurrent.*;

/**
 * 3 大方法,7 大参数,4 种拒绝策略
 */
public class Demo01 {
    public static void main(String[] args) {
        //ExecutorService threadPool = Executors.newSingleThreadExecutor();//单个线程
        //ExecutorService threadPool = Executors.newFixedThreadPool(5);//创建一个固定大小的线程池
        //ExecutorService threadPool = Executors.newCachedThreadPool();//可伸缩的
        /**
         * 自定义线程池
         * 最大线程数量如何定义
         * 1、CPU 密集型  几核处理器就是几,可以保持CPU的效率最高
         * 2、IO  密集型  > 判断程序中十分消耗io的线程
         *        程序中  15个大型任务,io十分消耗资源
         */
        //获取CPU的核数
        int cpu = Runtime.getRuntime().availableProcessors();
        System.out.println(cpu);
        ExecutorService threadPool = new ThreadPoolExecutor(
                2,//核心线程池大小
                5,//最大核心线程池大小
                3,//超时了没有人调用就会释放
                TimeUnit.SECONDS,//超时单位
                new LinkedBlockingDeque<>(3),//阻塞队列
                Executors.defaultThreadFactory(),//线程工厂,创建线程池的,一般不用变
                new ThreadPoolExecutor.DiscardOldestPolicy());//队列满了,尝试去和最早的竞争,竞争不到也会丢掉
        //四种拒绝策略:
        //new ThreadPoolExecutor.AbortPolicy());//数量满了,不处理直接抛出异常
        //new ThreadPoolExecutor.CallerRunsPolicy());//哪来的去哪里,main方法执行
        //new ThreadPoolExecutor.DiscardPolicy());//队列满了不会抛出异常,丢掉任务
        //new ThreadPoolExecutor.DiscardOldestPolicy());//队列满了,尝试去和最早的竞争,竞争不到也会丢掉
        try {
            for (int i = 0; i < 9; i++) {
                //线程池创建线程
                threadPool.execute(() -> System.out.println(Thread.currentThread().getName() + "ok"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            threadPool.shutdown();
        }
    }
}

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

        在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程。
        当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中。
2.maximumPoolSize:线程池能够容纳同时执行的最大线程数,此值必须大于等于1

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

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

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

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

7.handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数( maximumPoolSize)。

三十七、线程池底层工作原理

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

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

        1.如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
        2.如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
        3.如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
        4.如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3.当一个线程完成任务时,它会从队列中取下一个任务来执行。

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

        如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉,所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小。

三十八、线程池的4种拒绝策略理论简介

等待队列也已经排满了,再也塞不下新任务了同时,线程池中的max线程也达到了,无法继续为新任务服务。

这时候我们就需要拒绝策略机制合理的处理这个问题。

JDK拒绝策略:

        1.AbortPolicy(默认):直接抛出 RejectedExecutionException异常阻止系统正常运知。
        2.CallerRunsPolicy:"调用者运行"一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
        3.DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务。
        4.DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案。
以上内置拒绝策略均实现了RejectedExecutionHandler接口。

三十九、线程池实际中使用哪一个

(超级大坑警告)你在工作中单一的/固定数的/可变的三种创建线程池的方法,你用那个多?

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

        Executors 中JDK已经给你提供了,为什么不用?

3.【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。

说明:线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。 如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。

4.【强制】线程池不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。

说明:Executors 返回的线程池对象的弊端如下:

1) FixedThreadPool 和 SingleThreadPool: 允许的请求队列长度为 Integer.MAX_VALUE,可能会堆积大量的请求,从而导致 OOM。

2) CachedThreadPool: 允许的创建线程数量为 Integer.MAX_VALUE,可能会创建大量的线程,从而导致 OOM。

阿里巴巴《Java 开发手册》

四十、线程池的手写改造和拒绝策略(实际项目应用)

package com.atguigu.springcloud.practice;
import java.util.concurrent.*;
public class MyThreadPoolExecutorDemo {
    public static void doSomething(ExecutorService executorService, int numOfRequest) {
        try {
            System.out.println(((ThreadPoolExecutor) executorService).getRejectedExecutionHandler().getClass() + ":");
            TimeUnit.SECONDS.sleep(1);
            for (int i = 0; i < numOfRequest; i++) {
                final int tempInt = i;
                executorService.execute(() -> System.out.println(Thread.currentThread().getName() + "\t 给用户:" + tempInt + " 办理业务"));
            }
            TimeUnit.SECONDS.sleep(1);
            System.out.println("\n\n");
        } catch (Exception e) {
            System.err.println(e.getMessage());
        } finally {
            executorService.shutdown();
        }
    }
    public static ExecutorService newMyThreadPoolExecutor(int corePoolSize,
                                                          int maximumPoolSize, int blockingQueueSize, RejectedExecutionHandler handler) {
        return new ThreadPoolExecutor(
                corePoolSize,
                maximumPoolSize,
                1,//keepAliveTime
                TimeUnit.SECONDS,
                new LinkedBlockingQueue<>(blockingQueueSize),
                Executors.defaultThreadFactory(),
                handler);
    }
    public static void main(String[] args) {
        doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.AbortPolicy()), 10);
        doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.CallerRunsPolicy()), 20);
        doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.DiscardOldestPolicy()), 10);
        doSomething(newMyThreadPoolExecutor(2, 5, 3, new ThreadPoolExecutor.DiscardPolicy()), 10);
    }
}

四十一、线程池配置合理线程数

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

CPU密集型

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

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

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

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

lO密集型

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

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

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

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

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

参考公式:CPU核数/ (1-阻塞系数)

阻塞系数在0.8~0.9之间

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

四十二、死锁编码及定位分析

死锁是什么?

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

产生死锁主要原因:

  • 系统资源不足
  • 进程运行推进的顺序不合适
  • 资源分配不当

发生死锁的四个条件:

  • 互斥条件,线程使用的资源至少有一个不能共享的。
  • 至少有一个线程必须持有一个资源且正在等待获取一个当前被别的线程持有的资源。
  • 资源不能被抢占。
  • 循环等待。

如何解决死锁问题:

破坏发生死锁的四个条件其中之一即可。

产生死锁的代码(根据发生死锁的四个条件):

package com.study.mainshi1;
import java.util.concurrent.TimeUnit;
class MyTask implements Runnable {
    private final Object resourceA;
    private final Object resourceB;
    MyTask(Object resourceA, Object resourceB) {
        this.resourceA = resourceA;
        this.resourceB = resourceB;
    }
    @Override
    public void run() {
        synchronized (resourceA) {
            System.out.println(String.format("%s 自己持有%s,尝试持有%s",
                    Thread.currentThread().getName(), resourceA, resourceB));
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            synchronized (resourceB) {
                System.out.println(String.format("%s 同时持有%s,%s",//
                        Thread.currentThread().getName(), resourceA, resourceB));
            }
        }
    }
}
public class DeadLockDemo {
    public static void main(String[] args) {
        Object resourceA = new Object();
        Object resourceB = new Object();
        new Thread(new MyTask(resourceA, resourceB), "Thread A").start();
        new Thread(new MyTask(resourceB, resourceA), "Thread B").start();
    }
}

程序卡死,未出现同时持有的字样。

查看是否死锁工具

  1. jps命令定位进程号

  2. jstack找到死锁查看

C:\Users\abc>jps -l
11968 com.lun.concurrency.DeadLockDemo
6100 jdk.jcmd/sun.tools.jps.Jps
6204 Eclipse

C:\Users\abc>jstack 11968
2021-03-09 02:42:46
Full thread dump Java HotSpot(TM) 64-Bit Server VM (25.251-b08 mixed mode):

"DestroyJavaVM" #13 prio=5 os_prio=0 tid=0x00000000004de800 nid=0x2524 waiting on condition [0
x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Thread B" #12 prio=5 os_prio=0 tid=0x000000001e0a5800 nid=0x6bc waiting for monitor entry [0x
000000001efae000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.lun.concurrency.MyTask.run(DeadLockDemo.java:27)
        - waiting to lock <0x000000076b431d80> (a java.lang.Object)
        - locked <0x000000076b431d90> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

"Thread A" #11 prio=5 os_prio=0 tid=0x000000001e0a4800 nid=0x650 waiting for monitor entry [0x
000000001eeae000]
   java.lang.Thread.State: BLOCKED (on object monitor)
        at com.lun.concurrency.MyTask.run(DeadLockDemo.java:27)
        - waiting to lock <0x000000076b431d90> (a java.lang.Object)
        - locked <0x000000076b431d80> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

"Service Thread" #10 daemon prio=9 os_prio=0 tid=0x000000001e034000 nid=0x2fb8 runnable [0x000
0000000000000]
   java.lang.Thread.State: RUNNABLE

"C1 CompilerThread3" #9 daemon prio=9 os_prio=2 tid=0x000000001dffa000 nid=0x26e8 waiting on c
ondition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread2" #8 daemon prio=9 os_prio=2 tid=0x000000001dff6000 nid=0x484 waiting on co
ndition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread1" #7 daemon prio=9 os_prio=2 tid=0x000000001dfe0800 nid=0x35c8 waiting on c
ondition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"C2 CompilerThread0" #6 daemon prio=9 os_prio=2 tid=0x000000001dfde800 nid=0x3b7c waiting on c
ondition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Attach Listener" #5 daemon prio=5 os_prio=2 tid=0x000000001dfdd000 nid=0x3834 waiting on cond
ition [0x0000000000000000]
   java.lang.Thread.State: RUNNABLE

"Signal Dispatcher" #4 daemon prio=9 os_prio=2 tid=0x000000001dfdb000 nid=0x214 runnable [0x00
00000000000000]
   java.lang.Thread.State: RUNNABLE

"Finalizer" #3 daemon prio=8 os_prio=1 tid=0x000000001df70800 nid=0x2650 in Object.wait() [0x0
00000001e54f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b388ee0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:144)
        - locked <0x000000076b388ee0> (a java.lang.ref.ReferenceQueue$Lock)
        at java.lang.ref.ReferenceQueue.remove(ReferenceQueue.java:165)
        at java.lang.ref.Finalizer$FinalizerThread.run(Finalizer.java:216)

"Reference Handler" #2 daemon prio=10 os_prio=2 tid=0x000000001c17d000 nid=0x1680 in Object.wa
it() [0x000000001e44f000]
   java.lang.Thread.State: WAITING (on object monitor)
        at java.lang.Object.wait(Native Method)
        - waiting on <0x000000076b386c00> (a java.lang.ref.Reference$Lock)
        at java.lang.Object.wait(Object.java:502)
        at java.lang.ref.Reference.tryHandlePending(Reference.java:191)
        - locked <0x000000076b386c00> (a java.lang.ref.Reference$Lock)
        at java.lang.ref.Reference$ReferenceHandler.run(Reference.java:153)

"VM Thread" os_prio=2 tid=0x000000001c178000 nid=0x3958 runnable

"GC task thread#0 (ParallelGC)" os_prio=0 tid=0x0000000002667800 nid=0xd3c runnable

"GC task thread#1 (ParallelGC)" os_prio=0 tid=0x0000000002669000 nid=0x297c runnable

"GC task thread#2 (ParallelGC)" os_prio=0 tid=0x000000000266a800 nid=0x2fd0 runnable

"GC task thread#3 (ParallelGC)" os_prio=0 tid=0x000000000266c000 nid=0x1c90 runnable

"GC task thread#4 (ParallelGC)" os_prio=0 tid=0x000000000266f800 nid=0x3614 runnable

"GC task thread#5 (ParallelGC)" os_prio=0 tid=0x0000000002670800 nid=0x298c runnable

"GC task thread#6 (ParallelGC)" os_prio=0 tid=0x0000000002674000 nid=0x2b40 runnable

"GC task thread#7 (ParallelGC)" os_prio=0 tid=0x0000000002675000 nid=0x25f4 runnable

"VM Periodic Task Thread" os_prio=2 tid=0x000000001e097000 nid=0xd54 waiting on condition

JNI global references: 5

Found one Java-level deadlock:
=============================
"Thread B":
  waiting to lock monitor 0x000000001e105dc8 (object 0x000000076b431d80, a java.lang.Object),
  which is held by "Thread A"
"Thread A":
  waiting to lock monitor 0x000000001c181828 (object 0x000000076b431d90, a java.lang.Object),
  which is held by "Thread B"

Java stack information for the threads listed above:
===================================================
"Thread B":
        at com.lun.concurrency.MyTask.run(DeadLockDemo.java:27)
        - waiting to lock <0x000000076b431d80> (a java.lang.Object)
        - locked <0x000000076b431d90> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)
"Thread A":
        at com.lun.concurrency.MyTask.run(DeadLockDemo.java:27)
        - waiting to lock <0x000000076b431d90> (a java.lang.Object)
        - locked <0x000000076b431d80> (a java.lang.Object)
        at java.lang.Thread.run(Thread.java:748)

Found 1 deadlock.

C:\Users\abc>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值