JUC:
1、java.util.concurrent 解决并发编程问题,多线程。提升java的整个并发编程方案
java.util.concurrent: 核心功能 (线程池、阻塞队列、线程工厂、异步编排….)
java.util.concurrent.atomic 原子类 (专门为一些常见的基本类型变量设计了原子类操作)
java.util.concurrent.locks 锁(各种锁, 粒度更细的锁)
以前:synchronized【锁升级…………】
缺点:不太灵活,效率低下
synchronized(){
if(){
return
}
}
synchronized func(){} //串行化
//运行完成以后锁由jvm进行释放
难点:
1)、线程
2)、资源,做业务()
3)、锁
设计模式:单一职责原则;领域对象(DDD【Domain Driven Design】== dao、vo、to、dto、po、do。。。。)
2、进程和线程
进程:比较大, 一个进程就是一个启动的完整程序 ps -ef 进程 【进程是隔离的】
微服务:微服务是一种软件架构,每个微服务运行在自己的进程内
线程:一个进程在处理程序的时候,为了快(为了同时做更多的事情),开了多个线程。
多个线程可以并发/行处理业务。
线程间通信: wait(),notify(),notifyAll();
线程间数据共享: 任意技术(Map)、
同一线程整个链路共享数据:ThreadLocal(同一个线程的整个链路) …..
3、并发和并行
并发:淘宝双11 高并发
并发底层还是,串行处理,但是由于CPU时间片转的快,让我们觉得在同时处理
恢复现场:多线程并不是线程越多越好。适量才是最好
并行:互不影响;
4、wait/sleep
wait: 线程等待 wait()/notify(); 【释放锁】
sleep: 线程睡眠; 【不释放锁】。
共同效果:调用以后,线程就看着好像不往下执行了。
5、创建线程回顾
如何启动一个线程?
1、继承Thread
2、实现Runnable
3、实现Callable
4、线程池创建
6、Lambda表达式
7、synchronized
卖票:防止超卖
线程 操作(卖票)Dao/Service 资源类(票)bean
【线程 操作 资源类】
空调:
分: 资源类(空调) + 遥控器
不分: 类(属性+成员方法)
8、锁:
多线程共用一把锁就能解决竞争
1)、public synchronized void sale()
2)、synchronized (this){}
总结:
1)、synchronized 在 成员方法上的时候。锁住了方法。
谁是锁?锁就是调用这个方法的对象。 也就是this
2)、synchronized 在 静态方法上的时候。锁住了方法。
谁是锁?锁就是这个静态方法所在的类,类在方法区只有一份;
3)、synchronized 代码块 随便来
synchronized (this){}
synchronized 是 可重入设计
9、如何写多线程?
线程 操作 资源类
锁的设计。轻量
总结:资源竞争的时候,同一把锁就锁住了,否则多线程就不安全;
【可重入锁】: this
Class SixSixSix{
synchronized a(){
Thread.currentThread.getId();
b(){
Thread.currentThread.getId();
}
}
synchronized b(){
}
}
所有的锁都应该设计为可重入的,否则就有可能死锁
10、java.util.concurrent.locks 专门准备了很多锁。
Condition: wait()/notify();
Lock: 锁
ReentrantLock: 可重入锁
ReadWriteLock: 读写锁
ReentrantReadWriteLock: 可重入读写锁
11、ReentrantLock 可重入锁
1)、在任意需要加锁的位置 lock.lock();
2)、事情做完以后 lock.unlock();
3)、可以随便重复加,自动判断之前加过,就累计数量
4)、加锁与解锁一定成对出现。
5)、其他用法
1)、不同的加锁姿势
boolean lock = lock.lock(); //加锁 一直阻塞,到加锁成功
boolean lock = lock.tryLock(); //尝试加锁 不阻塞,立即得到锁的结果
boolean lock = lock.tryLock(3, TimeUnit.SECONDS); //带时间的尝试加锁 有限阻塞,最多等3秒。 得到锁的结果
2)、加锁就得解
6)、模式代码
第一种模式
xxx(){
lock.lock();
try{
//业务代码
}finally{
lock.unlock();
}
}
第二种模式
xxx(){
boolean b = lock.tryLock(..);
if(b){
try{
//业务逻辑
}finally{
lock.unlock();
}
}else{
//没得到锁
}
}
12、公平锁?
非公平锁: 以前多个线程抢锁,谁能抢到无法预测
公平锁: 多个线程如果在抢锁,锁释放以后,等锁等的最久的人应该最优先抢到,可以一定程度有序 Lock(true); 底层使用队列维护即可
13、ReentrantLock 与 synchronized
相同点:
都是可重入的,独占锁(排他锁、非共享锁)
不同点:
1)、ReentrantLock解锁需要手动【加锁解锁一定成对出现】,synchronized 自动解锁
2)、ReentrantLock 更加灵活。synchronized 的灵活性需要设计代码块,代码块多了不灵活
3)、ReentrantLock 功能更强。ReentrantLock可以限时等待式阻塞, synchronized 一直等待阻塞
4)、ReentrantLock 可以实现公平锁。synchronized 加锁 看心情
14、ReadWriteLock rwLock = new ReentrantReadWriteLock(); //藏了一对读写锁
rwLock.readLock(): 读锁
rwLock.writeLock():写锁
1、写加写锁,读加读锁
2、只要有写都互斥
写写互斥
写读互斥
读写互斥
3、只读
读读共享
读锁:共享锁 写锁:排他锁
共享锁就相当于无锁
new ReentrantReadWriteLock(true); 公平读写锁, 按照等待时长公平竞争
rwLock.readLock()/writeLock()
lock();//加锁
unlock();//解锁
tryLock();//有限等待加锁
15、线程间通信
线程间:多个不同线程
通信: 一个线程通知其他线程
wait() 等待 /notify() 通知;
有锁(synchronized)的环境下才说wait()/notify();
无锁环境wait()/notify(); IllegalMonitorStateException
加锁环境下:
wait() 等待: 等待会释放锁
notify() 通知:
sleep() 睡眠: 加锁的环境下,睡眠不放锁。
sleep和wait相同点
1、效果都是阻塞。我下面的代码不执行
sleep 和wait不同点
1、sleep 不放锁,睡醒接着干
2、wait 放锁。
3、notify() 通知、唤醒。 notify();//唤醒一个 notifyAll();//唤醒多个
被唤醒以后,代码必须从 wait()以后执行,锁还得抢;
notify();/ notifyAll(); 最终还是只有一个人能抢到
wait()必有notify()/All()
多线程:
1、线程 操作 资源类
2、判断(等待判断) 干活(执行业务) 去通知(通知别人)
虚假唤醒
原因:只要这个线程被唤醒,从wait后开始执行。导致其实唤醒了以后,可执行条件并没有达到。
解决:把wait条件的if换成while
synchronized == wait == notify/notifyAll
在同一个锁对象上候着的所有线程。
其实 wait/notify 是 锁对象的wait/notify
synchronized == wait == notify/notifyAll
lock == Condition.await === Condition.signal
物理Condition, xxCondition.await(); 会被 xxCondition.signal(); 拉起。用于精确唤醒
拉起以后。await()下面开始执行
lock==Condition
//作业 2个歌手。大合唱。每人一句
男:夜渐微凉
女:繁花落地成霜
女:你在远方眺望
女:耗尽所有暮光
男:不思量 自难相忘
女:夭夭桃花凉
女:前世你怎舍下
男:这一海心茫茫
合:还故作不痛不痒不牵强
合:都是假象
合:凉凉夜色为你思念成河
合:化作春泥呵护着我
男:浅浅岁月拂满爱人袖
女:片片芳菲入水流
合:凉凉天意潋滟一身花色
男:落入凡尘伤情着我
女:生劫易渡情劫难了
女:折旧的心 还有几分前生的恨
合:还有几分 前生的恨
//线程操作资源类. 像极:Tomcat 一个请求一来 new Thread(Controller/Service).start()
//判断(等待判断) 干活(执行业务) 去通知(通知别人)
wait == notify/notifyAll / Condition.await === Condition.signal
等: 释放锁,让出CPU
notify: 唤醒别人
唤醒以后: CPU、锁
虚假唤醒 + 植物等待
16、多线程集合类
Collection(单值)
List
Set
Queue
Map(键值对)
1)、普通的集合类,Map都会有线程安全问题
ConcurrentModificationException:并发修改异常
ArrayList是如何给里面添加元素
安全的:
List: Vector 和 SynchronizedList 和 CopyOnWriteArrayList【浪费空间】
Set: synchronizedSet、没有轻量的; 只需要给hashCode计算的时候加锁;
Map: synchronizedMap、ConcurrentHashMap
synchronized集合类比较重量级;
ConcurrentHashMap、CopyOnWriteArrayList
SpringBoot应用
@RestController
Controller{
}
@Prototype()//多实例,就可以不用关系多线程资源竞争问题
@Service
xxxService{
private ConcurrentHashMap map = new ConcurrentHashMap();
void handle(){
//map.put
dao.aaa(){
int i = 1;
}
}
}
1)、Tomcat 有一个,启动后SpringBoot应用有一个,Spring容器有一个,默认每个组件都是单实例
2)、请求一来 Tomcat 分配一个线程处理请求,请求很多,就是多线程调用,单实例对象资源会竞争;
3)、 @Prototype(type=Singleton/Prototype); 不要靠他,浪费内存
17、常用的几个
CountDownLatch: 闭锁。 一直减到0,放行一起等待的 就结束
CyclicBarrier: 循环栅栏。 一直加到N,放行以前等待的 可重置
Semaphore: 信号量。 可加可减, acquire获取信号没有就等待, 可重置
限流:
boolean b = semaphore.tryAcquire();//尝试获取一个
if(b){
//调用业务
}else {
return “服务器正忙,请稍后…”;
}
18、Callable
class MathCalculator implements Callable
//
@Override
public Integer call() throws Exception {
int i = 0;
i++; //1
++i; //2
System.out.println(Thread.currentThread().getName()+”==>call执行了”);
return i++;
}
}
FutureTask task = new FutureTask(Callable的对象);
new Thread(task).start();
=============Callable的使用==================,
FutureTask
new Thread(futureTask).start();
Integer i1 = futureTask.get();//阻塞式获取值
Integer i2 = futureTask.get(1, TimeUnit.SECONDS); //限时等待
boolean done = futureTask.isDone(); //查看Task是否完成
if(done){
}
boolean cancelled = futureTask.isCancelled(); //线程是否已经被取消了,中断了
futureTask.cancel(true);// 强制取消任务
19、BlockingQueue
多线程用的多; BlockingQueue 整个系列是有锁的
队列的普通操作:(操作不了抛异常)
add(e): 添加元素 从队尾添加一个数据 异常:(加满了)
remove(): 移除元素 从队头取出并移除这数据 返回移除的元素 异常:(队列是空的)
element(): 读取队头元素值 从队头获取一个数据 返回队头元素的值 异常:(队列是空的)
队列的特殊返回值
offer(e): 添加元素 会返回成功与失败 true/false
poll(): 移除元素 会返回被移除的元素 队列是空的就null
peek(): 读取队头元素值 会读取队头的元素值 队列是空的就null
队列的阻塞功能
put(e): 添加元素 队列满了,就会一直等待
take(): 移除元素 会返回被移除的元素 队列是空的一直等待
队列的限时阻塞功能
offer(e,long,TimeUnit): 添加元素 队列满了,会等待指定的时间
poll(time, unit) 移除元素 队列空了,会等待指定的时间
1)、数组、链表
数组:移动式添加内容很慢,找东西都慢;
连续空间
链表:移动式添加内容很块,找东西都慢;
碎片化空间
小数据量用数组
中大数据量用链接(利用碎片化空间)
2)、PriorityBlockingQueue 优先级队列
PriorityBlockingQueue
queue.add(8);
queue.add(1);
queue.add(4);
queue.add(7);
queue.add(16);
for (int i = 0; i < 5; i++) {
Integer remove = queue.remove();
System.out.println(remove);
}
20、ThreadPool 线程池
new Thread().start(); //资源耗尽
无休止的new Thread(),很容易资源耗尽。线程池为了控制资源的
拒绝策略:
ThreadPoolExecutor.AbortPolicy(): 线程池都满了,还给线程池提交任务,就会抛RejectedExecutionException
ThreadPoolExecutor.CallerRunsPolicy(): 不拒绝提交的任务。让任务直接运行
ThreadPoolExecutor.DiscardPolicy(): 忽略,装聋子,啥都不干。任务丢了,但啥都不说
ThreadPoolExecutor.DiscardOldestPolicy() 移除队列最老的任务,把新提交的任务放进行
21、volatile
保证可见性、顺序性
不保证原子性
1)、轻量级的同步机制
22、Atomic:原子类
所有操作都是利用 UnSafe;直接操作内存;
//JVM自己会管理内存。但是这种直接操作内存的,JVM就管理不到了。需要自己开辟和释放
public native void putAddress(long address, long value); //内存地址、存的数据
public native long allocateMemory(long var1); //字节为单位, allocateMemory(1024) //申请1KB内存。返回内存的起始地址
public native long reallocateMemory(long var1, long var3);
public native void freeMemory(long var1);//释放内存,传入地址, 1字节
//内存屏障
public native void loadFence();
public native void storeFence();
public native void fullFence();
//CompareAndSwap:比较交换技术
public final native boolean compareAndSwapObject(Object var1, long var2, Object var4, Object var5);
public final native boolean compareAndSwapInt(Object var1, long var2, int var4, int var5);
public final native boolean compareAndSwapLong(Object var1, long var2, long var4, long var6);
//无锁自旋+比较交换
//操作系统
public native void unpark(Object var1); //唤醒
public native void park(boolean var1, long var2); //阻塞等待
23、CAS:CompareAndSwap 比较并交换
Unsafe 类提供的硬件层的原子操作
CAS:为锁设计的时候,提供竞争修改状态位的
AtomicInteger: 0 底层全靠 Unsafe,Unsafe 全靠 C,C全靠汇编,汇编全靠01, 01全靠高低电信号,电信号全靠电
incrementAndGet(){
int i = unsafe.getAndAddInt(this, valueOffset, 1); //先Get再Add 1;内存告诉我们当时给这个i加的1
return i + 1;
}
24、AQS:(AbstractQueuedSynchronizer)抽象队列同步器
锁的底层:是通过CAS竞争state + 队列
队列(数据库): 所有要竞争state的人,如果有人抢到锁,其他人(线程)得等(在队列里面等),当锁被释放以后,从队列里面唤醒一个线程继续执行
ReentrantLock implements Lock{
Sync sync;//同步器,是 AbstractQueuedSynchronizer 的实现
同步器有两种实现
NonfairSync:非公平
FairSync: 公平
}
AbstractQueuedSynchronizer: 提供了所有的基本操作。使用模板设计模式
AQS:
①提供了一个队列(CLH 锁队列);维护抢锁的所有线程;而且维护了所有的公共逻辑。
CLH 第一次排队的时候,才初始化这个队列的头尾,为了节省空间;
头尾节点是两个new Node() 【两个虚拟节点】
②CLH 锁队列里面每一个元素是一个Node(节点)【定义好的数据结构】
【线程、prev、next、waitStatus】整个Node是一个双向列表
③提供state;锁的标志位 0:代表没人抢; 大于0的其他值:代表已经有人占用了
④提供了 protected 的几个方法,如果直接调用会 throw new UnsupportedOperationException();
tryAcquire(): 尝试获取量
tryRelease(): 尝试是否量
tryAcquireShared(): 尝试获取共享量
tryReleaseShared(): 尝试释放共享量
isHeldExclusively(): 判断当前是否排他锁
⑤我们可以自定义一个类,继承AbstractQueuedSynchronizer并重写 提供的几个 protected 方法。就能实现自定义占锁/释放锁
⑥研究Sync、FairSync、NonfairSync
25、Lock里面有什么?
1、CLH队列
每一个Node
2、state
1)、AQS底层靠 CLH队列
2)、CLH队列 维护Node数据结构(双链表)
3)、所有线程竞争 state 标志位【利用CAS(原子性)】
4)、可重入锁,判断如果当前线程就是之前占了锁的线程,那就给 state + 1
注意:lock了几遍就unlock几遍。state变不成0就死锁了。
一次解锁就ok;
5)、公平锁/非公平锁
非公平锁上来就CAS,先试一下能不能直接改state;改不了就排队
公平锁上来就挨个排队
26、synchronized 锁升级的过程;
JVM在底层进行了优化;
锁(0/1/2)
1、如果给一个代码块/方法加上了 synchronized。
2、【偏向锁】 : 代码在运行的时候,锁是 偏向锁。JVM发现只有一个线程在运行这个代码块,这个线程继续执行,锁就默认就是他的
3、【轻量级锁】: 第二个线程进来也运行 synchronized,牵扯到抢锁,第二个抢锁的线程看到标志位是1,以自旋while(true){}的方式获取锁
4、【重量级锁】:如果有更多的线程,自旋了很多次(10)都获取不到锁,jvm让线程wait;
线程切换最浪费时间和空间的。
偏向锁 —-> 轻量级锁 —-> 重量级锁;