JUC概述
什么是JUC
JUC 是 java.util.concurrent工具包简称,是一个处理线程的工具包。JDK1.5开始出现
进程线程
进程:指系统中正在运行的一个应用程序,进程是CPU资源分配的最小单位
线程:系统分配处理器时间资源的基本单元,进程内独立执行的一个单元执行流。线程是程序执行的最小单元。
线程的状态
- NEW 新建
- RUNNABLE 准备就绪
- BLOCKED 阻塞
- WAITING 不见不散
- TIMED_WAITING 过时不候
- TERMINATED 终结
wait和sleep区别
- sleep是Thread的静态方法, wait是Object的方法,任何实例对象都能调用。
- sleep不会释放锁,也不需要占用锁。 wait会释放锁,但调用它的前提是当前线程占有锁(代码在synchronized中)
- 都可以被interrupted方法中断。
并发与并行
并发:同一时刻,多个线程访问同一个资源,多线程对一个点 【电商秒杀】
并行:多项工作一起执行,之后汇总。
管程
管程是Monitor 监视器,即锁,是一种同步机制,保证同一时间只有一个线程访问被保护的数据或代码。
JVM同步基于进入和退出,是使用管程对象进行管理的。
用户线程与守护线程
用户线程: 平时自定义的线程, 主线程结束,用户线程运行,JVM仍存活
守护线程: 比如垃圾回收线程, 只有守护线程没有用户线程了, JVM结束
Lock接口
synchroized 实现卖票例子
Lock的可重入锁实现卖票例子
Lock和synchroized 的不同
线程间通信
多线程编程步骤:①创建资源类,②判断、干活、通知 ③创建多线程调用资源类方法 ④防止虚假唤醒问题
线程间通信:两个线程交替完成加一减一例子
定制化通信,三个线程AA打印5次、BB打印10次、CC打印15次,循环10轮 例子
集合的线程安全
ArrayList不安全解决方式: ①vector ②Collections.synchronizedList() ③CopyOnWriteArrayList
HashSet不安全解决方式:CopyOnWriteArraySet
HashMap不安全解决方式:ConcurrentHashMap
多线程锁
锁的范围
Java中每个对象都可以作为锁。具体表现为以下三种形式:
- 对于普通同步方法,锁是当前实例对象this
- 对于静态同步方法,锁是当前类字节码,Class对象
- 对于同步方法块,锁是synchronized括号里配置的对象
公平锁与非公平锁
非公平锁 | 可能导致其他线程饿死。(不问有没有人,直接抢),效率高 |
公平锁 | 阳光普照(问一下有人没,有人就排队),效率相对低 |
可重入锁
synchronized() 是隐式的可重入锁, Lock是显式的可重入锁
可重入锁特点:可以自由进入多层锁。打开外层锁后,还可以继续打开内层锁。
死锁
什么是死锁
两个或两个以上的进程, 因为争夺资源而造成互相等待的现象,如果没有外力干涉,他们无法再执行下去
产生死锁的原因
- 系统资源不足
- 进程运行推进顺序不合适
- 资源分配不当
手写死锁例子。
验证是否是死锁
- jps -l 得到正在运行的程序进程号
- jstack 进程号 可以验证是否是死锁发生
Callable接口
Runable接口和Callable接口区别
- 是否有返回值, Callable有返回值
- 是否抛出异常, 无法计算结果会有异常抛出
- 实现方法名称不同, Runable接口是run方法, Callable接口是call方法
想要通过Callable接口创建线程,必须得通过Runnable接口的实现类 FutureTask,FutureTask构造可以传递Callable,然后new Thread,放入FutureTask
FutureTask原理
举例:
1.老师上课口渴了没水喝,跑去买水不合适,讲课线程继续,单开启另外线程,让班长去买水,把水买回来放那,老师需要的时候直接get()
2.三个同学,三个计算任务,我是主线程需要统计他们计算的结果, 假设第二个同学计算量很大,我从第一个同学开始汇总,到了第二个同学,他还没计算好, 我单独给第二个同学开一个线程让他继续计算,我去汇总第三个同学。等我汇总完了第三个同学, 再回过头来等第二个同学计算完,并汇总。
JUC强大的辅助类
减少计数 CountDownLatch
CountDownLatch的构造方法,传入一个初始计数。 await方法是阻塞式的,计数为0才通过。 countDown() 不是阻塞的,会使计数减一
例子: 6个同学走完了,班长才锁门
循环栅栏CyclicBarrier
cyclicBarrier.await();没有达到设定的值的时候,不会执行await之后的语句,线程会阻塞,每次阻塞设定的值会+1, 到达后会执行cyclicBarrier对象的runnable接口,每个线程执行cyclicBarrier.await();后续的任务
例子: 集齐七颗龙珠即可召唤神龙
信号灯Semaphore
acquire() 方法,从此信号量中获取一个许可,在提供一个许可前线程将一直阻塞
release() 释放一个许可,将其返回给信号量
例子: 6辆车停进三个停车位
ReentrantReadWriteLock读写锁
悲观锁、乐观锁
悲观锁,不支持并发操作,每次操作都锁上。
乐观锁,支持并发操作,操作完改变版本号,如果其他线程操作提交时发现版本号变了就回滚。
表锁、行锁
表锁: 整张表都锁上
行锁: 只锁一行
行锁可能发生死锁
读写锁
读写锁都可能发生死锁
读写锁特点
并发读, 独占写, 读锁和写锁都可能发生死锁
一个资源可以被多个读线程访问,或者可以被一个写线程访问, 但是不能同时存在读写线程, 读写互斥、写写互斥、读读共享。
ps:读写锁本质上是一种自旋锁
举例: 读写锁缓存。
缺点:
- 造成锁饥饿,一直读,没法写操作, 比如坐地铁,100个人上车1个人下车,一直堵着下不了
- 读的时候,哪个线程都不可以写, 写的时候,自己线程可以读,其他线程不能读
写锁可以降级为读锁, 读锁不能升级为写锁
降级方法:1.获取写锁 2.获取读锁 3.释放写锁 4.释放读锁, 实时就完成了写锁降级为读锁的过程。
BlockingQueue阻塞队列
常用:
- ArrayBlockingQueue (常用) : 由数组结构组成的有界的阻塞队列
- LinkedBlockingQueue(常用) : 由链表组成的有界的阻塞队列,默认大小为最大整型值
常用方法:
第一组方法 抛出异常 add(e) remove() element()
第二组方法 返回特殊值 offer(e) poll() peek()
第三组方法 阻塞 put(e) take()
第四组 阻塞,超时放弃 offer(e, time, unit) poll(time, unit)
ThreadPool 线程池
概述及架构
线程池特点:
- 可以降低资源消耗,通过重复利用已创建的线程降低线程创建和销毁造成的消耗。
- 提高响应速度:任务到达时,可以不需要等待线程创建就能立即执行
- 提高线程的可管理性:线程是稀缺资源,无限制创建会消耗系统资源,降低系统稳定性。用线程池可以统一分配、调优和监控
架构–Executor框架实现
线程池使用方式
- 一池N线程: Executors.newFixedThreadPool(int)
- 一池一线程 : Executors.newSingleThreadExecutor()
- 可扩容线程池 :Executors.newCachedThreadPool()
底层都是用的ThreadPoolExecutor
ThreadPoolExecutor 七个参数含义
- int corePoolSize, 常驻线程数量(核心)
- int maximumPoolSize 最大线程数量
- long keepAliveTime,线程存活时间
- TimeUnit unit , 时间单位
- BlockingQueue workQueue ,阻塞队列
- ThreadFactory threadFactory, 线程工厂
- RejectedExecutionHandler handler,拒绝策略
线程底层工作流程
在执行execute()方法的时候才开始创建线程,根据需求先创建常驻线程,然后再来需求进入阻塞队列, 再来需求创建更多线程直接进行处理,再来需求执行拒绝策略
四种拒绝策略
- AbortPolicy (默认): 直接抛出异常,阻止系统正常运行
- CallerRunsPolicy: 不会抛弃任务,也不会抛出异常,而是将某些任务回退给调用者
- DiscardOldestPolicy: 抛弃队列中等待最久的任务,然后把当前任务加入队列中尝试再次提交当前任务
- DiscardPolicy:不做任何处理
Fork/Join 分支合并
Fork: 把复杂任务进行拆分
Join: 把分拆的任务结果进行合并
例子:1+2+…+100 拆分分别计算再合并
CompletableFuture异步回调
CompletableFuture.runAsync() 没返回值的异步回调
CompletableFuture.supplyAsync() 有返回值的异步回调