Java JUC并发编程
JUC简介
概念区分
-
JUC是java.util.concurrent包的简称
- 更好的支持高并发任务
- 让开发者进行多线程编程时减少竞争条件和死锁的问题
-
进程
- 线程
-
串行
-
并行
- 一边烧水 一边切菜
-
并发
-
同一资源争夺
- 春运抢票
- 电商秒杀
-
-
管程 Monitor 监视器
- 同步机制 保证同一时间,只有一个线程访问被保护的数据或者代码
-
Thread.sleep 与 Object.wait 区别
-
用户线程
- 守护线程
Lock接口
加锁方式
-
synchronized
-
同步锁
- 自动加锁 释放锁
-
-
ReentryLock
-
可重入锁
- 手动lock() try{} finnally unlock()
-
创建线程的多种方法
-
继承 Thread
-
Runnable 接口
-
Callable 接口
- FutureTask
-
线程池
- Executors
- ThreadPoolExecutor
案例:售票员卖票
-
方式
- 30张票 3个售票员(线程)来卖票
-
分析
-
资源类 Ticket
-
tnum
- 总票数
-
两种方式对应的方法
-
synchronized 方式1
-
sell() 方法
- synchronized sell()
-
-
可重入锁方式2
-
sell() 方法
- 手动 lock try{} finally{unlock}
-
-
-
-
创建3个线程
- 每个线程循环卖票30次
-
多线程编程步骤(上)
- 1 创建资源类,创建属性和方法
- 2 创建多线程,调用资源的方法
线程间通信 定制通信
案例:两个线程,对一个初始值是0的变量,一个线程+1,一个线程减1
-
分析:
-
创建资源Share
-
num
- 变量
-
可重入锁方式
-
ReentrantLock
-
Condition
- con
-
-
incr()方法负责 +1
-
decr()方法负责 -1
-
-
创建两个线程
- 1个线程 负责不停调用incr()
- 1个线程 负责不停调用decr()
- 两个线程需要来回等,需要通信
-
两个线程需要依据num的当前状态,临时中断,等待对方线程执行
-
这个时候就需要 判断
-
条件满足
- 自己执行
- 等待wait
-
-
-
synchronized 方式
-
incr()方法中判断 num
-
如果不为 0
- this.wait();
-
如果为0
- 执行+1,再通知其他线程 this.notifyAll();
-
-
-
ReentrantLock 可重入锁方式
-
创建 lock.newCondition()
-
incr()方法中判断 num
-
如果不为 0
- condition.await();
-
如果为0
- 执行+1,再通知其他线程 condition.signalAll();
-
-
多线程编程步骤(中 下)
-
1 创建资源类,创建属性和方法
-
2 在资源类操作方法中
-
1 判断
-
wait 在哪里等待 在哪里唤醒
- 注意虚假唤醒
- 判断条件 if->while
-
-
2干活
- do something
-
3通知
- noticeAll()
-
-
3 创建多线程,调用资源的方法
案例:三个线程(AA,BB,CC) 按照如下顺序打印:
AA打印5次,BB打印10次,CC打印15次
总共10轮
-
分析:
-
创建资源 ShareRes
-
flag
- 依据flag的1,2,3 标识线程的先后顺序
-
lock
- ReentrantLock
-
Condition
- con1
- con2
- con3
-
print5()
-
print10()
-
print15()
-
-
创建3个线程
-
AA线程调用 print5()
-
判断flag是否为1
-
flag!=1
- con1.await()
-
flag=1
- 执行
- 修改flag=2
- 定制 通知BB con2.await()
-
-
-
BB线程调用 print10()
-
判断flag是否为2
-
flag!=2
- con2.await()
-
flag=2
- 执行
- 修改flag=3
- 定制 通知CC con3.await()
-
-
-
CC线程调用 print15()
-
判断flag是否为3
-
flag!=3
- con3.await()
-
flag=3
- 执行
- 修改flag=1
- 定制 通知AA con1.await()
-
-
-
3个线程中,需要定制通信,指定唤醒的线程
-
-
集合的线程安全
集合ArrayList
-
线程不安全演示
- 创建40个线程,分别向集合中add() 再遍历
- 报出异常 ConcurrentModificationException
-
解决方法
-
Vector
- synchronized
-
Collections.synchronizedCollection
-
CopyOnWriteArrayList
-
写时复制技术
-
分析
- 并发读
- 有更新时,复制一份新的副本,更新
- 其他线程从老的副本读
- 更新完成,所有线程从新的副本读
-
类比
- 签到名单
-
-
Happen-before原则
-
-
HashSet
-
解决方法
- Collections.synchronizedCollection
- CopyOnWriteArraySet
HashMap
-
解决方法
-
HashTable
- synchronized
-
ConcurrentHashMap
-
1.7
- Segment 是 ConcurrentHashMap 的一个内部类
- class Segment<K,V> extends ReentrantLock
- 核心数据如 value ,以及链表都是 volatile 修饰的,保证了获取时的可见性。
- 采用了分段锁技术
-
1.8
-
抛弃了原有的 Segment 分段锁,而采用了 CAS + synchronized 来保证并发安全性
-
CAS compare and swap 比较并交换
- 内存位置(V)、预期原值(A)和新值(B)
- 如果内存位置的值与预期原值相匹配,
那么处理器会自动将该位置值更新为新值 。
否则,处理器不做任何操作 - 我认为位置 V 应该包含值 A;
如果包含该值,则将 B 放到这个位置;
否则,不要更改该位置,只告诉我这个位置现在的值即可。
-
-
-
多线程锁
synchronized 锁的3中形式
-
普通同步方法
- 当前对象 this
-
static 同步方法
- 类的class对象
-
同步方法块
- 括号中的对象
公平锁 fair,非公平锁 nofair
-
演示
- new ReentrantLock(true);
- new ReentrantLock(false);
-
公平锁
- 阳光普照
- 效率稍微低
-
非公平锁
- 线程饿死
- 效率高
可重入锁 递归锁
- synchronized (隐式) Lock (显式) 都是可重入锁
死锁
-
线程A B 争夺资源时,相互等待的情况
-
原因
- 系统资源不足
- 推进顺序不合适
- 资源分配不当
-
查看程序等待是不是死锁
- jps
- jstack pid
Callable接口
Callable call()方法有返回值 ,可以引发异常
Runnable
- FutureTask构造可以传递Callable
步骤演示
- FutureTask(Callable)
- new Thread(futureTask,“A”).start();
- futureTask.isDone()
- futureTask.get()
FutureTask 未来任务原理
-
老师上课,口渴了,需要喝水。安排班长买水 等班长买回来,老师就能喝水了
-
考试时,先做会的题目,再做不会的题目
-
汇总一次
- 下次调用get ,不需要重新执行一遍
辅助工具类
减少计数器CountDownLatch
-
可以多个线程调用 await()来等待
- 计数器为0 ,这些线程都会被唤醒
-
案例:6个同学依次离开教室,最终班长锁门
-
分析
- 定义一个计数器,一开始6
- 每个同学离开后,计数器减1
- 班长一直在等,直到计数器变成0,锁门
-
实现步骤
-
new CountDownLatch(6)
-
创建6个线程,6个同学离开,每个线程里面
- countDownLatch.countDown();
-
main线程 班长等待
- countDownLatch.await();
-
计数器变成0,班长 离开教室
-
-
循环栅栏 CyclicBarrier
-
循环柱塞 构造一个目标障碍数
- 障碍慢慢被清除,目标可以到达
-
案例,集齐七龙珠,召唤神龙
-
步骤
-
new CyclicBarrier(NUMBER,Runnable)
-
创建7个线程分别收集龙珠,每个线程里面
- cyclicBarrier.await();
-
等7个线程都执行完,Runnable 里面召唤神龙
-
-
信号量 Semaphore
-
维护了一个许可集,可以限制访问某些资源的线程数量
-
案例:停车场 3个车位,6辆车准备停车
-
步骤
-
new Semaphore(3) permits
-
创建6个线程,模拟停车,每个线程
-
申请许可 抢占
- semaphore.acquire();
-
设置停放时间
-
时间到后,释放许可
- semaphore.release();
-
-
这样6两车都能找到车位
-
-
ReadWriteLock读写锁
乐观锁 悲观锁
读锁
- 共享锁
写锁
- 独占锁
读写锁的演变
-
第一阶段 无锁
- 容易出现 并发修改异常
-
第二阶段 独占锁
- 读读 读写 写写 都不能共享
-
第三个阶段 读锁 写锁
-
优点
- 读读 可以共享
-
缺点
-
造成锁饥饿,一直读,没有写操作
- 读的时候 不能写,等结束 才能写
-
-
只有读完成之后,才可以写,写操作可以读
-
锁的降级
-
获取写锁
-
获取读锁
-
释放写锁
- 释放读锁
-
-
演示
-
定义资源 MyCache
-
属性
- volatile map
- ReentrantReadWriteLock
-
方法
-
put(k,v)
- 获得写锁 rwLock.writeLock().lock();
- 模拟业务处理,等待300ms
- map.put(k,v)
-
get(k)
- 获得读锁 rwLock.readLock().lock();
- 模拟业务处理,等待300ms
- return map.get(k)
-
-
-
开启多线程
- 创建5个线程 通过put 添加数据
- 等待300ms
- 创建5个线程,通过get读取数据
-
结果
- 5个线程依次写
- 写完之后,5个线程同时读
BlockQueue阻塞队列
理解
-
首先是一个队列,通过一个共享的队列,使得数据从队列的一段流入,从另外一端流出
-
Thread1 .put
-
BlockingQueue
- Thread2.take
-
好处
-
我们不要关系什么时候需要阻塞线程,什么时候需要唤醒
- 添加时,如果队列满了,会自动阻塞
- 取出时,如果队列为空,自动阻塞
应用
-
有若干生产者,有若干消费者,通过队列的方式来传递,就很方便
- 之前需要考虑生产速度是不是能够满足消费的速度
演示
-
new ArrayBlockingQueue(3)
- capacity 队列容量为3
-
System.out.println(queue.offer(“a”));
- 依次执行4次put方法
- 第四个put会阻塞,因为阻塞队列的容量是3
LinkedBlockingQueue 高效并发处理数据
-
生产端 和消费端采用的独立的锁
- 后面的线程池就是选用的这个阻塞队列
ThreadPool线程池
框架
-
Executors
-
ThreadPoolExecutor
-
ExecutorService
- Executor
-
-
ThreadFactory
演示
-
Executors.newSingleThreadExecutor()
-
一池一线程
- 允许队列的最大长度是Integer.MAX_VALUE,容易OOM
-
-
Executors.newFixedThreadPool(fixNum)
-
一池N线程
- 允许队列的最大长度是Integer.MAX_VALUE,容易OOM
-
-
Executors.newCachedThreadPool()
-
可扩容
- 允许创建的线程数量是Integer.MAX_VALUE,容易OOM
-
自定义线程池
-
new ThreadPoolExecutor(7个参数)
-
corePoolSize
- 常驻线程数量
-
maximumPoolSize
- 最大数量
-
keepAliveTime
- 存活时间
-
unit
- 时间单位
-
workQueue
-
BlockingQueue
- 阻塞队列
-
-
Executors.defaultThreadFactory()
-
ThreadFactory
- 线程工厂
-
-
defaultHandler
-
RejectedExecutionHandler
-
拒绝策略
-
AbortPolicy
-
Discard
- 默默丢弃,无异常,无返回
- 如果允许丢失
-
CallerRuns
- 调用者运行 回退到调用者
- 降低新任务的流量
-
DiscardOldest
- 抛弃队列中等待最久的,
也就是最前面的
- 抛弃队列中等待最久的,
-
-
-
-
举例参照
- 银行业务窗口,一开始3个窗口,最多可以同时开始10个窗口
ForkJoinPool分支合并
理解
-
需要递归拆分的任务
- 继承后,可以实现递归自己调用自己的任务
-
fork
- 将大任务递归拆分成小任务
-
join
- 把分拆的业务结果合并
-
ForkJoinPool
-
分支合并池
- 提供线程满足计算任务
-
-
RecursiveTask
- 递归任务
演示
-
创建递归任务
MyTask extends RecursiveTask-
常用属性
- VALUE=10;
- begin
- end
- result
-
public MyTask(int begin,int end){}
- 构造函数
-
重写 Integer compute() @Override
-
拆分成2个小任务
-
new MyTask(begin, mid)
- task01
-
new MyTask(mid+1, end)
- task02
-
-
子任务迭代执行
- task01.fork();
- task02.fork();
-
合并子任务执行结果
- return task01.join()+task02.join();
-
-
-
主线程
-
poo=new ForkJoinPool()
- 创建分支合并池
-
myTask=new MyTask(1, 100)
- 创建递归任务
-
forkJoinTask pool.submit(task)
- 返回ForkJoinTask
-
forkJoinTask.get()
- 阻塞等待结果
-
pool.shutdown()
- 关闭
-
CompletableFuture异步回调
Future
-
同步回调
- isDone 判断是否完成
- get() 阻塞到任务结束
CompletableFuture
-
异步回调
-
演示
-
没有返回值
-
CompletableFuture cf1 =
CompletableFuture.runAsync()- Runnable 只有run 没有返回值
-
cf1.get();
-
-
有返回值
-
CompletableFuture cf2 = CompletableFuture.supplyAsync()
- Supplier T get() 有返回值
-
cf2.whenComplete((t,u)->{}).get()
- t 返回值
- u 发生的异常
-
-
XMind - Trial Version