欢迎大家关注 github.com/hsfxuebao/j… ,希望对大家有所帮助,要是觉得可以的话麻烦给点一下Star哈
7、阻塞队列
阻塞队列知道吗?
7.1、什么是阻塞队列
队列 + 阻塞队列
- 阻塞队列,顾名思义,首先它是一个队列,而一个阻塞队列在数据结构中所起的作用大致如下图所示
- 当阻塞队列是满时,往队列里添加元素的操作将会被阻塞。当队列是空时,从队列中取出元素的操作将被阻塞
- 试图从空的阻塞队列中获取元素的线程将会被阻塞,直到其他的线程往空的队列插入新的元素。
- 试图往己满的阻塞队列中添加新元素的线程同样也会被阻塞,直到其他的线程从列中移除一个或者多个元素,或者完全清空队列后使队列重新变得空闲起来并后续新增一些元素
7.2、阻塞队列好处
为什么用阻塞队列?有什么好处?
- 在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤醒
- 为什么需要BlockingQueue?好处是我们不需要关心什么时候需要阻塞线程,不用我们自己去控制线程的唤醒(notify)和阻塞(wait)
- 什么时候需要唤醒线程?这一切BlockingQueue都给你一手包办了
- 在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度
7.3、阻塞队列分类
BlockingQueue 分类(看前三个即可)
- ArrayBlockingQueue:由数组结构组成的有界阻塞队列——对应于 ArrayList
- LinkedBlockingQueue:由链表结构组成的有界(大小默认值为Integer.MAX_VALUE)阻塞队列——对应于 LinkedList
- SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列——生产一个,消费一个
- PriorityBlockingQueue:支持优先级排序的无界阻塞队列
- DelayQueue:使用优先级队列实现的延迟无界阻塞队列
- LinkedTransferQueue:由链表结构组成的无界阻塞队列。
- LinkedBlockingDeque:由链表结构组成的双向阻塞队列
7.4、阻塞队列用法
BlockingQueue 的核心方法
方法类型
抛出异常
返回布尔
阻塞
超时
插入
add(E e)
offer(E e)
put(E e)
offer(E e,Time,TimeUnit)
取出
remove()
poll()
take()
poll(Time,TimeUnit)
队首
element()
peek()
无
无
抛出异常
- 当阻塞队列满时,再往队列里add插入元素会抛IllegalStateException:Queue full
- 当阻塞队列空时,再往队列里remove移除元素会抛NoSuchElementException
返回布尔
- 插入方法,成功 ture 失败 false
- 移除方法,成功返回出队列的元素,队列里面没有就返回null
一直阻塞
- 当阻塞队列满时,生产者线程继续往队列里put元素,队列会一直阻塞生产线程直到put数据成功或者响应中断退出。
- 当阻塞队列空时,消费者线程试图从队列里take元素,队列会一直阻塞消费者线程直到队列可用。
超时退出
当阻塞队列满时,队列会阻塞生产者线程一定时间,超过后限时后生产者线程会退出
7.4.1、ArrayBlockingQueue
代码示例
/**
* ArrayBlockingQueue:是一个基于数组结构的有界阻塞队列,此队列按FIFO原则对元素进行排序
* LinkedBlockingQueue:是一个基于链表结构的阻塞队列,此队列按FIFO排序元素,吞吐量高于ArrayBlockingQueue
* SynchronousQueue:一个不存储元素的阻塞队列,每个插入操作必须等到另一个线程调用移出操作,否则插入操作一直处于阻塞状态,吞吐量通常要高
*
* 阻塞队列
* 1.阻塞队列有没有好的一面
* 2.不得不阻塞,你如何管理
*
*/
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
BlockingQueue<String> blockingQueue = new ArrayBlockingQueue<String>(3);
//addAndRemove(blockingQueue);
//offerAndPoll(blockingQueue);
//putAndTake(blockingQueue);
outOfTime(blockingQueue);
}
private static void addAndRemove(BlockingQueue<String> blockingQueue) {
System.out.println(blockingQueue.add("a"));
System.out.println(blockingQueue.add("b"));
System.out.println(blockingQueue.add("c"));
System.out.println(blockingQueue.add("e"));
System.out.println(blockingQueue.element());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
System.out.println(blockingQueue.remove());
}
private static void offerAndPoll(BlockingQueue<String> blockingQueue) {
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("e"));
System.out.println(blockingQueue.peek());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
}
private static void putAndTake(BlockingQueue<String> blockingQueue) throws InterruptedException {
blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
blockingQueue.put("d");
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
}
private static void outOfTime(BlockingQueue<String> blockingQueue) throws InterruptedException {
System.out.println(blockingQueue.offer("a",2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a",2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a",2L, TimeUnit.SECONDS));
System.out.println(blockingQueue.offer("a",2L, TimeUnit.SECONDS));
}
}
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667
7.4.2、SynchronousQueue
阻塞队列之 SynchronousQueue
- SynchronousQueue没有容量。与其他BlockingQueue不同,SynchronousQueue是一个不存储元素的BlockingQueue。
- 每一个put操作必须要等待一个take操作,否则不能继续添加元素,反之亦然。
- 一句话总结:SynchronousQueue 时零库存阻塞队列
代码示例
-
代码
/**
- 阻塞队列SynchronousQueue演示
*/ public class SynchronousQueueDemo { public static void main(String[] args) { BlockingQueue 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 { try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take()); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take()); try { TimeUnit.SECONDS.sleep(5); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "\t" + blockingQueue.take()); } catch (InterruptedException e) { e.printStackTrace(); } }, "BBB").start(); }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354
-
程序运行结果:线程 AAA 生产完新的数据后,必须等待线程 BBB 取走后,才能继续生产
AAA put 1 BBB 1 AAA put 2 BBB 2 AAA put 3 BBB 3 123456
7.5、阻塞队列使用场景
阻塞队列使用场景
生产者消费者模式、线程池、消息中间件
传统版:消费者生产者模式
-
代码:
- Lock 替代 synchronized
lock.await()替代object.wait()lock.signalAll()替代object.notifyAll()
/**
- 题目:一个初始值为0的变量,两个线程对其交替操作,一个加1,一个减1,来5轮
- 口诀:
- 1.线程操作资源类 --> 编写方法
- 2.判断 干活 通知 --> await() 和 signalAll()
- 3.防止虚假唤醒机制 --> 使用 while 判断,而不是 if
*/ public class ProdConsumer_TraditionDemo { public static void main(String[] args) { ShareData shareData = new ShareData();
new Thread(() -> { for (int i = 1; i <= 5; i++) { try { shareData.increment(); } catch (Exception e) { e.printStackTrace(); } } }, "AAA").start(); new Thread(() -> { for (int i = 1; i <= 5; i++) { try { shareData.decrement(); } catch (Exception e) { e.printStackTrace(); } } }, "BBB").start(); }}
// 资源类 class ShareData { private int number = 0; private Lock lock = new ReentrantLock(); private Condition condition = lock.newCondition();
public void increment() throws InterruptedException { lock.lock(); try { //1 判断 while (number == 1) { //等待,不能生产 condition.await(); } //2 干活 number++; System.out.println(Thread.currentThread().getName() + "\t" + number); //3 通知唤醒 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void decrement() throws InterruptedException { lock.lock(); try { //1 判断 while (number == 0) { //等待,不能生产 condition.await(); } //2 干活 number--; System.out.println(Thread.currentThread().getName() + "\t" + number); //3 通知唤醒 condition.signalAll(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }}
-
程序运行结果
AAA 1 BBB 0 AAA 1 BBB 0 AAA 1 BBB 0 AAA 1 BBB 0 AAA 1 BBB 0 12345678910
补充:synchronized 和Lock有什么区别?用新的Lock有什么好处?你举例说说
1、实现方式:www.cnblogs.com/lycroseup/p…
- synchronized是关键字属天JVM层面,synchronized的语义底层是通过一个monitor的对象来完成,其实
wait()、notify()等方法也依赖于monitor对象,这就是为什么只有在同步的块或者方法中才能调用wait()、notify()等方法,否则会抛出java.lang.IllegalMonitorStateException的异常的原因 - Lock是具体类(java.util.concurrent.Locks.Lock)是api层面的锁
2、使用方法
- synchronized 不需要用户去手动释放锁,当synchronized代码执行完后系统会自动让线程释放对锁的占用
- ReentrantLock则需要用户去手动释放锁着没有主动放锁,就有可能导致出现死锁现象。需要
lock()和unlock()方法配合try{ }finally{ }语句块来完成。
3、等待是否可中断
- synchronized不可中断,除非抛出异常或者正常运行完成
- ReentrantLock 可中断
- 设置超时方法
trylock(long timeout,TimeUnit unit) - 或者
LockInterruptibly()放代码块中,调用interrupt()方法可中断
- 设置超时方法
4、加锁是否公平
- synchronized非公平锁
- ReentrantLock两者都可以,默认公平锁,构造方法可以传入boolean值,true为公平锁,false为非公平锁
5、锁绑定多个条件condition
- synchronized没有
- ReentrantLock用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
从字节码来看 synchronized 和 Lock
-
代码
public static void main(String[] args) { synchronized (new Object()) { }
ReentrantLock lock = new ReentrantLock();} 123456
-
字节码指令
- 有一条 monitorenter 指令,表示获取锁
- 有两条 monitorexit 指令,其中第二条 monitorexit 指令保证程序出了一场,照样能释放锁
- 使用 ReentrantLock 类,在字节码层面就是 new 了一个对象
0 new #2 <java/lang/Object> 3 dup 4 invokespecial #1 <java/lang/Object.> 7 dup 8 astore_1 9 monitorenter 10 aload_1 11 monitorexit 12 goto 20 (+8) 15 astore_2 16 aload_1 17 monitorexit 18 aload_2 19 athrow 20 new #3 <java/util/concurrent/locks/ReentrantLock> 23 dup 24 invokespecial #4 <java/util/concurrent/locks/ReentrantLock.> 27 astore_1 28 return 12345678910111213141516171819
题目:多线程交替打印,展示 Lock 的精确唤醒
-
代码
/**
- 题目:多线程之间按顺序调用,实现A->B->C三个线程启动,要求如下:
- A打印5次,B打印10次,C打印15次
- 紧接着
- A打印5次,B打印10次,C打印15次
- ......
- 打印10轮
*/ public class SyncAndReentrantLockDemo { public static void main(String[] args) { ShareResource shareResource = new ShareResource();
new Thread(() -> { for (int i = 1; i <= 10; i++) { shareResource.print5(); } }, "A").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { shareResource.print10(); } }, "B").start(); new Thread(() -> { for (int i = 1; i <= 10; i++) { shareResource.print15(); } }, "C").start(); }}
// 资源类 class ShareResource { private int number = 1;//A:1.B:2,C:3 private Lock lock = new ReentrantLock(); private Condition c1 = lock.newCondition(); private Condition c2 = lock.newCondition(); private Condition c3 = lock.newCondition();
public void print5() { lock.lock(); try { //1判断 while (number != 1) { c1.await(); } //2干活 for (int i = 1; i <= 5; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } //3通知 number = 2; c2.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print10() { lock.lock(); try { //1判断 while (number != 2) { c2.await(); } //2干活 for (int i = 1; i <= 10; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } //3通知 number = 3; c3.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } } public void print15() { lock.lock(); try { //1判断 while (number != 3) { c3.await(); } //2干活 for (int i = 1; i <= 15; i++) { System.out.println(Thread.currentThread().getName() + "\t" + i); } //3通知 number = 1; c1.signal(); } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }}
-
程序运行结果(一下结果仅是一轮打印的结果)
A 1 A 2 A 3 A 4 A 5 B 1 B 2 B 3 B 4 B 5 B 6 B 7 B 8 B 9 B 10 C 1 C 2 C 3 C 4 C 5 C 6 C 7 C 8 C 9 C 10 C 11 C 12 C 13 C 14 C 15 123456789101112131415161718192021222324252627282930
阻塞队列版:消费者生产者模式
-
代码
/**
- volatile/CAS/atomicInteger/BlockQueue/线程交互/原子引用
*/ public class ProdConsumer_BlockQueueDemo { public static void main(String[] args) throws Exception { MyResource myResource = new MyResource(new ArrayBlockingQueue<>(10));
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){ } System.out.println("5秒钟到,main停止"); myResource.stop(); }}
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() + ""; // 原子整形对象加 1 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生产停止"); } public void myConsumer() throws Exception { String result = null; while (FLAG) { result = blockingQueue.poll(2L, TimeUnit.SECONDS); // 从阻塞队列中取出数据 if (null == result || result.equalsIgnoreCase("")) { FLAG = false; System.out.println(Thread.currentThread().getName() + "\t 超过2秒,消费退出"); return; } System.out.println(Thread.currentThread().getName() + "\t消费队列" + result + "成功"); } System.out.println(Thread.currentThread().getName() + "\t消费停止"); } public void stop() throws Exception { this.FLAG = false; // 停止生产与消费 }} 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687
-
程序运行结果
java.util.concurrent.ArrayBlockingQueue Prod 生产线程启动 Consumer 消费线程启动 Prod 插入队列1成功 Consumer 消费队列1成功 Prod 插入队列2成功 Consumer 消费队列2成功 Prod 插入队列3成功 Consumer 消费队列3成功 Prod 插入队列4成功 Consumer 消费队列4成功 Prod 插入队列5成功 Consumer 消费队列5成功 5秒钟到,main停止 Prod 生产停止 Consumer 超过2秒,消费退出 12345678910111213141516
8、线程池
线程池用过吗?ThreadPoolExecutor他谈你的理解?生产上你如何设置合理参数?
8.1、Callable 接口
Callable 与 Futuretask(适配器模式)
-
代码
public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask futureTask = new FutureTask<>(new MyThread()); new Thread(futureTask, "AA").start(); // 开始执行耗时计算 int result01 = 100; // 等待线程执行完成 while (!futureTask.isDone()){ // Do something here } // 要求获得Callable线程的计算结果,如果没有计算完成就要去强求,会导致阻塞,直到计算完成 int result02 = futureTask.get(); System.out.println("result=" + (result01 + result02)); } }
class MyThread implements Callable { @Override public Integer call() throws Exception { System.out.println("callable come in ..."); // 模拟耗时操作 try { TimeUnit.SECONDS.sleep(2); }catch (InterruptedException e){
} return 1024; }} 1234567891011121314151617181920212223242526272829303132333435
-
程序运行结果
callable come in ... result=1124 12
多个线程共享 Futuretask
-
代码:线程 A 和线程 B 共享 Futuretask
public class CallableDemo { public static void main(String[] args) throws ExecutionException, InterruptedException { FutureTask futureTask = new FutureTask<>(new MyThread()); new Thread(futureTask, "AA").start(); // 开始执行耗时计算 new Thread(futureTask, "BB").start(); // 开始执行耗时计算 int result01 = 100; while (!futureTask.isDone()){ // Do something here } // 要求获得Callable线程的计算结果,如果没有计算完成就要去强求,会导致阻塞,直到计算完成 int result02 = futureTask.get(); System.out.println("result=" + (result01 + result02)); } }
class MyThread implements Callable { @Override public Integer call() throws Exception { System.out.println("callable come in ..."); // 模拟耗时操作 try { TimeUnit.SECONDS.sleep(2); }catch (InterruptedException e){
} return 1024; }}
-
程序运行结果
callable come in ... result=1124 12
8.2、线程池的优势
为什么要用线程池,线程池的优势是什么?
线程池做的工作主要是控制运行的线程的数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行。
线程池的主要特点为:线程复用;控制最大并发数;管理线程。
- 第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 第二:提高响应速度。当任务到达时,任务可以不需要的等到线程创建就能立即执行。
- 第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控
8.3、线程池如何使用
线程池架构说明
- Java 中的线程池是通过Executor框架实现的
- 该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类
- 线程池框架架构图如下:
线程池的编码实现
了解:
Executor:newScheduledThreadPool(),带时间调度的线程池Executors.newWorkStealingPool(int):java8新增,使用目前机器上可用的处理器作为它的并行级别
重要:
Executors.newFixedThreadPool(int):执行长期的任务,性能好很多Executors.newSingleThreadExecutor():一个任务一个任务执行的场景Executors.newCachedThreadPool():执行很多短期异步的小程序或者负载较轻的服务器
线程池使用步骤:
- 创建线程池
- 使用线程池
- 关闭线程池
Executors.newFixedThreadPool(int):固定线程数量的线程池
主要特点如下:
-
创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
-
newFixedThreadPool()创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的LinkedBlockingQueue 阻塞队列public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); } 12345
代码示例
-
代码
/**
- 第四种使用Java多线程的方式:线程池
*/ public class MyThreadPoolDemo { public static void main(String[] args) { System.out.println("Fixed Thread Pool"); fixedThreadPool(); }
private static void fixedThreadPool() { //一池5个线程 ExecutorService threadPool = Executors.newFixedThreadPool(5); //一般常用try-catch-finally //模拟10个用户来办理业务,每个用户就是一个线程 try { for (int i = 0; i < 9; i++) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }}
-
程序运行结果:线程池数量为 5,所以线程名最大为 pool-1-thread-5
Fixed Thread Pool pool-1-thread-3 办理业务 pool-1-thread-4 办理业务 pool-1-thread-2 办理业务 pool-1-thread-1 办理业务 pool-1-thread-2 办理业务 pool-1-thread-4 办理业务 pool-1-thread-5 办理业务 pool-1-thread-3 办理业务 pool-1-thread-1 办理业务 12345678910
Executors.newSingleThreadExecutor():单个线程的线程池
主要特点如下:
-
创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序执行。
-
newSingleThreadExecutor()将线程池的corePoolSize和maximumPoolSize都设置为1,它使用的LinkedBlockingQueue 阻塞队列public static ExecutorService newSingleThreadExecutor() { return new FinalizableDelegatedExecutorService (new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue())); } 123456
代码示例
-
代码
/**
- 第四种使用Java多线程的方式:线程池
*/ public class MyThreadPoolDemo { public static void main(String[] args) { System.out.println("Single Thread Pool"); singleThreadPool(); }
private static void singleThreadPool() { //一池1个线程 ExecutorService threadPool = Executors.newSingleThreadExecutor(); try { for (int i = 0; i < 9; i++) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }} 123456789101112131415161718192021222324252627282930313233
-
程序运行结果:线程池中只有一个线程
Single Thread Pool pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 pool-1-thread-1 办理业务 12345678910
Executors.newCachedThreadPool():自适应线程数量的线程
主要特点如下:
-
创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
-
newCachedThreadPool()将线程池的corePoolSize设置为0,将线程池的maximumPoolSize设置为Integer.MAX_VALUE,使用的SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程public static ExecutorService newCachedThreadPool() { return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue()); } 12345
代码示例
-
代码
/**
- 第四种使用Java多线程的方式:线程池
*/ public class MyThreadPoolDemo { public static void main(String[] args) { System.out.println("Cached Thread Pool"); cachedThreadPool(); }
private static void cachedThreadPool() { //不定量线程 ExecutorService threadPool = Executors.newCachedThreadPool(); try { for (int i = 0; i < 9; i++) { threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t办理业务"); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }} 123456789101112131415161718192021222324252627282930313233
-
程序运行结果
Cached Thread Pool pool-1-thread-1 办理业务 pool-1-thread-2 办理业务 pool-1-thread-3 办理业务 pool-1-thread-4 办理业务 pool-1-thread-5 办理业务 pool-1-thread-6 办理业务 pool-1-thread-7 办理业务 pool-1-thread-8 办理业务 pool-1-thread-3 办理业务 12345678910
new ThreadPoolExecutor()
8.4、线程池 7 大参数
ThreadPoolExecutor 构造器
/**
* Creates a new {@code ThreadPoolExecutor} with the given initial
* parameters.
*
* @param corePoolSize the number of threads to keep in the pool, even
* if they are idle, unless {@code allowCoreThreadTimeOut} is set
* @param maximumPoolSize the maximum number of threads to allow in the
* pool
* @param keepAliveTime when the number of threads is greater than
* the core, this is the maximum time that excess idle threads
* will wait for new tasks before terminating.
* @param unit the time unit for the {@code keepAliveTime} argument
* @param workQueue the queue to use for holding tasks before they are
* executed. This queue will hold only the {@code Runnable}
* tasks submitted by the {@code execute} method.
* @param threadFactory the factory to use when the executor
* creates a new thread
* @param handler the handler to use when execution is blocked
* because the thread bounds and queue capacities are reached
* @throws IllegalArgumentException if one of the following holds:<br>
* {@code corePoolSize < 0}<br>
* {@code keepAliveTime < 0}<br>
* {@code maximumPoolSize <= 0}<br>
* {@code maximumPoolSize < corePoolSize}
* @throws NullPointerException if {@code workQueue}
* or {@code threadFactory} or {@code handler} is null
*/
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;
}
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051
七大参数
- corePoolSize:线程池中的常驻核心线程数
- 在创建了线程池后,当有请求任务来之后,就会安排池中的线程去执行请求任务,近似理解为今日当值线程
- 当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中
- maximumPoolSize:线程池能够容纳同时执行的最大线程数,此数值必须大于等于1
- keepAliveTime:多余的空闲线程的存活时间。当前线程池数量超过corePoolSize时,当空闲时间达到keepAliveTime值时,多余空闲线程会被销毁直到只剩下corePoolSize个线程为止
- unit:keepAliveTime的单位
- workQueue:任务队列,被提交但尚未被执行的任务(阻塞队列)
- threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程一般用默认的即可
- handler:拒绝策略,表示当队列满了并且工作线程大于等于线程池的最大线程数时,如何来拒绝
理解:线程池的创建参数,就像一个银行。
- corePoolSize就像银行的“当值窗口“,比如今天有2位柜员在受理客户请求(任务)。如果超过2个客户,那么新的客户就会在等候区(等待队列workQueue)等待。
- 当等候区也满后,又来了几位客户,这个时候就要开启“加班窗口”,让其它3位柜员来加班,此时达到最大窗口maximumPoolSize,为5个。emmm …,但后来的客户会抢占等候区客户的机会,先办理业务
- 如果开启了所有窗口,等候区依然满员,此时就应该启动”拒绝策略“handler,告诉不断涌入的客户,叫他们不要进入,已经爆满了。
- 由于不再涌入新客户,加班窗口逐渐开始空闲,这个时候就通过keepAlivetTime将多余的3个”加班窗口“取消,恢复到2个”当值窗口“。
验证从core扩容到maximum后,立即运行当前到达的任务,而不是队列中的
-
代码:线程池的核心线程数量为 2,最大线程数量为 5
public class T1 {
public static void main(String[] args) { ExecutorService threadPool = new ThreadPoolExecutor( 2, 5, 100, TimeUnit.SECONDS, new LinkedBlockingDeque<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); try { for (int i = 1; i <= 8; i++) { final int tempInt = i; threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "号窗口,服务顾客" + tempInt); try { Thread.sleep(3000); } catch (InterruptedException e) { e.printStackTrace(); } }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }} 1234567891011121314151617181920212223242526272829303132333435363738
-
程序运行结果
pool-1-thread-2号窗口,服务顾客2 pool-1-thread-1号窗口,服务顾客1 pool-1-thread-3号窗口,服务顾客6 pool-1-thread-4号窗口,服务顾客7 pool-1-thread-5号窗口,服务顾客8 pool-1-thread-1号窗口,服务顾客3 pool-1-thread-2号窗口,服务顾客4 pool-1-thread-5号窗口,服务顾客5 12345678
-
分析结果:core=2,所以1号窗口对应1号顾客,2号窗口对应2号顾客,但是接下来,3、4、5号顾客又来了,进入容量为3的队列中排队,接下来6、7、8号顾客又来了,1、2号窗口正在服务,且队列也满了,此时应该开启3、4、5号窗口来提供服务,为6、7、8号顾客提供服务,然后再由这5个窗口为3、4、5号顾客提供服务
8.5、线程池底层原理
- 在创建了线程池后,等待提交过来的任务请求。
- 当调用execute()方法添加一个请求任务时,线程池会做如下判断:
- 如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
- 如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
- 如果这时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
- 如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
- 当一个线程完成任务时,它会从队列中取下一个任务来执行。
- 当一个线程无事可做超过一定的时间(keepAliveTime)时,线程池会判断:
- 如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉
- 所以线程池的所有任务完成后它最终会收缩到corePoolSize的大小
8.6、线程池面试题
8.6.1、线程池拒绝策略
拒绝策略是什么?
- 等待队列也已经排满了,再也塞不下新任务了,同时线程池中的max线程也达到了,无法继续为新任务服务。
- 这时候我们就需要拒绝策略机制合理的处理这个问题。
JDK内置拒绝策略(以下内置策略均实现了RejectedExecutionHandler接口)
-
AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
-
CallerRunsPolicy:调用者运行,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退给调用者,从而降低新任务的流量
-
DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
-
DiscardPolicy:直接丢弃任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种方案
8.6.2、创建线程池方法
单一的、固定的、可变的三种创建线程池的方法,用哪个多?
结论:一个都不用,答案来自于阿里开发手册
【强制】线程资源必须通过线程池提供,不允许在应用中自行显式创建线程。
说明:使用线程池的好处是减少在创建和销毁线程上所消耗的时间以及系统资源的开销,解决资源不足的问题。如果不使用线程池,有可能造成系统创建大量同类线程而导致消耗完内存或者“过度切换”的问题。
【强制】线程池不允许使用Executors 去创建,而是通过ThreadPoolExecutor的方式,这样的处理方式让写的同学更加明确线程池的运行规则,规避资源耗尽的风险。
说明:Executors返回的线程池对象的弊端如下:
- FixedThreadPool和SingleThreadPool:允许的请求队列长度为Integer.MAX_VALUE,可能会堆积大量的请求,从而导致OOM。
- CachedThreadPool和ScheduledThreadPool:允许的创建线程数量为Integer.MAX_VALUE,可能会创建大量的线程,从而导致OOM。
8.6.3、自己手写线程池
工作中如何使用线程池,是否自定义过线程池使用?
- JDK 自带线程池的缺陷:底层使用了 LinkedBlockingQueue 阻塞队列,该阻塞队列默认是无界的,允许的请求队列长度为Integer.MAX_VALUE
/**
* Creates a {@code LinkedBlockingQueue} with a capacity of
* {@link Integer#MAX_VALUE}.
*/
public LinkedBlockingQueue() {
this(Integer.MAX_VALUE);
}
1234567
代码示例:指令阻塞队列上限 + 使用默认的 AbortPolicy 策略
-
代码
/**
- 第四种使用Java多线程的方式:线程池
*/ public class MyThreadPoolDemo { public static void main(String[] args) { System.out.println("Custom Thread Pool\n"); customThreadPool();
} private static void customThreadPool() { ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.AbortPolicy() ); try { for (int i = 1; i <= 8; i++) { final int temp = i; threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t办理业务" + temp); }); } } catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); } }} 1234567891011121314151617181920212223242526272829303132333435363738394041
-
for (int i = 1; i <= 8; i++) 时,线程池最大线程数 + 阻塞队列数量 = 8 ,所以程序不会抛异常,线程池 hold 得住
Custom Thread Pool
pool-1-thread-1 办理业务1 pool-1-thread-1 办理业务3 pool-1-thread-1 办理业务4 pool-1-thread-1 办理业务5 pool-1-thread-2 办理业务2 pool-1-thread-5 办理业务8 pool-1-thread-3 办理业务6 pool-1-thread-4 办理业务7 12345678910
-
for (int i = 1; i <= 8; i++) 时,同时并发的最大线程数可能会超过 8 ,所以程序可能会抛异常,也可能不会,跑不跑异常,就看线程池能不能即时处理我们交给他的任务
Custom Thread Pool
java.util.concurrent.RejectedExecutionException: Task com.Heygo.MyThreadPoolDemo$$LambdaAbortPolicy.rejectedExecution(ThreadPoolExecutor.java:2063) at java.util.concurrent.ThreadPoolExecutor.reject(ThreadPoolExecutor.java:830) at java.util.concurrent.ThreadPoolExecutor.execute(ThreadPoolExecutor.java:1379) at com.Heygo.MyThreadPoolDemo.customThreadPool(MyThreadPoolDemo.java:40) at com.Heygo.MyThreadPoolDemo.main(MyThreadPoolDemo.java:23) pool-1-thread-2 办理业务2 pool-1-thread-3 办理业务6 pool-1-thread-2 办理业务3 pool-1-thread-2 办理业务5 pool-1-thread-5 办理业务8 pool-1-thread-1 办理业务1 pool-1-thread-4 办理业务7 pool-1-thread-3 办理业务4 12345678910111213141516
代码示例:使用 CallerRunsPolicy 策略
-
代码
private static void customThreadPool() { ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.CallerRunsPolicy() ); try { for (int i = 1; i <= 9; i++) { final int temp = i; threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t办理业务" + temp); }); }
} catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); }} 123456789101112131415161718192021222324
-
程序运行结果:无法处理的任务会回退给调用线程
Custom Thread Pool
pool-1-thread-1 办理业务1 pool-1-thread-3 办理业务6 pool-1-thread-2 办理业务2 pool-1-thread-3 办理业务4 pool-1-thread-1 办理业务3 main 办理业务9 pool-1-thread-5 办理业务8 pool-1-thread-4 办理业务7 pool-1-thread-2 办理业务5 1234567891011
代码示例:使用 DiscardOldestPolicy 策略
-
代码
private static void customThreadPool() { ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardOldestPolicy() ); try { for (int i = 1; i <= 9; i++) { final int temp = i; threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t办理业务" + temp); }); }
} catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); }} 123456789101112131415161718192021222324
-
程序运行结果:等待时间最长的业务 3 被抛弃了。。。
Custom Thread Pool
pool-1-thread-1 办理业务1 pool-1-thread-3 办理业务6 pool-1-thread-1 办理业务4 pool-1-thread-2 办理业务2 pool-1-thread-1 办理业务9 pool-1-thread-5 办理业务8 pool-1-thread-4 办理业务7 pool-1-thread-3 办理业务5 12345678910
代码示例:使用 DiscardPolicy 策略
-
代码
private static void customThreadPool() { ExecutorService threadPool = new ThreadPoolExecutor(2, 5, 1L, TimeUnit.SECONDS, new LinkedBlockingQueue<>(3), Executors.defaultThreadFactory(), new ThreadPoolExecutor.DiscardPolicy() ); try { for (int i = 1; i <= 9; i++) { final int temp = i; threadPool.execute(() -> { System.out.println(Thread.currentThread().getName() + "\t办理业务" + temp); }); }
} catch (Exception e) { e.printStackTrace(); } finally { threadPool.shutdown(); }} 123456789101112131415161718192021222324
-
程序运行结果:业务 9 直接来吃闭门羹
Custom Thread Pool
pool-1-thread-1 办理业务1 pool-1-thread-3 办理业务6 pool-1-thread-2 办理业务2 pool-1-thread-1 办理业务3 pool-1-thread-4 办理业务7 pool-1-thread-5 办理业务8 pool-1-thread-2 办理业务5 pool-1-thread-3 办理业务4 12345678910
8.6.4、合理配置线程池
如何查看机器的逻辑处理器个数
System.out.println(Runtime.getRuntime().availableProcessors());
如何合理配置线程池?
CPU 密集型
- CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行。
- CPU密集任务只有在真正的多核CPU上才可能得到加速(通过多线程),而在单核CPU上无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力就那些。
- CPU密集型任务配置尽可能少的线程数量,一般公式:CPU核数+1个线程的线程池
IO 密集型
- 由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如CPU核数*2。
- IO密集型,即该任务需要大量的IO,即大量的阻塞。
- 在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待。
- 所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间。
- IO密集型时,大部分线程都阻塞,故需要多配置线程数:参考公式:CPU核数/(1-阻塞系数),阻塞系数在0.8~0.9之间,比如8核CPU:8/(1-0.9)=80个线程数
9、死锁及定位
死锁编码及定位分析
9.1、什么是死锁
产生死锁的主要原因
死锁是指两个或两个以上的进程在执行过程中,因争夺资源而造成的一种互相等待的现象,若无外力干涉那它们都将无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,死锁出现的可能性就很低,否则就会因争夺有限的资源而陷入死锁。
死锁产生原因
- 系统资源不足
- 进程运行推进的顺序不合适
- 资源分配不当
9.2、死锁示例代码
死锁代码 Demo
-
代码:两个线程线程持有自己的锁,同时又去抢对方的锁,emmm。。。不是说不让用字符串作为线程同步锁吗
/**
- 死锁是指两个或者两个以上的进程在执行过程中,因抢夺资源而造成的一种互相等待的现象,
- 若无外力干涉它们将都无法推进下去,如果系统资源充足,进程的资源请求都能够得到满足,
- 死锁出现的可能性也就很低,否则就会因争夺有限的资源而陷入死锁。
*/ public class DeadLockDemo { public static void main(String[] args) { String lockA = "lockA"; String lockB = "lockB";
new Thread(new HoldLockThread(lockA, lockB), "ThreadAAA").start(); new Thread(new HoldLockThread(lockB, lockA), "ThreadBBB").start(); /* * windows下的java运行程序,也有类似ps的查看进程的命令,但是目前我们需要查看的只是java * * linux * ps -ef|grep xxxx ls -l查看当前进程的命令 * * windows * jps = java ps jps -l * jstack * */ }}
class HoldLockThread implements Runnable { private String lockA; private String lockB;
public HoldLockThread(String lockA, String lockB) { this.lockA = lockA; this.lockB = lockB; } public void run() { // 持有自己的锁 synchronized (lockA) { System.out.println(Thread.currentThread().getName() + "\t自己持有:" + lockA + "\t尝试获得:" + lockB); // 睡眠一会儿,保证另一个线程能持有自己的锁 try { TimeUnit.SECONDS.sleep(2); } catch (InterruptedException e) { e.printStackTrace(); } // 还希望得到别人的锁 synchronized (lockB) { System.out.println(Thread.currentThread().getName() + "\t自己持有:" + lockB + "\t尝试获得:" + lockA); } } }} 12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758
-
程序运行结果
9.3、死锁解决方案
如何排查死锁问题?
- jps命令定位进程号
- jstack找到死锁查看
操作示例
-
jps -l 查看产生死锁的进程号
C:\Users\Heygo\Desktop\Interview>jps -l 29808 sun.tools.jps.Jps 3824 org.jetbrains.jps.cmdline.Launcher 25220 8900 DeadLockDemo 12345
-
jstack pid :找到死锁
Found one Java-level deadlock:
"ThreadBBB": waiting to lock monitor 0x000000001c403158 (object 0x000000076b518a18, a java.lang.String), which is held by "ThreadAAA" "ThreadAAA": waiting to lock monitor 0x000000001c400ef8 (object 0x000000076b518a50, a java.lang.String), which is held by "ThreadBBB"
Java stack information for the threads listed above:
"ThreadBBB": at HoldLockThread.run(DeadLockDemo.java:56) - waiting to lock <0x000000076b518a18> (a java.lang.String) - locked <0x000000076b518a50> (a java.lang.String) at java.lang.Thread.run(Thread.java:748) "ThreadAAA": at HoldLockThread.run(DeadLockDemo.java:56) - waiting to lock <0x000000076b518a50> (a java.lang.String) - locked <0x000000076b518a18> (a java.lang.String) at java.lang.Thread.run(Thread.java:748)
Found 1 deadlock.