📒博客首页:热爱编程的大李子 📒
🌞文章目的:Callable接口 | 辅助类 | 读写锁 | 阻塞队列 | 线程池 | Stream流 | 分支合并框架 总结🌞
🍄参考视频:尚硅谷2020权威教程JUC🍄
🙏博主在学习阶段,如若发现问题,请告知,非常感谢🙏
💙同时也非常感谢各位小伙伴们的支持💙
🌈每日一语:我生来就是高山而非溪流 ,我欲于群峰之巅俯视平庸的沟壑。我生来就是人杰而非草芥,我站在伟人之肩藐视卑微的懦夫!🌈
💗感谢: 我只是站在巨人们的肩膀上整理本篇文章,感谢走在前路的大佬们💗
🌟最后,祝大家每天进步亿点点! 欢迎大家点赞👍➕收藏⭐️➕评论💬支持博主🤞!🌟
文章目录
7、Callable接口
引入: 面试题:获得多线程的方法几种?
(1)继承thread类(2)runnable接口
如果只回答这两个你连被问到juc的机会都没有
(3) 实现Callable接口
7.1 是什么?
这是一个函数式接口,因此可以用作lambda表达式或方法引用的赋值对象。
7.2 与runnable对比
实现方法对比:
//创建新类MyThread实现runnable接口
class MyThread implements Runnable{
@Override
public void run() {
}
}
//新类MyThread2实现callable接口
class MyThread2 implements Callable<Integer>{
@Override
public Integer call() throws Exception {
return 200;
}
}
面试题:callable接口与runnable接口的区别?
答:(1)是否有返回值
(2)是否抛异常
(3)落地方法不一样,一个是run,一个是call
7.3 怎么用
直接替换runnable是否可行?
不可行,因为:thread类的构造方法根本没有Callable
解决办法:认识不同的人找中间人
这像认识一个不认识的同学,我可以找中间人介绍。
中间人是什么?java多态,一个类可以实现多个接口!!
运行成功后如何获得返回值?ft.get();
实现代码
class MyThread implements Callable<Integer> {
@Override
public Integer call() throws Exception {
TimeUnit.SECONDS.sleep(4);
System.out.println("*********come in here");
return 1024;
}
}
/**
* @author lxy
* @version 1.0
* @Description 多线程中,第三种获得多线程的方式
* @date 2022/4/29 16:55
*/
public class CallableDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
FutureTask <Integer> futureTask = new FutureTask <>(new MyThread());
new Thread(futureTask,"A").start();//只被执行一次.
new Thread(futureTask,"B").start();
System.out.println(Thread.currentThread().getName());
System.out.println(futureTask.get());
}
}
7.4 FutureTask
7.4.1 FutureTask原理
未来的任务,用它就干一件事,异步调用
main方法就像一个冰糖葫芦,一个个方法由main串起来。
但解决不了一个问题:正常调用挂起堵塞问题 , 比如一个方法执行起来很花费时间…我们可以将它单独另启动一个线程A去做,主线程继续往下执行,最终在主线程结束之前将A的结果再进行操作… 可以大大提高效率.
例子:
(1)老师上着课,口渴了,去买水不合适,讲课线程继续,我可以单起个线程找班长帮忙买水,
水买回来了放桌上,我需要的时候再去get。
(2)4个同学,A算1+20,B算21+30,C算31*到40,D算41+50,是不是C的计算量有点大啊,
FutureTask单起个线程给C计算,我先汇总ABD,最后等C计算完了再汇总C,拿到最终结果
(3)高考:会做的先做,不会的放在后面做
7.4.2 原理补充
- 在主线程中需要执行比较耗时的操作时,但又不想阻塞主线程时,可以把这些作业交给Future对象在后台完成,当主线程将来需要时,就可以通过Future对象获得后台作业的计算结果或者执行状态。
- 一般FutureTask多用于耗时的计算,主线程可以在完成自己的任务后,再去获取结果。
- 如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。get方法获取结果只有在计算完成时获取,否则会一直阻塞直到任务转入完成状态,然后会返回结果或者抛出异常。
- 只计算一次(线程只能被创建执行一次),get方法放到最后
8、JUC强大的辅助类讲解
8.1 CountDownLatch(减少计数)
/**
*
* CountDownLatch: 让一些线程阻塞直到另一些线程完成一系列操作后才被唤醒。
*
* CountDownLatch主要有两个方法,当一个或多个线程调用await方法时,这些线程会阻塞。
* 其它线程调用countDown方法会将计数器减1(调用countDown方法的线程不会阻塞),
* 当计数器的值变为0时,因await方法阻塞的线程会被唤醒,继续执行。
*
* 解释:6个同学陆续离开教室后值班同学才可以关门。
*
* main主线程必须要等前面6个线程完成全部工作后,自己才能开干
*/
public class CountDownLatchDemo {
public static void main(String[] args) throws InterruptedException {
CountDownLatch countDownLatch = new CountDownLatch(6);//给计数器赋值为6
for (int i = 0; i < 6; i++) {//有六个上自习的同学,各自离开教室的时间不一样
new Thread(()->{
System.out.println(Thread.currentThread().getName()+"\t号同学离开教室");
countDownLatch.countDown();//每调用一次,计数器减一(也就是一个线程退出)
},String.valueOf(i)).start();
}
countDownLatch.await();//阻塞main线程,当计数器减为0,则唤醒main线程.
System.out.println(Thread.currentThread().getName()+"\t************班长关门走人,main线程是班长");
}
}
8.2 CyclicBarrier(循环栅栏)
/**
* CyclicBarrier:字面意思是可循环(Cyclic)使用的屏障(Barrier)。它要做的事情是,
* 让一组线程到达一个屏障(也可以叫同步点)时被阻塞,直到最后一个线程到达屏障时,屏障才会开门,所有
* 被屏障拦截的线程才会继续干活。
* 线程进入屏障通过CyclicBarrier的await()方法。
*
* 例:集齐7颗龙珠就可以召唤神龙
*/
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 = 0; 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();
}
}
}
8.3 Semaphore 信号灯
/**
*
* @Description: 信号量
*
* 在信号量上我们定义两种操作:
* acquire(获取) 当一个线程调用acquire操作时,它要么通过成功获取信号量(信号量减1),
* 要么一直等下去,直到有线程释放信号量,或超时。
* release(释放)实际上会将信号量的值加1,然后唤醒等待的线程。
*
* 信号量主要用于两个目的,一个是用于多个共享资源的互斥使用(资源被用尽,只能等待,直到有多余资源),
* 另一个用于并发线程数的控制。(1s 100w访问量)
* 例:抢车位
*/
public class SemaphoreDemo {
public static void main(String[] args) {
Semaphore semaphore = new Semaphore(3);//资源类,有三个空车位
for (int i = 1; i <= 6; i++) {
new Thread(()->{
try {
semaphore.acquire();//占车位,车位数-1
System.out.println(Thread.currentThread().getName()+"\t抢占到了车位");
//暂停一会线程
TimeUnit.SECONDS.sleep(3);
System.out.println(Thread.currentThread().getName()+"\t离开了车位");
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
semaphore.release();//车位数+1
}
},String.valueOf(i)).start();
}
}
}
9、ReentrantReadWriteLock
9.1 读写锁介绍
现实中有这样一种场景:对共享资源有读和写的操作,且写操作没有读操作那么频繁。在没有写操作的时候,多个线程同时读一个资源没有任何问题,所以应该允许多个线程同时读取共享资源;但是如果一个线程想去写这些共享资源,就不应该允许其他线程对该资源进行读和写的操作了。
针对这种场景,JAVA 的并发包提供了读写锁ReentrantReadWriteLock
,它表示两个锁,一个是读操作相关的锁,称为共享锁;一个是写相关的锁,称为排他锁。
线程进入读锁的前提条件:
- 没有其他线程的写锁
- 没有写请求, 或者有写请求,但调用线程和持有锁的线程是同一个(可重入锁)。
线程进入写锁的前提条件:
- 没有其他线程的读锁
- 没有其他线程的写锁
而读写锁有以下三个重要的特性:
(1)公平选择性:支持非公平(默认)和公平的锁获取方式,吞吐量还是非公平优于公平。
(2)重进入:读锁和写锁都支持线程重进入。
(3)锁降级:遵循获取写锁、获取读锁再释放写锁的次序,写锁能够降级成为读锁。
9.2 ReentrantReadWriteLock
ReentrantReadWriteLock 类的整体结构
public class ReentrantReadWriteLock implements ReadWriteLock,java.io.Serializable {
/**
* 读锁
*/
private final ReentrantReadWriteLock.ReadLock readerLock;
/**
* 写锁
*/
private final ReentrantReadWriteLock.WriteLock writerLock;
final Sync sync;
/**
* 使用默认(非公平)的排序属性创建一个新的
* ReentrantReadWriteLock
*/
public ReentrantReadWriteLock() {
this(false);
}
/**
* 使用给定的公平策略创建一个新的 ReentrantReadWriteLock
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
/**
* 返回用于写入操作的锁
*/
public ReentrantReadWriteLock.WriteLock writeLock() {
return writerLock;
}
/**
* 返回用于读取操作的锁
*/
public ReentrantReadWriteLock.ReadLock readLock() {
return readerLock;
}
abstract static class Sync extends AbstractQueuedSynchronizer {
}
static final class NonfairSync extends Sync {
}
static final class FairSync extends Sync {
}
public static class ReadLock implements Lock, java.io.Serializable {
}
public static class WriteLock implements Lock, java.io.Serializable {
}
}
可以看到,ReentrantReadWriteLock 实现了ReadWriteLock 接口,ReadWriteLock 接口定义了获取读锁和写锁的规范,具体需要实现类去实现;同时其还实现了Serializable 接口,表示可以进行序列化,在源代码中可以看到ReentrantReadWriteLock 实现了自己的序列化逻辑。
9.3 入门案例
场景: 使用ReentrantReadWriteLock 对一个hashmap 进行读和写操作
package com.rg.juc;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
class MyCache {
private volatile Map <String, Object> map = new HashMap <>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void put(String key, Object value) {
readWriteLock.writeLock().lock();//上写锁
try {
System.out.println(Thread.currentThread().getName() + "\t ----写入数据" + key);
//网络拥堵(暂停一会线程)
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
map.put(key, value);
System.out.println(Thread.currentThread().getName() + "\t ----写入完成"+key);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.writeLock().unlock();
}
}
public void get(String key) {
readWriteLock.readLock().lock();//读锁
try {
System.out.println(Thread.currentThread().getName() + "\t ----读取数据" + key);
//网络拥堵(暂停一会线程)
try {
TimeUnit.MILLISECONDS.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
Object result = map.get(key);
System.out.println(Thread.currentThread().getName() + "\t ----读取完成" + result);
} catch (Exception e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
/**
* @author lxy
* @version 1.0
* @Description
* @date 2022/5/1 11:59
*/
public class ReadWriteLockDemo {
public static void main(String[] args) {
MyCache myCache = new MyCache();
//五个读线程
for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.put(tempInt + "", tempInt + "");
}, String.valueOf(i)).start();
}
//五个写线程
for (int i = 1; i <= 5; i++) {
final int tempInt = i;
new Thread(() -> {
myCache.get(tempInt + "");
}, String.valueOf(i)).start();
}
}
}
9.4 小结(重要)
- 在线程持有读锁的情况下,该线程不能取得写锁(因为获取写锁的时候,如果发现当前的读锁被占用,就马上获取失败,不管读锁是不是被当前线程持有)。
- 在线程持有写锁的情况下,该线程可以继续获取读锁(获取读锁时如果发现写锁被占用,只有写锁没有被当前线程占用的情况才会获取失败)。
原因: 当线程获取读锁的时候,可能有其他线程同时也在持有读锁,因此不能把获取读锁的线程“升级”为写锁;而对于获得写锁的线程,它一定独占了读写锁,因此可以继续让它获取读锁,当它同时获取了写锁和读锁后,还可以先释放写锁继续持有读锁,这样一个写锁就“降级”为了读锁。
10 阻塞队列
10.1 BlockingQueue 简介
Concurrent 包中,BlockingQueue 很好的解决了多线程中,如何高效安全“传输”数据的问题。通过这些高效并且线程安全的队列类,为我们快速搭建高质量的多线程程序带来极大的便利。本文详细介绍BlockingQueue 家庭中的所有成员,包括他们各自的功能以及常见使用场景。
阻塞队列,顾名思义,首先它是一个队列, 通过一个共享的队列,可以使得数据由队列的一端输入,从另外一端输出;
- 当队列是空的,从队列中获取元素的操作将会被阻塞
- 当队列是满的,从队列中添加元素的操作将会被阻塞
- 试图从空的队列中获取元素的线程将会被阻塞,直到其他线程往空的队列插入新的元素
- 试图向已满的队列中添加新元素的线程将会被阻塞,直到其他线程从队列中移除一个或多个元素或者完全清空,使队列变得空闲起来并后续新增.
在多线程领域:所谓阻塞,在某些情况下会挂起线程(即阻塞),一旦条件满足,被挂起的线程又会自动被唤起
为什么需要BlockingQueue ?
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切BlockingQueue都给你一手包办了
在concurrent包发布以前,在多线程环境下,我们每个程序员都必须去自己控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度。
10.2 阻塞队列种类分析
架构介绍
种类分析
- ArrayBlockingQueue:由数组结构组成的有界阻塞队列。
- LinkedBlockingQueue:由链表结构组成的有界(但大小默认值为integer.MAX_VALUE)阻塞队列。
- PriorityBlockingQueue:支持优先级排序的无界阻塞队列。
- DelayQueue:使用优先级队列实现的延迟无界阻塞队列。
- SynchronousQueue:不存储元素的阻塞队列,也即单个元素的队列。
- LinkedTransferQueue:由链表组成的无界阻塞队列。
- LinkedBlockingDeque:由链表组成的双向阻塞队列。
10.3 BlockingQueue 核心方法
10.4 入门案例
package com.rg.juc;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* @author lxy
* @version 1.0
* @Description 阻塞队列
* @date 2022/5/3 16:32
*/
public class BlockingQueueDemo {
public static void main(String[] args) throws InterruptedException {
ArrayBlockingQueue<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("x"));//IllegalStateException: Queue full
//
// System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());
// System.out.println(blockingQueue.remove());
// // System.out.println(blockingQueue.remove());//NoSuchElementException
//第二组
/*System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
// System.out.println(blockingQueue.offer("x"));//false
// System.out.println(blockingQueue.peek());//如果有元素则返回队头元素,如果没有则返回NULL
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());//null*/
//第三组 没有返回值
/*blockingQueue.put("a");
blockingQueue.put("b");
blockingQueue.put("c");
// blockingQueue.put("x");//一直阻塞等待...
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());
System.out.println(blockingQueue.take());//一直阻塞等待...*/
//第四组
System.out.println(blockingQueue.offer("a"));
System.out.println(blockingQueue.offer("b"));
System.out.println(blockingQueue.offer("c"));
System.out.println(blockingQueue.offer("x",3L, TimeUnit.SECONDS));
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll());
System.out.println(blockingQueue.poll(3L,TimeUnit.SECONDS));
}
}
11、ThreadPool线程池
11.1 为什么用线程池
例子:
- 10年前单核CPU电脑,假的多线程,像马戏团小丑玩多个球,CPU需要来回切换。
- 现在是多核电脑,多个线程各自跑在独立的CPU上,不用切换效率高。
线程池的优势:
线程池做的工作只要是控制运行的线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过了最大数量,超出数量的线程排队等候,等其他线程执行完毕, 再从队列中取出任务来执行。
它的主要特点为:线程复用;控制最大并发数;管理线程。
第一:降低资源消耗。通过重复利用已创建的线程降低线程创建和销毁造成的销耗。
第二:提高响应速度。当任务到达时,任务可以不需要等待线程创建就能立即执行。
第三:提高线程的可管理性。线程是稀缺资源,如果无限制的创建,不仅会销耗系统资源,还会降低系统的稳定性,使用线程池可以进行统一的分配,调优和监控。
11.2 线程池如何使用
11.2.1 架构说明
Java中的线程池是通过Executor框架实现的,该框架中用到了Executor,Executors,ExecutorService,ThreadPoolExecutor这几个类 Executors是操作线程的工具类
11.2.2 线程池分类
Executors.newFixedThreadPool(int)
: 执行长期任务性能好,创建一个线程池,一池有N个固定的线程,有固定线程数的线程
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
// newFixedThreadPool创建的线程池corePoolSize和maximumPoolSize值是相等的,它使用的是LinkedBlockingQueue
Executors.newSingleThreadExecutor()
: 一个任务一个任务的执行,一池一线程
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>()));
}
// newSingleThreadExecutor 创建的线程池corePoolSize和maximumPoolSize值都是1,它使用的是LinkedBlockingQueue
Executors.newCachedThreadPool()
:执行很多短期异步任务,线程池根据需要创建新线程,但在先前构建的线程可用时将重用它们。可扩容,遇强则强
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,60L, TimeUnit.SECONDS,new SynchronousQueue<Runnable>());
}
// newCachedThreadPool创建的线程池将corePoolSize设置为0,将maximumPoolSize设置为Integer.MAX_VALUE,它使用的是SynchronousQueue,也就是说来了任务就创建线程运行,当线程空闲超过60秒,就销毁线程。
11.2.3 ThreadPoolExecutor底层原理
11.3 入门案例
package com.rg.juc;
import java.util.concurrent.*;
/**
* @author lxy
* @version 1.0
* @Description 银行开放窗口,顾客办理业务
* @date 2022/5/3 21:58
*/
public class MyThreadPoolDemo {
public static void main(String[] args) {
initPool();
}
private static void initPool() {
// ExecutorService threadPool = Executors.newFixedThreadPool(5);//一池五个工作线程,类似一个银行有5个受理窗口
// ExecutorService threadPool = Executors.newSingleThreadExecutor();//一池1个工作线程,类似一个银行一个受理窗口
ExecutorService threadPool = Executors.newCachedThreadPool();//一池N个工作线程,类似一个银行有N个受理窗口
try{
//模拟有10个顾客来银行办理业务,目前池子里面有5个工作人员提供服务
for (int i = 1; i <= 10; i++) {
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t 办理业务");
});
//暂停线程 ,线程会变得逐渐有序..
// try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();//释放线程池
}
}
}
11.4 线程池七大参数
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.corePoolSize = corePoolSize;
this.maximumPoolSize = maximumPoolSize;
this.workQueue = workQueue;
this.keepAliveTime = unit.toNanos(keepAliveTime);
this.threadFactory = threadFactory;
this.handler = handler;
}
1、corePoolSize:线程池中的常驻核心线程数 (也就是当前线程数)
2、maximumPoolSize:线程池中能够容纳同时执行的最大线程数,此值必须大于等于1
3、keepAliveTime:多余的空闲线程的存活时间当前池中线程数量超过corePoolSize时,当空闲时间
达到keepAliveTime时,多余线程会被销毁直到只剩下corePoolSize个线程为止
4、unit:keepAliveTime的单位
5、workQueue:任务队列,被提交但尚未被执行的任务
6、threadFactory:表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认的即可
7、handler:拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大线程(maximumPoolSize)时如何来拒绝请求执行的runnable的策略
11.5 线程池底层工作原理
分析
1、在创建了线程池后,开始等待请求。
2、当调用execute()方法添加一个请求任务时,线程池会做出如下判断:
2.1如果正在运行的线程数量小于corePoolSize,那么马上创建线程运行这个任务;
2.2如果正在运行的线程数量大于或等于corePoolSize,那么将这个任务放入队列;
2.3如果这个时候队列满了且正在运行的线程数量还小于maximumPoolSize,那么还是要创建非核心线程立刻运行这个任务;
2.4如果队列满了且正在运行的线程数量大于或等于maximumPoolSize,那么线程池会启动饱和拒绝策略来执行。
3、当一个线程完成任务时,它会从队列中取下一个任务来执行。
4、当一个线程无事可做超过一定的时间(keepAliveTime)时,线程会判断:
如果当前运行的线程数大于corePoolSize,那么这个线程就被停掉。
所以线程池的所有任务完成后,它最终会收缩到corePoolSize的大小。
11.6 线程池的拒绝策略
11.6.1 线程池是什么
等待队列已经排满了,再也塞不下新任务了
同时,线程池中的max线程也达到了,无法继续为新任务服务。
这个是时候我们就需要拒绝策略机制合理的处理这个问题。
11.6.2 JDK内置的拒绝策略
- AbortPolicy(默认):直接抛出RejectedExecutionException异常阻止系统正常运行
- CallerRunsPolicy:“调用者运行”一种调节机制,该策略既不会抛弃任务,也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量。
- DiscardOldestPolicy:抛弃队列中等待最久的任务,然后把当前任务加人队列中. 尝试再次提交当前任务。也就是抛弃最先入队列还没处理的几个。
- DiscardPolicy:该策略默默地丢弃无法处理的任务,不予任何处理也不抛出异常。如果允许任务丢失,这是最好的一种策略。
以上内置拒绝策略均实现了 RejectedExecutionHandle
接口
11.7 工作中线程池用哪个?
在工作中单一的/固定数的/可变的三种创建线程池的方法哪个用的多?超级大坑
答案是一个都不用,我们工作中只能使用自定义的
Executors中JDK已经给你提供了,为什么不用?
自定义线程池代码
public class MyThreadPoolDemo {
public static void main(String[] args) {
ExecutorService threadPool = new ThreadPoolExecutor(
2,
5,
2L,
TimeUnit.SECONDS,
new LinkedBlockingDeque <>(3),
Executors.defaultThreadFactory(),
//new ThreadPoolExecutor.AbortPolicy()
//new ThreadPoolExecutor.CallerRunsPolicy()
//new ThreadPoolExecutor.DiscardOldestPolicy()
new ThreadPoolExecutor.DiscardPolicy());
try{
//模拟有10个顾客来银行办理业务,目前池子里面有5个工作人员提供服务
for (int i = 1; i <= 9; i++) {
final int tempInt = i;
threadPool.execute(()->{
System.out.println(Thread.currentThread().getName()+"\t 办理业务"+tempInt);
});
//暂停线程 ,线程会变得逐渐有序..
// try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); }
}
}catch (Exception e){
e.printStackTrace();
}finally {
threadPool.shutdown();//释放线程池
}
}
}
12、Java8之流式计算复习
12.1 函数式接口
java内置核心四大函数式接口
如何使用
//R apply(T t);函数型接口,一个参数,一个返回值
Function<String,Integer> function = t ->{return t.length();};
System.out.println(function.apply("abcd"));
//boolean test(T t);断定型接口,一个参数,返回boolean
Predicate<String> predicate = t->{return t.startsWith("a");};
System.out.println(predicate.test("a"));
// void accept(T t);消费型接口,一个参数,没有返回值
Consumer<String> consumer = t->{
System.out.println(t);
};
consumer.accept("javaXXXX");
//T get(); 供给型接口,无参数,有返回值
Supplier<String> supplier =()->{return UUID.randomUUID().toString();};
System.out.println(supplier.get());
private static void testFunction() {
// Function<String,Integer> function = new Function <String, Integer>() {
// @Override
// public Integer apply(String s) {
// return s.length();
// }
// };
//R apply(T t);函数型接口,一个参数,一个返回值
Function <String,Integer> function = s->{ return s.length(); };
System.out.println(function.apply("abc"));
//boolean test(T t);断定型接口,一个参数,返回boolean
// Predicate<String> predicate = new Predicate <String>() {
// @Override
// public boolean test(String s) {
// return s.isEmpty();
// }
// };
Predicate <String> predicate = s->{return s.isEmpty();};
System.out.println(predicate.test("abc"));
// void accept(T t);消费型接口,一个参数,没有返回值
// Consumer<String> consumer = new Consumer <String>() {
// @Override
// public void accept(String s) {
// System.out.println("I love Java!");
// }
// };
Consumer <String> consumer = s -> {System.out.println("I love Java!"); };
consumer.accept("java");
//T get(); 供给型接口,无参数,有返回值
// Supplier<String> supplier = new Supplier <String>() {
// @Override
// public String get() {
// return "Java";
// }
// };
Supplier <String> supplier = ()->{
return "Java";
};
System.out.println(supplier.get());
}
12.2 Stream流
12.2.1 是什么?
流(Stream) 到底是什么呢?
是数据渠道,用于操作数据源(集合、数组等)所生成的元素序列。
“集合讲的是数据,流讲的是计算!”
12.2.2 特点
- Stream 自己不会存储元素
- Stream 不会改变源对象。相反,他们会返回一个持有结果的新Stream。
- Stream 操作是延迟执行的。这意味着他们会等到需要结果的时候才执行。
12.2.3 如何用
1.创建一个Stream:一个数据源(数组、集合)
2.中间操作:一个中间操作,处理数据源数据
3.终止操作:一个终止操作,执行中间操作链,产生结果
类比: 源头=>中间流水线=>结果
12.2.4 代码演示
package com.rg.juc;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.sql.SQLOutput;
import java.util.Arrays;
import java.util.List;
import java.util.function.Consumer;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.function.Supplier;
/**
* @author lxy
* @version 1.0
* @Description
* @date 2022/5/4 20:51
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
class User
{
private Integer id;
private String userName;
private int age;
}
/**
* @create 2019-02-26 22:24
*
* 题目:请按照给出数据,找出同时满足
* 偶数ID且年龄大于24且用户名转为大写且用户名字母倒排序
* 最后只输出一个用户名字
*/
public class StreamDemo {
public static void main(String[] args) {
User u1 = new User(11, "a", 23);
User u2 = new User(12, "b", 24);
User u3 = new User(13, "c", 22);
User u4 = new User(14, "d", 28);
User u5 = new User(16, "e", 26);
List <User> list = Arrays.asList(u1, u2, u3, u4, u5);
list.stream().filter(u->{
return u.getId() % 2 ==0;
}).filter(t->{
return t.getAge() > 24;
}).map(m->{
return m.getUserName().toUpperCase();
}).sorted((o1,o2)->{
return o2.compareTo(o1);
}).forEach(System.out::println);
}
private static void testFunction() {
// Function<String,Integer> function = new Function <String, Integer>() {
// @Override
// public Integer apply(String s) {
// return s.length();
// }
// };
//R apply(T t);函数型接口,一个参数,一个返回值
Function <String,Integer> function = s->{ return s.length(); };
System.out.println(function.apply("abc"));
//boolean test(T t);断定型接口,一个参数,返回boolean
// Predicate<String> predicate = new Predicate <String>() {
// @Override
// public boolean test(String s) {
// return s.isEmpty();
// }
// };
Predicate <String> predicate = s->{return s.isEmpty();};
System.out.println(predicate.test("abc"));
// void accept(T t);消费型接口,一个参数,没有返回值
// Consumer<String> consumer = new Consumer <String>() {
// @Override
// public void accept(String s) {
// System.out.println("I love Java!");
// }
// };
Consumer <String> consumer = s -> {System.out.println("I love Java!"); };
consumer.accept("java");
//T get(); 供给型接口,无参数,有返回值
// Supplier<String> supplier = new Supplier <String>() {
// @Override
// public String get() {
// return "Java";
// }
// };
Supplier <String> supplier = ()->{
return "Java";
};
System.out.println(supplier.get());
}
}
13、分支合并框架
13.1 原理
Fork:把一个复杂任务进行分拆,大事化小
Join:把分拆任务的结果进行合并
13.2 相关类
ForkJoinPool
分支合并池 类比=> 线程池
ForkJoinTask
ForkJoinTask 类比=> FutureTask
RecursiveTask
递归任务:继承后可以实现递归(自己调自己)调用的任务
class Fibonacci extends RecursiveTask<Integer> {
final int n;
Fibonacci(int n) { this.n = n; }
Integer compute() {
if (n <= 1)
return n;
Fibonacci f1 = new Fibonacci(n - 1);
f1.fork();
Fibonacci f2 = new Fibonacci(n - 2);
return f2.compute() + f1.join();
}
}
13.3 入门案例
/**
* @author lxy
* @version 1.0
* @Description 分支合并案例
* @date 2022/5/5 17:07
*/
class MyTask extends RecursiveTask<Integer> {//RecursiveTask:递归任务
private static final Integer ADJUST_VALUE = 10;
private int begin;
private int end;
private int result;
public MyTask(int begin, int end) {
this.begin = begin;
this.end = end;
}
@Override
protected Integer compute() {
if(end-begin <= ADJUST_VALUE){
for (int i = begin; i <= end; i++) {
result = result + i;
}
}else{
int middle = (end + begin) / 2;
MyTask task01 = new MyTask(begin, middle);
MyTask task02 = new MyTask(middle + 1, end);
task01.fork();//进行递归执行
task02.fork();
result = task01.join()+task02.join();//将结果进行合并
}
return result;
}
}
public class ForkJoinDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyTask myTask = new MyTask(0, 10);
ForkJoinPool threadPool = new ForkJoinPool();
ForkJoinTask <Integer> forkJoinTask = threadPool.submit(myTask);
System.out.println(forkJoinTask.get());
threadPool.shutdown();//关闭线程池
}
}
14、异步回调
14.1 原理
14.2 入门案例
/**
* @author lxy
* @version 1.0
* @Description 同步,异步,异步回调 案例
* @date 2022/5/5 18:18
*/
public class CompletableFutureDemo {
public static void main(String[] args) throws ExecutionException, InterruptedException {
//同步,异步,异步回调
CompletableFuture<Void> completableFuture = CompletableFuture.runAsync(()->{
System.out.println(Thread.currentThread().getName()+"没有返回,update mysql ok");
});
completableFuture.get();
//异步回调
CompletableFuture<Integer> completableFuture2 = CompletableFuture.supplyAsync(()->{
System.out.println(Thread.currentThread().getName()+"\t completableFuture2");
int age = 10 / 0;
return 1024;
});
completableFuture2.whenComplete((t,u)->{
System.out.println("------t="+t);
System.out.println("------u="+u);;
}).exceptionally(f->{//t是正常结果 u和f都是异常信息
System.out.println("------exception:"+f.getMessage());
return 444;
}).get();
}
}
总结
OK,今天关于 JUC的知识分享 就到这里,希望本篇文章能够帮助到大家,同时也希望大家看后能学有所获!!!
后序博主忙完找暑假实习,将对JUC并发进行深入的学习和分享,欢迎大家继续观看我的JUC并发编程专栏!