JUC(java.util.concurrent)
2021-01-11 11:53:07 0 举报
AI智能生成
JUC高并发
作者其他创作
大纲/内容
JUC是什么?
java.util.concurrent 在并发编程中使用的工具类
三个构成
Java.util.concurrent
java.util.concurrent.atomic(原子性)
java.util.concurrent.locks(锁)
进程/线程
什么是进程?
进程时一个具有一定独立功能的程序关于某个数据集合的一次运行活动,它是操作系统动态执行的基本单元,在传统的操作系统中,在传统的操作系统系统中,进程即使基本的分配单元,也是基本的执行单元
什么是线程?
通常在一个进程中可以包含若干个线程,当然一个进程中至少有一个线程,不然没有存在的意义
,线程可以利用进程所拥有的资源,在引入线程的操作系统中,通常都是把进程作为分配资源的基本单位,而把线程作为独立运行和独立调度的基本单位
由于线程比进程更小,基本上不拥有系统资源,姑对它的调度所付出的开销就会小的多,能更高效的提高系统多个程序间的并发执行程度
并发/并行
什么是并发
指在同一时刻只能有一条指令执行,但多个进程指令被快速的轮换执行,使得在宏观上具有多个进程同时执行的效果,但在微观上并不是同时执行的,只是把时间分成若干段,使多个进程快速交替的执行。
什么是并行
指在同一时刻,有多条指令在多个处理器上同时执行。所以无论从微观还是从宏观来看,二者都是一起执行的。
wait /sleep 区别
wait 放开手去睡,释放自己的锁
sleep 紧握手去睡,不会释放锁
多线程的5中状态
new 新建
runable 运行
blocked 阻塞
waiting 一直等待
timed_waiting 等待指定的时间,时间过后退出
TERMINATED 结束
Lambda表达式
接口显示/默认 @FunctionalInterface
口诀
左边小括号,写死右箭头,右边大括号 ()->{}
函数式接口只能有一个未实现的方法
也可以有多个已经实现的default/static方法
线程间的通信
在高内聚低耦合的基础上,线程操作资源类
判断/干活/通知
防止虚假唤醒(用while代替if)
5个Java常见的异常
ConcurrrentModifictionException(集合并发修改异常)
OutOfMemoryError (堆内存溢出)
ClassNotFoudException (类找不到)
stackoverflowError (栈内存错误)
nullpointerException (空指针)
Callable 接口
是什么
已经存在 runable接口了 为什么会有FutureTask这个类呢
因为 线程异步+并发从而导致出来
获取多线程的四种方式
继承Thread类
实现 runable接口
实现 callable接口
线程池
与 runable对比
1),是否有返回值
2),是否有异常
3),落地方法不一样 runable 是run callable是call方法
4),一个带泛型一个不带泛型
怎么用
直接替换 runable是否可行
认识不同的人找中间人
FutureTask
原理
就类似于糖葫芦,这时有一个糖葫芦里面有4个(表示4个线程),如果第三个线程需要执行10秒,其它三个线程需要执行一秒,这时执行第三个线程的时候需要阻塞,等待第三个完成之后,之后才能继续进行,这时就需要使用 FutureTask 来控制阻塞
FutureTask 就是 把第三个阻塞的线程拆解出来 ,其它三个线程执行自己的,第三个线程独立出来,不让阻塞,最后把三个线程结果和第三个线程结果相加,就等到最终结果
FutureTaskDemo
注意: 当多个线程来抢 FutureTask 这时,只会执行一次FutureTask 的call方法,如果想执行多次,这时就需要创建多个这个实例对象
JUC强大的辅助类
CountDownLatch 减少计数
栗子
结果为
原理
CountDownLatch 主要有两个方法,当一个或多个线程调用 await方法时,这些线程会阻塞
其它线程调用countdown 方法会将计数器减一 (调用countdown 方法的线程不会阻塞)
当计数器的值变为0时,因 await方法阻塞的线程会被唤醒,继续执行
CyclicBarrier 循环栅栏
栗子
结果
原理
Semaphore 信号灯
栗子
结果
原理
ReadWriteLock (读写锁)
为什么已经存在lock,现在又出来一个 readwritelock ?
1),lock 是一个读写锁,在修改的数据的时候,别的线程都无法进行读写操作 类似于表锁
2),readwritelock 是一个对比lock 是对锁一个更细粒化的操作 类似于行锁
类似案例
红蜘蛛
缓存
代码
资源类
main
总结
多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行
但是
如果一个线程去写共享资源,就不应该再有其它线程可以对资源进行读或写
但是
如果一个线程去写共享资源,就不应该再有其它线程可以对资源进行读或写
读-读能共存
读-写不能共存
写-写不能共存
BlockingQueue 阻塞队列
栗子
栈与队列
队列: 先进先出
栈: 先进后出,后来先出
阻塞队列
阻塞: 必须要阻塞/不得不阻塞 (阻塞队列是一个队列,在数据结构中如下)
阻塞队列用处
在多线程领域: 所谓的阻塞,在某些情况下会挂起线程,一旦条件满足,被挂起的线程又会自动被唤起
为什么需要 blockingQueue?
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切 blockingqueue 都给你一手包办了
在 concurrent 包发布以前,在多线程环境下,我们每个程序员必须自己去控制这些细节,尤其还要兼顾效率和线程安全,而这会给我们的程序带来不小的复杂度
架构梳理,种类分析
种类分析
ArrayBlockingQueue
由数组结构组成的有界阻塞队列
LinkedBlockingQueue
由链表组成的有界 (但大小默认值为 integer.MAX_VALUE)阻塞队列
PriorityBlockingQueue
支持优先级排序的无界阻塞队列
DelayQueue
使用优先级队列实现的延迟无界阻塞队列
SynchronousQueue
不存储元素的阻塞队列,也即单个元素的队列
synchronousQueue 没有容量,与其它 BlockingQueue 不同,Synchronous 是一个不存储元素的 BlockingQueue 每一个put操作必须要等待一个take 操作,否则不能继续添加元素,反之亦然
SynchronousQueueDemo
LinckedTransferQueue
由链表组成的无界阻塞队列
LinkedBlockingDeque
由链表组成的双向阻塞队列
BlockingQueue 核心方法
方法类型
抛出异常
添加
add
移除
remove
删除
element
特殊值
添加
offer
移除
poll
删除
peek
阻塞
添加
put
移除
take
删除
不可用
超时
添加
oeffer(e,time,unit)
移除
poll(time,unit)
删除
不可用
用在哪里
生产者消费者模式
Lock 的手动传统版
阻塞队列版本
线程池
消息中间件
ThreadPool线程池
ThreadPoolDemo栗子
为什么要用线程池?
例子: 10年前单核cpu电脑,假的多线程,像马戏团小丑玩多个球,cpu需要来回切换,现在是多核电脑,多个线程各自泡在独立的cpu上,不用切换效率高。
优势: 线程池做的工作主要是控制运行线程数量,处理过程中将任务放入队列,然后在线程创建后启动这些任务,如果线程数量超过最大数量,超出数量的线程排队等候,等其它线程执行完毕,再从队列中取出任务来执行
它的主要特点为: 线程复用,控制最大并发数,管理线程
1), 降低资源消耗,通过重复利用己创建的线程降低线程创建和销毁造成的消耗
2),提高响应速度,当任务到达时,任务可以不需要等待线程的创建就能立即执行
3),提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统稳定性,使用线程池可以进行统一的分配,调优和监控
线程池如何使用
架构说明
Java中的线程池是通过Executor 框架实现的,该框架中用到了 Executor , Executors , ExecutorService , ThreadPoolExecutor 这几个类
编码实现
Executors.newFixedThreadPool(int)
执行长期任务性能比较好,创建一个线程池,一个池中有N个固定的线程,有固定线程数的线程
Executor.newScheduledThreadPool()
线程带时间调度的(池子里面的线程每隔2s执行一次)
Java8新出的 Executors.newWorkStealingPool(int)
使用目前机器上可用的处理器作为它的并行级别
Executors.newSingleThreadExecutor()
一个任务一个任务地执行,一池一线程
Executors.newCachedThreadPool()
一池N线程,自动切换,适用 执行很多短期异步的小程序或者负载缴轻的服务器
代码
底层原理 ThreadPoolExecutor
public static ExecutorService newFixedThreadPool(int nThreads) {
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
return new ThreadPoolExecutor(nThreads, nThreads,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>());
}
public static ExecutorService newSingleThreadExecutor() {
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
return new FinalizableDelegatedExecutorService
(new ThreadPoolExecutor(1, 1,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<Runnable>()));
}
public static ExecutorService newCachedThreadPool() {
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
60L, TimeUnit.SECONDS,
new SynchronousQueue<Runnable>());
}
ThreadExecutorPool 线程池几个重要的参数
7大参数
corePoolSize
线程池中常驻核心线程数
maximumPoolSize
线程池中能够容纳同时执行的最大线程数,此值必须大于等于1
keepAliveTime
多余的空闲线程的存活时间,当前池中线程数量超过 corePoolSize 时,当空闲时间达到 keepAliveTime时,多余线程会被销毁直到只剩下 corePoolSize个线程为止
unit
keepAliveTime 的单位
workQueue
任务阻塞队列,被提交尚未执行的任务
threadFactory
表示生成线程池中工作线程的线程工厂,用于创建线程,一般默认的即可
handler
拒绝策略,表示当队列满了,并且工作线程大于等于线程池的最大连接数(MaximunPoolSize) 时如何来拒绝请求执行的 runable的策略
线程池底层工作原理
运行流程图
1), 在创建了线程池后,开始等待请求
2), 当调用 execute() 方法添加一个请求任务时,线程池会做出以下判断
2-1), 如果整在运行的线程数量小于 corePoolSize 那么马上创建线程运行这个任务
2-2),如果正在运行的线程数量大于或等于 corePoolSize 那么将这个任务放入队列
2-3),如果这个时候队列满了且正在运行的线程数量小于 maximumPoolSize 那么还是要创建非核心线程立即运行这个任务
2-4),如果队队列满了且正在运行线程的数量大于或等于 maximumPoolSize ,那么线程会启动饱和拒绝策略来执行
3), 当一个线程完成任务时,它会从队列中取下一个任务来执行
4), 当一个线程无事可做超过一定的时间 (keepAliveTime)时,线程会判断
4-1), 如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉
所以线程池所有任务完成后,它最终就会收缩到 corePoolSize的大小
线程池用哪个?生产中如何设置合理的参数
线程池的拒绝策略
是什么
等待队列已经排满了,再也塞不下新的任务
同时,线程池中的 max 线程也满了,无法继续为新的任务提供服务
这个时候我们就需要拒绝策略机制合理处理这个任务
JDK内置的拒绝策略
AbortPolicy(默认)
直接抛出 RejectedExecutionHandler 异常阻止系统正常运行
DiscardPolicy
该策略默默地丢弃无法处理的任务.不予任何处理也不会抛出异常,如果允许任务丢失,这是最好的一种策略
DiscardOldestPolicy
抛弃队列中等待最久的任务,然后把当前任务加入队列中,尝试再次提交任务
CallerRunsPolicy
调用者运行一种调节机制,该策略既不会抛弃任务也不会抛出异常,而是将某些任务回退到调用者,从而降低新任务的流量
以上内置拒绝策略均实现了 RejectedExecutorHandler 接口
在工作中单一的/固定数的/可变的三种创建线程池的方法那个用的多?
答案: 一个都不用,我们工作中只能使用自定义的
Executors 中jdk已经给你提供了,为什么不用呢?
1),线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
2),在阿里巴巴手册,线程池不允许使用 Executor 去创建,而是通过 TheadPoolExecutor 的方式
在工作中如何使用线程池,是否自定义过线程池
合理配置线程池你是如何考虑的?
CPU 密集型
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行,CPU密集任务只有在真正的多喝CPU上才能得到加速(通过多线程)
而在单核CPU上(悲催吧),无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力也就那些
CPU密集型任务配置尽可能少的线程数量,一般公式为 CPU核数+1个线程的线程池
IO密集型
由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 CPU核数*2
IO密集型,即该任务有大量的IO,即大量的阻塞,在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待
所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间
参考公式 : CPU核数/1-阻塞系数 (阻塞系数在 0.8-0.9之间)
比如8核CPU: 8/1-0.9=80个线程数
Java8之流式函数式接口
函数式接口
java.util.function
java内置核心四大函数式接口
函数式接口
参数类型
返回类型
用途
消费型接口 -Consumer<T>
T
void
对类型为T的对象应用操作,包含方法 void accept(T t)
供给型接口 -Supplier<T>
无
T
返回类型为T的对象,包含方法 T get();
函数型接口-Function<T,R>
T
R
对类型T的对象应用操作,并返回结果,结果是R类型的对象,包含方法 R apply(T t)
断定型接口-Predicate<T>
T
boolean
确定类型为T的对象是否满足某约束,并返回boolean值,包含方法 boolean test(T t)
Stream 流
what?
数据渠道,用于操作数据源(集合,数组等)所产生的元素序列 集合讲的是数据,流讲的是计算
why?
特点
Stream 自己不会存储数据
Stream 不会改变源对象,相反,它们会返回一个持有结果的新对象
Stream 操作是延迟执行的,这意味着它们会等到需要结果的时候才执行
how
阶段
创建一个 Stream : 一个数据源(数组,集合)
中间操作: 一个中间操作,处理数据源数据
终止操作: 一个终止操作,执行中间操作链,产生结果
源头 => 中间流水线 =>结果
实例
Stream 流
分支合并(ForkJoinPool)框架
原理
相关类
ForkJoinPool
ForkJoinTask
实例
异步回调(CompletableFuture)
原理
实例
没有返回值
又返回值
0 条评论
下一页