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方法
5个Java常见的异常
ConcurrrentModifictionException(集合并发修改异常)
OutOfMemoryError (堆内存溢出)
ClassNotFoudException (类找不到)
stackoverflowError (栈内存错误)
nullpointerException (空指针)
Callable 接口
是什么
已经存在 runable接口了 为什么会有<b><font color="#c41230">FutureTask</font></b>这个类呢
因为 线程<b><font color="#c41230">异步+并发</font></b>从而导致出来
获取多线程的四种方式
继承Thread类
实现 runable接口
实现 callable接口
线程池
与 runable对比
1),是否有返回值
2),是否有异常
3),落地方法不一样 runable 是run callable是call方法
4),一个带泛型一个不带泛型
怎么用
直接替换 runable是否可行
认识不同的人找中间人
FutureTask
原理
就类似于糖葫芦,这时有一个糖葫芦里面有4个(表示4个线程),如果第三个线程需要执行10秒,其它三个线程需要执行一秒,这时执行第三个线程的时候需要<b><font color="#c41230">阻塞</font></b>,等待第三个完成之后,之后才能继续进行,这时就需要使用 FutureTask 来控制阻塞
FutureTask 就是 把第三个阻塞的线程拆解出来 ,其它三个线程执行自己的,第三个线程独立出来,不让阻塞,最后把三个线程结果和第三个线程结果相加,就等到最终结果
FutureTaskDemo
注意: 当多个线程来抢 FutureTask 这时,只会执行一次FutureTask 的call方法,如果想执行多次,这时就需要创建多个这个实例对象
ReadWriteLock (读写锁)
为什么已经存在lock,现在又出来一个 readwritelock ?
1),lock 是一个读写锁,在修改的数据的时候,别的线程都无法进行读写操作 类似于表锁
2),readwritelock 是一个对比lock 是对锁一个更细粒化的操作 类似于行锁
总结
多个线程同时读一个资源类没有任何问题,所以为了满足并发量,读取共享资源应该可以同时进行<br>但是<br>如果一个线程去写共享资源,就不应该再有其它线程可以对资源进行读或写
读-读能共存
读-写不能共存
写-写不能共存
<b><font color="#c41230">BlockingQueue 阻塞队列</font></b>
栗子
栈与队列
队列: 先进先出
栈: 先进后出,后来先出
阻塞队列
阻塞: 必须要阻塞/不得不阻塞 (阻塞队列是一个队列,在数据结构中如下)
阻塞队列用处
在多线程领域: 所谓的阻塞,在某些情况下会<font color="#c41230">挂起</font>线程,一旦条件满足,被挂起的线程又会自动被<font color="#c41230">唤起</font>
为什么需要 blockingQueue?
好处是我们不需要关心什么时候需要阻塞线程,什么时候需要唤醒线程,因为这一切 blockingqueue 都给你一手包办了
在 concurrent 包发布以前,在多线程环境下,<font color="#c41230">我们每个程序员必须自己去控制这些细节,尤其还要兼顾效率和线程安全</font>,而这会给我们的程序带来不小的复杂度
架构梳理,种类分析
种类分析
<b><font color="#c41230">ArrayBlockingQueue</font></b>
由<font color="#c41230"><b>数组</b></font>结构组成的<font color="#c41230"><b>有界</b></font>阻塞队列
<font color="#c41230"><b>LinkedBlockingQueue</b></font>
由<font color="#c41230"><b>链表</b></font>组成的<b><font color="#c41230">有界 (但大小默认值为 integer.MAX_VALUE)阻塞队列</font></b>
PriorityBlockingQueue
支持优先级排序的无界阻塞队列
DelayQueue
使用优先级队列实现的延迟无界阻塞队列
<b><font color="#c41230">SynchronousQueue</font></b>
<font color="#c41230"><b>不存储元素的阻塞队列,也即单个元素的队列</b></font>
synchronousQueue 没有容量,与其它 BlockingQueue 不同,Synchronous 是一个不存储元素的 BlockingQueue 每一个put操作必须要等待一个take 操作,否则不能继续添加元素,反之亦然
SynchronousQueueDemo
LinckedTransferQueue
由链表组成的无界阻塞队列
LinkedBlocking<font color="#f384ae"><b>Deque</b></font>
由链表组成的双向阻塞队列
BlockingQueue 核心方法
方法类型
<b><font color="#c41230">抛出异常</font></b>
<b><font color="#c41230">特殊值</font></b>
<b><font color="#c41230">阻塞</font></b>
<b><font color="#c41230">超时</font></b>
用在哪里
生产者消费者模式
Lock 的手动传统版
阻塞队列版本
线程池
消息中间件
<b><font color="#c41230">ThreadPool线程池</font></b>
ThreadPoolDemo栗子
为什么要用线程池?
例子: 10年前单核cpu电脑,假的多线程,像马戏团小丑玩多个球,cpu需要来回切换,现在是多核电脑,多个线程各自泡在独立的cpu上,不用切换效率高。
优势: 线程池做的工作主要是<b><font color="#c41230">控制运行线程数量</font></b>,<b><font color="#c41230">处理过程中将任务放入队列</font></b>,然后在线程创建后启动这些任务,<b><font color="#c41230">如果线程数量超过最大数量,超出数量的线程排队等候</font></b>,等其它线程执行完毕,再从队列中取出任务来执行
它的主要特点为: <b><font color="#c41230">线程复用,控制最大并发数,管理线程</font></b>
1), 降低资源消耗,通过重复利用己创建的线程降低线程创建和销毁造成的消耗
2),提高响应速度,当任务到达时,任务可以不需要等待线程的创建就能立即执行
3),提高线程的可管理性,线程是稀缺资源,如果无限制的创建,不仅会消耗系统资源,还会降低系统稳定性,使用线程池可以进行统一的分配,调优和监控
线程池如何使用
架构说明
Java中的线程池是通过Executor 框架实现的,该框架中用到了 <b><font color="#c41230">Executor , Executors </font></b>, ExecutorService , ThreadPoolExecutor 这几个类
编码实现
<b><font color="#c41230">Executors.newFixedThreadPool(int)</font></b>
执行长期任务性能比较好,创建一个线程池,<b><font color="#c41230">一个池中有N个固定的线程</font></b>,有固定线程数的线程
Executor.newScheduledThreadPool()
线程带时间调度的(池子里面的线程每隔2s执行一次)
Java8新出的 Executors.newWorkStealingPool(int)
使用目前机器上可用的处理器作为它的并行级别
<b><font color="#c41230">Executors.newSingleThreadExecutor()</font></b>
一个任务一个任务地执行,<b><font color="#c41230">一池一线程</font></b>
<b><font color="#c41230">Executors.newCachedThreadPool()</font></b>
<b><font color="#c41230">一池N线程</font></b>,自动切换,适用 执行很多短期异步的小程序或者负载缴轻的服务器
代码
底层原理 <b><font color="#c41230">ThreadPoolExecutor</font></b>
public static ExecutorService <font color="#c41230"><b>newFixedThreadPool</b></font>(int nThreads) {<br> return <b><font color="#c41230">new ThreadPoolExecutor</font></b>(nThreads, nThreads,<br> 0L, TimeUnit.MILLISECONDS,<br> new <font color="#c41230"><b>LinkedBlockingQueue</b></font><Runnable>());<br> }
public static ExecutorService <font color="#c41230"><b>newSingleThreadExecutor</b></font>() {<br> return new FinalizableDelegatedExecutorService<br> (<b><font color="#c41230">new ThreadPoolExecutor</font></b>(1, 1,<br> 0L, TimeUnit.MILLISECONDS,<br> new <font color="#c41230"><b>LinkedBlockingQueue</b></font><Runnable>()));<br> }
public static ExecutorService <font color="#c41230"><b>newCachedThreadPool</b></font>() {<br> return <b><font color="#c41230">new ThreadPoolExecutor</font></b>(0, Integer.MAX_VALUE,<br> 60L, TimeUnit.SECONDS,<br> new <font color="#c41230"><b>SynchronousQueue</b></font><Runnable>());<br> }
<b><font color="#c41230">ThreadExecutorPool 线程池几个重要的参数</font></b>
<b><font color="#c41230">7大参数</font></b>
corePoolSize
线程池中常驻<b><font color="#c41230">核心线程数</font></b>
maximumPoolSize
线程池中能够<b><font color="#c41230">容纳同时执行的最大线程数</font></b>,此值必须大于等于1
keepAliveTime
多余的空闲线程的<b><font color="#c41230">存活时间</font></b>,当前池中线程数量超过 corePoolSize 时,当空闲时间达到 keepAliveTime时,多余线程会被销毁直到只剩下 corePoolSize个线程为止
workQueue
<b><font color="#c41230">任务阻塞队列</font></b>,被提交尚未执行的任务
threadFactory
表示生成线程池中工作线程的线程工厂,用于创建线程,<b><font color="#c41230">一般默认的即可</font></b>
handler
<b><font color="#c41230">拒绝策略</font></b>,表示当队列满了,并且工作线程大于等于线程池的最大连接数(MaximunPoolSize) 时如何来拒绝请求执行的 runable的策略
<b><font color="#c41230">线程池底层工作原理</font></b>
1), 在创建了线程池后,开始等待请求
2), 当调用 execute() 方法添加一个请求任务时,线程池会做出以下判断
2-1), 如果整在运行的线程数量小于 corePoolSize 那么马上创建线程运行这个任务
2-2),如果正在运行的线程数量大于或等于 corePoolSize 那么将这个任务<b><font color="#c41230">放入队列</font></b>
2-3),如果这个时候队列满了且正在运行的线程数量小于 maximumPoolSize 那么还是要创建非核心线程立即运行这个任务
2-4),如果队队列满了且正在运行线程的数量大于或等于 maximumPoolSize ,那么线程会<b><font color="#c41230">启动饱和拒绝策略来执行</font></b>
3), 当一个线程完成任务时,它会从队列中取下一个任务来执行
4), 当一个线程无事可做超过一定的时间 (keepAliveTime)时,线程会判断
4-1), 如果当前运行的线程数大于 corePoolSize,那么这个线程就被停掉
所以线程池所有任务完成后,<font color="#c41230"><b>它最终就会收缩到 corePoolSize的大小</b></font>
<b><font color="#c41230">线程池用哪个?生产中如何设置合理的参数</font></b>
线程池的拒绝策略
是什么
<font color="#c41230"><b>等待队列已经排满了</b></font>,再也塞不下新的任务
同时,<b><font color="#c41230">线程池中的 max 线程也满了</font></b>,无法继续为新的任务提供服务
这个时候我们就需要<b><font color="#c41230">拒绝策略机制</font></b>合理处理这个任务
JDK内置的<b><font color="#c41230">拒绝策略</font></b>
<b><font color="#c41230">AbortPolicy</font></b>(默认)
直接抛出 RejectedExecutionHandler 异常阻止系统正常运行
DiscardPolicy
该策略默默地丢弃无法处理的任务.不予任何处理也不会抛出异常,如果允许任务丢失,这是最好的一种策略
DiscardOldestPolicy
抛弃队列中等待最久的任务,然后把当前任务加入队列中,尝试再次提交任务
CallerRunsPolicy
调用者运行一种调节机制,该策略既不会抛弃任务也不会抛出异常,而是将某些任务<font color="#c41230"><b>回退</b></font>到调用者,从而降低新任务的流量
以上内置拒绝策略均实现了 <b><font color="#c41230">RejectedExecutorHandler </font></b>接口
在工作中单一的/固定数的/可变的三种创建线程池的方法那个用的多?
答案: 一个都不用,我们工作中只能使用自定义的
Executors 中jdk已经给你提供了,为什么不用呢?
1),线程资源必须通过线程池提供,不允许在应用中自行显式创建线程
2),在阿里巴巴手册,线程池不允许使用 Executor 去创建,而是通过 <b><font color="#c41230">TheadPoolExecutor</font></b> 的方式
在工作中如何使用线程池,是否自定义过线程池
<b><font color="#c41230">合理配置线程池你是如何考虑的?</font></b>
CPU 密集型
CPU密集的意思是该任务需要大量的运算,而没有阻塞,CPU一直全速运行,CPU密集任务只有在真正的多喝CPU上才能得到加速(通过多线程)
而在单核CPU上(悲催吧),无论你开几个模拟的多线程该任务都不可能得到加速,因为CPU总的运算能力也就那些
CPU密集型任务配置尽可能少的线程数量,一般公式为<b> <font color="#c41230">CPU核数+1个线程的线程池</font></b>
IO密集型
由于IO密集型任务线程并不是一直在执行任务,则应配置尽可能多的线程,如 <b><font color="#c41230">CPU核数*2</font></b>
IO密集型,即该任务有大量的IO,即大量的阻塞,在单线程上运行IO密集型的任务会导致浪费大量的CPU运算能力浪费在等待
所以在IO密集型任务中使用多线程可以大大的加速程序运行,即使在单核CPU上,这种加速主要就是利用了被浪费掉的阻塞时间
参考公式 : <b><font color="#c41230">CPU核数/1-阻塞系数 </font></b> (阻塞系数在 0.8-0.9之间)
比如8核CPU: 8/1-0.9=80个线程数