JUC基础-0606

news/2024/5/20 18:30:02/文章来源:https://blog.csdn.net/lannister_awalys_pay/article/details/131078204

9.ReentrantReadWriteLock读写锁

9.1 锁的基本概念

悲观锁:不支持并发,效率低,但是可以解决所有并发安全问题

乐观锁:支持并发读,维护一个版本号,写的时候比较版本号进行控制,先提交的版本号的线程可以进行写。

表锁:只操作一条记录的时候,对整张表上锁

行锁:只对一条记录上锁,行锁会发生死锁

读锁:共享锁,发生死锁

写锁:独占锁,发生死锁

读锁发生死锁案例:

  • 两个线程都持有读锁,不释放并都企图获取写锁
  • 读锁升级写锁可能会导致死锁:这是因为在升级期间,读锁需要被释放,但是写锁在获得之前需要等待所有的读锁释放。如果有其他线程持有读锁并试图获取写锁,则会出现死锁情况。因此,建议在升级锁之前先释放读锁,并在获得写锁之前检查是否存在等待的写锁。

写锁发生死锁案例:

  • 两个线程都要对两条记录进行修改,两线程都持有一条记录的写锁,不释放,并企图获取另一条记录的写锁,产生死锁。

9.2 读写锁介绍

现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那 么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。

针对这种场景,JAVA 的并发包提供了读写锁 ReentrantReadWriteLock, 它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁

  1. 线程进入读锁的前提条件:
    • 没有其他线程的写锁
    • 没有写请求, 或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁)。
  2. 线程进入写锁的前提条件:
    • 没有其他线程的读锁
    • 没有其他线程的写锁
  3. 而读写锁有以下三个重要的特性:
    1. 公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公 平优于公平。
    2. 重进入:读锁和写锁都支持线程重进入。
    3. 锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
//资源类
class MyCache {//创建map集合private volatile Map<String,Object> map = new HashMap<>();//创建读写锁对象private ReadWriteLock rwLock = new ReentrantReadWriteLock();//放数据public void put(String key,Object value) {//添加写锁rwLock.writeLock().lock();try {System.out.println(Thread.currentThread().getName()+" 正在写操作"+key);//暂停一会TimeUnit.MICROSECONDS.sleep(300);//放数据map.put(key,value);System.out.println(Thread.currentThread().getName()+" 写完了"+key);} catch (InterruptedException e) {e.printStackTrace();} finally {//释放写锁rwLock.writeLock().unlock();}}//取数据public Object get(String key) {//添加读锁rwLock.readLock().lock();Object result = null;try {System.out.println(Thread.currentThread().getName()+" 正在读取操作"+key);//暂停一会TimeUnit.MICROSECONDS.sleep(300);result = map.get(key);System.out.println(Thread.currentThread().getName()+" 取完了"+key);} catch (InterruptedException e) {e.printStackTrace();} finally {//释放读锁rwLock.readLock().unlock();}return result;}
}public class ReadWriteLockDemo {public static void main(String[] args) throws InterruptedException {MyCache myCache = new MyCache();//创建线程放数据for (int i = 1; i <=5; i++) {final int num = i;new Thread(()->{myCache.put(num+"",num+"");},String.valueOf(i)).start();}TimeUnit.MICROSECONDS.sleep(300);//创建线程取数据for (int i = 1; i <=5; i++) {final int num = i;new Thread(()->{myCache.get(num+"");},String.valueOf(i)).start();}}
}

注意:如果不进行加锁的操作,可能会存在没写完就开始读,然后读出null的情况

9.3 读写锁演变

读写锁:一个资源可以被一个或多个读线程访问,或者被一个写线程访问。读写互斥,读读共享

  1. 无锁情况:多线程抢夺资源
  2. 加锁:使用synchronized和ReentrantLock
    1. 都是独占式的
    2. 每次只能来一个操作,读读不共享
  3. 读写锁:ReenTrantReadWriteLock
    1. 读读共享,提升性能,同时多人读
    2. 写独占
    3. 缺点:容易造成锁饥饿问题。例如一直有读进程,没有机会写。

9.4 读写锁的降级

  1. 将写入锁降级为读锁
    1. 过程:jdk8官方说明
      1. 获取写锁
      2. 再获取读锁
      3. 释放写锁
      4. 释放读锁
  2. 将读锁不能升级为写锁:读完成后才可以写,只有释放读锁之后才可以加写锁
    1. 过程:需要先读,释放读锁,再加写锁

代码演示:

				//锁降级//1 获取写锁writeLock.lock();System.out.println("-- writelock");//2 获取读锁readLock.lock();System.out.println("---read");//3 释放写锁writeLock.unlock();//4 释放读锁readLock.unlock();//	输出结果:
-- writelock
---read

因为读写锁是可重入锁,所以,加完写锁可以加读锁。

但是反过来就不行了

			//2 获取读锁readLock.lock();System.out.println("---read");//1 获取写锁writeLock.lock();System.out.println("-- writelock");//3 释放写锁writeLock.unlock();//4 释放读锁readLock.unlock();
//	输出结果:
---read卡住了

原因: 当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把获取读锁的线程“升级”为写锁。而对于获得写锁的线程,它一定独占了读写 锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释 放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。

10.阻塞队列BlockingQueue

10.1 BlockingQueue 简介

  1. Concurrent 包中,BlockingQueue 很好的解决了多线程中,如何高效安全 “传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建 高质量的多线程程序带来极大的便利。本文详细介绍了 BlockingQueue 家庭 中的所有成员,包括他们各自的功能以及常见使用场景。
  2. 阻塞队列,顾名思义,首先它是一个队列, 通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;
    1. 请添加图片描述
    2. 当队列是空的,从队列中获取元素的操作将会被阻塞
    3. 当队列是满的,从队列中添加元素的操作将会被阻塞
    4. 试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
    5. 试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增
  3. 常用的队列主要有以下两种:
    1. 先进先出(FIFO):先插入的队列的元素也最先出队列,类似于排队的功能。 从某种程度上来说这种队列也体现了一种公平性
    2. 后进先出(LIFO):后插入队列的元素最先出队列,这种队列优先处理最近发 生的事件(栈)
  4. 在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起 的线程又会自动被唤起
  5. 为什么需要 BlockingQueue
    1. 好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切 BlockingQueue 都给你一手包办了
    2. 在 concurrent 包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细 节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
  6. 多线程环境中,通过队列可以很容易实现数据共享,比如经典的“生产者”和“消费者”模型中,通过队列可以很便利地实现两者之间的数据共享。假设我们有若干生产者线程,另外又有若干个消费者线程。如果生产者线程需要把准备好的数据共享给消费者线程,利用队列的方式来传递数据,就可以很方便地解决他们之间的数据共享问题。但如果生产者和消费者在某个时间段内,万一发生数据处理速度不匹配的情况呢?理想情况下,如果生产者产出数据的速度大于消费者消费的速度,并且当生产出来的数据累积到一定程度的时候,那么生产者必须暂停等待一下(阻塞生产者线程),以便等待消费者线程把累积的数据处理完毕,反之亦然。
    • 当队列中没有数据的情况下,消费者端的所有线程都会被自动阻塞(挂起), 直到有数据放入队列
    • 当队列中填满数据的情况下,生产者端的所有线程都会被自动阻塞(挂起), 直到队列中有空的位置,线程被自动唤醒

10.2 阻塞队列分类

10.2.1 ArrayBolckingQueue(常用)

  1. 基于数组的阻塞队列实现,在 ArrayBlockingQueue 内部,维护了一个定长数 组,以便缓存队列中的数据对象,这是一个常用的阻塞队列,除了一个定长数 组外,ArrayBlockingQueue 内部还保存着两个整形变量,分别标识着队列的 头部和尾部在数组中的位置。
  2. ArrayBlockingQueue 在生产者放入数据和消费者获取数据,都是共用同一个锁对象,由此也意味着两者无法真正并行运行,这点尤其不同于 LinkedBlockingQueue;按照实现原理来分析,ArrayBlockingQueue 完全可以采用分离锁,从而实现生产者和消费者操作的完全并行运行。DougLea之所以没这样去做,也许是因为 ArrayBlockingQueue 的数据写入和获取操作已经足够轻巧,以至于引入独立的锁机制,除了给代码带来额外的复杂性外,其在性能上完全占不到任何便宜。 ArrayBlockingQueue 和 LinkedBlockingQueue 间还有一个明显的不同之处在于,前者在插入或删除 元素时不会产生或销毁任何额外的对象实例,而后者则会生成一个额外的 Node 对象。这在长时间内需要高效并发地处理大批量数据的系统中,其对于 GC 的影响还是存在一定的区别。而在创建 ArrayBlockingQueue 时,我们还 可以控制对象的内部锁是否采用公平锁,默认采用非公平锁。
  3. 一句话总结 :由数组结构组成的有界阻塞队列

10.2.2 LinkedBlockingQueue(常用)

  1. ArrayBlockingQueue LinkedBlockingQueue 是两个最普通也是最常用 的阻塞队列,一般情况下,在处理多线程间的生产者消费者问题,使用这两个 类足以。
  2. 一句话总结:由链表结构组成的有界(但大小默认值为 integer.MAX_VALUE)阻塞队列。

10.2.3 DelayQueue

一句话总结:使用优先级队列实现的延迟无界阻塞队列。

10.2.4 PriorityBlockingQueue

  1. 基于优先级的阻塞队列(优先级的判断通过构造函数传入的 Compator 对象来 决定),但需要注意的是 PriorityBlockingQueue 并不会阻塞数据生产者,而 只会在没有可消费的数据时,阻塞数据的消费者
  2. 因此使用的时候要特别注意,生产者生产数据的速度绝对不能快于消费者消费 数据的速度,否则时间一长,会最终耗尽所有的可用堆内存空间。
  3. 在实现 PriorityBlockingQueue 时,内部控制线程同步的锁采用的是公平锁
  4. 一句话总结: 支持优先级排序的无界阻塞队列。

10.2.5 SynchronousQueue

  1. 一种无缓冲的等待队列,类似于无中介的直接交易,有点像原始社会中的生产 者和消费者,生产者拿着产品去集市销售给产品的最终消费者,而消费者必须 亲自去集市找到所要商品的直接生产者,如果一方没有找到合适的目标,那么 对不起,大家都在集市等待。相对于有缓冲的 BlockingQueue 来说,少了一 个中间经销商的环节(缓冲区),如果有经销商,生产者直接把产品批发给经 销商,而无需在意经销商最终会将这些产品卖给那些消费者,由于经销商可以 库存一部分商品,因此相对于直接交易模式,总体来说采用中间经销商的模式 会吞吐量高一些(可以批量买卖);但另一方面,又因为经销商的引入,使得 产品从生产者到消费者中间增加了额外的交易环节,单个产品的及时响应性能 可能会降低。
  2. 声明一个 SynchronousQueue 有两种不同的方式,它们之间有着不太一样的 行为。
  3. 公平模式和非公平模式的区别
    • 公平模式:SynchronousQueue 会采用公平锁,并配合一个 FIFO 队列来阻塞 多余的生产者和消费者,从而体系整体的公平策略;
    • 非公平模式(SynchronousQueue 默认):SynchronousQueue 采用非公平 锁,同时配合一个 LIFO 队列来管理多余的生产者和消费者,而后一种模式, 如果生产者和消费者的处理速度有差距,则很容易出现饥渴的情况,即可能有 某些生产者或者是消费者的数据永远都得不到处理。
  4. 一句话总结: 不存储元素的阻塞队列,也即单个元素的队列。

10.2.6 LinkedTransferQueue

一句话总结:由链表组成的无界阻塞队列。

10.2.7 LinkedBlockingDeque

  1. LinkedBlockingDeque 是一个由链表结构组成的双向阻塞队列,即可以从队 列的两端插入和移除元素。
  2. 一句话总结:由链表组成的双向阻塞队列

10.3 核心方法

请添加图片描述

代码演示:

public class BlockingQueueDemo {public static void main(String[] args) throws InterruptedException {//创建阻塞队列BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<>(3);//第一组System.out.println(blockingQueue.add("a"));   //  打印trueSystem.out.println(blockingQueue.add("b"));   //  打印trueSystem.out.println(blockingQueue.add("c"));   //  打印trueSystem.out.println(blockingQueue.element());  //  打印aSystem.out.println(blockingQueue.add("w"));   //  抛出异常 Queue fullSystem.out.println(blockingQueue.remove());   // 打印aSystem.out.println(blockingQueue.remove());   // 打印bSystem.out.println(blockingQueue.remove());   // 打印cSystem.out.println(blockingQueue.remove());   // 抛出异常 NoSuchElementException//第二组System.out.println(blockingQueue.offer("a"));   //  打印 trueSystem.out.println(blockingQueue.offer("b"));   //  打印 trueSystem.out.println(blockingQueue.offer("c"));   //  打印 trueSystem.out.println(blockingQueue.offer("www"));   //  打印 falseSystem.out.println(blockingQueue.poll());   //  打印aSystem.out.println(blockingQueue.poll());   //  打印bSystem.out.println(blockingQueue.poll());   //  打印cSystem.out.println(blockingQueue.poll());   //  打印 null//第三组blockingQueue.put("a");        //blockingQueue.put("b");        //blockingQueue.put("c");        //blockingQueue.put("w");        //   陷入阻塞System.out.println(blockingQueue.take());       //  打印aSystem.out.println(blockingQueue.take());       //  打印bSystem.out.println(blockingQueue.take());       //  打印cSystem.out.println(blockingQueue.take());       //  陷入阻塞//第四组System.out.println(blockingQueue.offer("a"));   //  打印trueSystem.out.println(blockingQueue.offer("b"));   //  打印trueSystem.out.println(blockingQueue.offer("c"));   //  打印trueSystem.out.println(blockingQueue.offer("w",3L, TimeUnit.SECONDS));  //  等待了3秒,打印false}
}

10.4 小结

1. 在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起
2. 为什么需要 BlockingQueue? 在 concurrent 包发布以前,在多线程环境下, 我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全, 而这会给我们的程序带来不小的复杂度。使用后我们不需要关心什么时候需要 阻塞线程,什么时候需要唤醒线程,因为这一切 BlockingQueue 都给你一手 包办了

11.ThreadPool线程池

11.1 线程池概述

线程池(英语:thread pool):一种线程使用模式。线程过多会带来调度开销, 进而影响缓存局部性和整体性能。而线程池维护着多个线程,等待着监督管理 者分配可并发执行的任务。这避免了在处理短时间任务时创建与销毁线程的代 价。线程池不仅能够保证内核的充分利用,还能防止过分调度。
例子: 10 年前单核 CPU 电脑,假的多线程,像马戏团小丑玩多个球,CPU 需 要来回切换。 现在是多核电脑,多个线程各自跑在独立的 CPU 上,不用切换 效率高。
线程池的优势: 线程池做的工作只要是控制运行的线程数量,处理过程中将任 务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量, 超出数量的线程排队等候,等其他线程执行完毕,再从队列中取出任务来执行。
它的主要特点为:

  • 降低资源消耗: 通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
  • 提高响应速度: 当任务到达时,任务可以不需要等待线程创建就能立即执行。
  • 提高线程的可管理性: 线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
  • Java 中的线程是通过 Executor 框架实现的,该框架中用到了 Executor,Executors,ExecutorService,ThreadPoolExecutor 这几个类
    请添加图片描述

11.2 线程池分类

  1. Executors.newFixedThreadPool(int):一池N线程
  2. Executors.newSingleThreadExecutor( ):一个任务一个任务执行,一池一线程
  3. Executors.newCachedThreadPool( ):线程池根据需求创建线程,可扩容,遇强则强

11.2.1 newCachedThreadPool(常用)

  1. 作用:创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空 闲线程,若无可回收,则新建线程.
  2. 特点:
    • 线程池中数量没有固定,可达到最大值(Interger. MAX_VALUE)
    • 线程池中的线程可进行缓存重复利用和回收(回收默认时间为 1 分钟)
    • 当线程池中,没有可用线程,会重新创建一个线程
  3. 创建方式:
    //  扩容线程池ExecutorService threadPool3 = Executors.newCachedThreadPool();try {for(int i=1;i<=20;i++){final int num = i;threadPool3.execute(()->{System.out.println(Thread.currentThread().getName()+" 正在办理业务"+num);});}} catch (Exception e) {throw new RuntimeException(e);}finally {threadPool1.shutdown();}//	输出:
    pool-3-thread-1 正在办理业务1
    pool-3-thread-3 正在办理业务3
    pool-3-thread-2 正在办理业务2
    pool-3-thread-4 正在办理业务4
    pool-3-thread-5 正在办理业务5
    pool-3-thread-2 正在办理业务6
    pool-3-thread-1 正在办理业务10
    pool-3-thread-4 正在办理业务8
    pool-3-thread-3 正在办理业务9
    pool-3-thread-1 正在办理业务13
    pool-3-thread-5 正在办理业务7
    pool-3-thread-2 正在办理业务14
    pool-3-thread-4 正在办理业务12
    pool-3-thread-1 正在办理业务17
    pool-3-thread-3 正在办理业务19
    pool-3-thread-5 正在办理业务16
    pool-3-thread-2 正在办理业务18
    pool-3-thread-6 正在办理业务11
    pool-3-thread-8 正在办理业务20
    pool-3-thread-7 正在办理业务15
    
  4. 场景: 适用于创建一个可无限扩大的线程池,服务器负载压力较轻,执行时间较 短,任务多的场景

11.2.2 newFixedThreadPool(常用)

  1. 作用:创建一个可重用固定线程数的线程池,以共享的无界队列方式来运行这 些线程。在任意点,在大多数线程会处于处理任务的活动状态。如果在所有线 程处于活动状态时提交附加任务,则在有可用线程之前,附加任务将在队列中 等待。如果在关闭前的执行期间由于失败而导致任何线程终止,那么一个新线 程将代替它执行后续的任务(如果需要)。在某个线程被显式地关闭之前,池 中的线程将一直存在。
  2. 特征:
    • 线程池中的线程处于一定的量,可以很好的控制线程的并发量 • 线程可以重复被使用,在显示关闭之前,都将一直存在
    • 超出一定量的线程被提交时候需在队列中等待
  3. 创建方式:
    //  一池5线程ExecutorService threadPool1 = Executors.newFixedThreadPool(5);try {for(int i=1;i<=10;i++){final int num = i;threadPool1.execute(()->{System.out.println(Thread.currentThread().getName()+" 正在办理业务"+num);});}} catch (Exception e) {throw new RuntimeException(e);}finally {threadPool1.shutdown();}
    //	输出:
    pool-1-thread-1 正在办理业务1
    pool-1-thread-3 正在办理业务3
    pool-1-thread-2 正在办理业务2
    pool-1-thread-4 正在办理业务4
    pool-1-thread-5 正在办理业务5
    pool-1-thread-4 正在办理业务7
    pool-1-thread-5 正在办理业务8
    pool-1-thread-4 正在办理业务9
    pool-1-thread-5 正在办理业务10
    pool-1-thread-3 正在办理业务6
    
  4. 场景: 适用于可以预测线程数量的业务中,或者服务器负载较重,对线程数有严 格限制的场景

11.2.3 newSingleThreadExecutor(常用)

  1. 作用:创建一个使用单个 worker 线程的 Executor,以无界队列方式来运行该 线程。(注意,如果因为在关闭前的执行期间出现失败而终止了此单个线程, 那么如果需要,一个新线程将代替它执行后续的任务)。可保证顺序地执行各 个任务,并且在任意给定的时间不会有多个线程是活动的。与其他等效的 newFixedThreadPool 不同,可保证无需重新配置此方法所返回的执行程序即 可使用其他的线程。
  2. 特征: 线程池中最多执行 1 个线程,之后提交的线程活动将会排在队列中以此 执行
  3. 创建方式:
    //  一池一线程ExecutorService threadPool2 = Executors.newSingleThreadExecutor();try {for(int i=1;i<=10;i++){final int num = i;threadPool2.execute(()->{System.out.println(Thread.currentThread().getName()+" 正在办理业务"+num);});}} catch (Exception e) {throw new RuntimeException(e);}finally {threadPool1.shutdown();}
    //	输出:
    pool-2-thread-1 正在办理业务1
    pool-2-thread-1 正在办理业务2
    pool-2-thread-1 正在办理业务3
    pool-2-thread-1 正在办理业务4
    pool-2-thread-1 正在办理业务5
    pool-2-thread-1 正在办理业务6
    pool-2-thread-1 正在办理业务7
    pool-2-thread-1 正在办理业务8
    pool-2-thread-1 正在办理业务9
    pool-2-thread-1 正在办理业务10
    
  4. 场景:适用于需要保证顺序执行各个任务,并且在任意时间点,不会同时有多个 线程的场景

11.3 底层原理

11.2 中介绍的三个线程池底层实现都使用了ThreadPoolExecutor

11.3.1 ThreadPoolExecutor参数

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler) {if (corePoolSize < 0 ||maximumPoolSize <= 0 ||maximumPoolSize < corePoolSize ||keepAliveTime < 0)throw new IllegalArgumentException();if (workQueue == null || threadFactory == null || handler == null)throw new NullPointerException();this.acc = System.getSecurityManager() == null ?null :AccessController.getContext();this.corePoolSize = corePoolSize;this.maximumPoolSize = maximumPoolSize;this.workQueue = workQueue;this.keepAliveTime = unit.toNanos(keepAliveTime);this.threadFactory = threadFactory;this.handler = handler;}
  1. corePoolSize: 常驻线程数量,核心线程数量。不过有没有任务,都有这些线程
  2. maximumPoolSize: 最大支持线程数量
  3. keepAliveTime: 线程存活时间,扩容的线程多长时间不使用之后会结束掉
  4. unit: 3中的时间单位
  5. workQueue: 阻塞队列,常驻线程数量都用完了就会进入阻塞队列
  6. threadFactory: 线程工厂,用于创建线程
  7. handler: 拒绝策略,

11.3.2 底层工作流程

请添加图片描述

  1. 执行了execute之后,线程才会创建
  2. corePool:核心(常驻)
  3. maximumPool:最大线程
  4. 如果最大线程满了,阻塞队列也满了,直接执行拒绝策略
  5. 例如现有情况,来了1,2两个任务占满了常驻线程,第3,4,5会去阻塞队列等待,阻塞队列满了,会创建新线程去解决第6,7,8.对第9个执行拒绝策略。
  6. 任务流转:corePool -> BlockingQueue -> maximumPool -> rejectedExecutionHandler

11.3.3 拒绝策略

请添加图片描述

11.4 自定义线程池

  1. 项目中创建多线程时,使用常见的三种线程池创建方式,单一、可变、定长都 有一定问题,原因是 FixedThreadPool 和 SingleThreadExecutor 底层都是用 LinkedBlockingQueue 实现的,这个队列最大长度为 Integer.MAX_VALUE, 容易导致 OOM。所以实际生产一般自己通过 ThreadPoolExecutor 的 7 个参 数,自定义线程池

  2. 创建线程池推荐适用ThreadPoolExecutor及其7个参数手动创建

    • corePoolSize 线程池的核心线程数
    • maximumPoolSize 能容纳的最大线程数 o keepAliveTime 空闲线程存活时间
    • unit 存活的时间单位
    • workQueue 存放提交但未执行任务的队列
    • threadFactory 创建线程的工厂类
    • handler 等待队列满后的拒绝策略
  3. 为什么不允许适用不允许Executors.的方式手动创建线程池,如下图

请添加图片描述

代码示例:

ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 5, 2L, TimeUnit.SECONDS,new ArrayBlockingQueue<>(3),Executors.defaultThreadFactory(),new ThreadPoolExecutor.AbortPolicy());//10个顾客请求try {for (int i = 1; i <=10; i++) {//执行threadPool.execute(()->{System.out.println(Thread.currentThread().getName()+" 办理业务");});}}catch (Exception e) {e.printStackTrace();}finally {//关闭threadPool.shutdown();}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.luyixian.cn/news_show_690745.aspx

如若内容造成侵权/违法违规/事实不符,请联系dt猫网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

【Vue】三:Vue组件: 组件使用和组件嵌套

文章目录 1.第一个组件1.1不使用组件前1.2创建组件1.3注册组件1.4使用组件1.5 细节 2.组件嵌套 1.第一个组件 1.1不使用组件前 1.2创建组件 Vue.extends({该配置项和new Vue的配置项几乎相同})区别&#xff1a; &#xff08;1&#xff09;创建Vue组件的时候&#xff0c;不能使…

Kubernetes之pod

Kubernetes之pod 在通过docker运行程序时&#xff0c;我们通常会制作Dockerfile文件构建镜像。也可以基于某个镜像运行容器在容器中安装组件之后&#xff0c;再基于容器生成镜像 使用如下命令可生成镜像&#xff0c;想了解更多参数请添加–help docker build -f Dockerfile路…

【Leetcode -138.复制带随机指针的链表 -2130.链表最大孪生和】

Leetcode Leetcode -138.复制带随机指针的链表Leetcode -2130.链表最大孪生和 Leetcode -138.复制带随机指针的链表 题目&#xff1a;给你一个长度为 n 的链表&#xff0c;每个节点包含一个额外增加的随机指针 random &#xff0c;该指针可以指向链表中的任何节点或空节点。 构…

4种普遍的机器学习分类算法

朴素贝叶斯分类 朴素贝叶斯分类是基于贝叶斯定理与特征条件独立假设的分类方法&#xff0c;发源于古典数学理论&#xff0c;拥有稳定的数学基础和分类效率。它是一种十分简单的分类算法&#xff0c;当然简单并不一定不好用。通过对给出的待分类项求解各项类别的出现概率大小&a…

企业应该如何选择适合自己的直播平台?

企业应该如何选择适合自己的直播平台&#xff1f;本文将从功能需求、可靠性与稳定性、用户体验、技术能与售后服务能力等方面进行综合考虑&#xff0c;帮助您做出明智的决策&#xff0c;或是说提供选型方面的参考。 企业在选择一家直播平台时应考虑以下因素&#xff1a; 1. 企…

【链表的分类】

链表是一种常用的数据结构&#xff0c;它由一系列节点组成&#xff0c;每个节点包含一个数据元素和指向下一个节点的指针。根据节点的连接方式和节点的性质&#xff0c;链表可以分为多种类型。 单向链表&#xff08;Singly Linked List&#xff09; 单向链表是最基本的链表类…

PyGame游戏编程

Python非常受欢迎的一个原因是它的应用领域非常广泛&#xff0c;其中就包括游戏开发。而是用Python进行游戏开发的首选模块就是PyGame。 1. 初识Pygame PyGame是跨平台Python模块&#xff0c;专为电子游戏设计&#xff0c;包含图像、声音等&#xff0c;创建在SDL&#xff08;…

sms开发文档

sms系统设计参考毕业设计-----------学生选课管理系统的设计 一、使用axios 来实现网页中ajax请求 首先说到axios&#xff0c;是一个类库&#xff0c;他的底层基于ajax库&#xff0c;通常用于ajax请求 ajax又是什么 ajax是一种创建快速动态网页的技术&#xff0c; 传统的页…

LeetCode 24. 两两交换链表中的节点

给你一个链表&#xff0c;两两交换其中相邻的节点&#xff0c;并返回交换后链表的头节点。你必须在不修改节点内部的值的情况下完成本题&#xff08;即&#xff0c;只能进行节点交换&#xff09;。 示例 1&#xff1a; 输入&#xff1a;head [1,2,3,4] 输出&#xff1a;[2,1,4…

chatgpt赋能python:Python如何使用空行优化SEO

Python 如何使用空行优化 SEO 在网页排名算法中&#xff0c;空行的使用可以对网页的排名产生影响。在 Python 中&#xff0c;空行的使用也被用来优化代码和提高代码的可读性。本文将介绍如何在 Python 中使用空行来优化代码和优化 SEO。 空行的作用 在 Python 中&#xff0c…

直播问答功能(互动功能接收端JS-SDK)

功能概述 本模块主要用于展示问答模块。 初始化及销毁 在实例化该模块并进行使用之前&#xff0c;需要对SDK进行初始化配置&#xff0c;详细见参考文档。 在线文件引入方式 // script 标签引入&#xff0c;根据版本号引入JS版本。 <script src"https://websdk.vi…

OA系统,企业数字化转型的重要工具,用现成还是自己搭建呢

什么是OA系统 OA系统是办公自动化系统的简称&#xff0c;它是指一种基于计算机技术的办公工作管理系统&#xff0c;用于协调和规划企业内部各部门的信息发布、通信、人员流动、文档管理等方面的工作。它可以有效地提高企业办公效率和工作效益&#xff0c;优化企业内部沟通协作…

Java之旅(五)

运算符 算术运算符 加法&#xff08;&#xff09;减法&#xff08;-&#xff09;乘法&#xff08;*&#xff09;除法&#xff08;/&#xff09;取余&#xff08;%&#xff09;一元运算符 自增运算符&#xff08;&#xff09;自减运算符&#xff08;--&#xff09;变量前就先运…

元宇宙应用领域-教育

教育是一个国家发展的基础&#xff0c;在科技发展的时代&#xff0c;元宇宙将会帮助教育行业实现跨越式发展。 元宇宙与教育的结合将会对传统的教学模式带来翻天覆地的变化。它能将线上教学、线下体验、远程互动等优势集于一身&#xff0c;也能把教师从繁重的重复劳动中解放出…

公司来了个新的测试员,本以为是个菜鸡,没想到......

最近公司来了个新同事&#xff0c;学历并不高&#xff0c;而且大学也不是计算机专业的&#xff0c;今年刚满30岁。。 本以为也是来干点基础的活混混日子的&#xff0c;结果没想到这个人上来就把现有项目的性能测试了一遍&#xff0c;直接给公司节省了不少成本&#xff0c;这种…

自定义组件中,使用onLoad,onShow生命周期失效问题

的解决方法 自定义组件中&#xff0c;使用onLoad,onShow生命周期失效问题 自定义组件中&#xff0c;使用onLoad,onShow生命周期失效问题 官方文档可查阅到&#xff1a; 页面生命周期仅在page中的vue页面有效&#xff0c;而单独封装的组件中【页面周期无效】&#xff0c;但是Vu…

Python常考基础面试题

文章目录 Python基础面试题1、 Python 数据结构有哪些2、Python 中列表和元组的区别是什么&#xff1f;元组是不是真的不可变&#xff1f;3、什么是生成器和迭代器&#xff1f;它们之间有什么区别&#xff1f;迭代器生成器 4、什么是闭包&#xff1f;装饰器又是什么&#xff1f…

七、帧缓冲离屏渲染

第一部分基础概念 1)两种帧缓冲的由来 首先opengl能够显示到屏幕&#xff0c;也是有一个默认的framebuffer由窗口系统创建并管理的&#xff0c;将数据放到默认framebuffer 中就可以显示到屏幕上。但是应用程序也想创建额外的非可显示的framebuffer。 应用程序自己创建FBO也是…

以AI为灯,照亮医疗放射防护监管盲区

相信绝大部分人都有在医院拍X光片的经历&#xff0c;它能够让医生更方便快速地找出潜在问题&#xff0c;判断病人健康状况&#xff0c;是医疗诊断过程中的常见检查方式。但同时X射线也是一把双刃剑&#xff0c;它的照射量可在体内累积&#xff0c;对人体血液白细胞有杀伤力&…

RK1126 C++ yolov5 6.2

基于 rk npu &#xff0c; 实现 yolov5 6.2 模型推理 实现过程 ⚡️​ 编译 opencv 需根据自己路径修改. cmake -D CMAKE_BUILD_TYPERELEASE \-D CMAKE_C_COMPILER./gcc-arm-8.3-2019.02-x86_64-arm-linux-gnueabihf/bin/arm-linux-gnueabihf-gcc \-D CMAKE_CXX_COMPILER./gc…