一、Lock体系
(一)可重入锁---ReetrantLock
1、可重入锁:线程可以进入任何一个它拥有的锁 所同步着的代码块
(ReetrantLock、synchronized)
不可重入锁:不可重复获得锁 (lock)
2、ReetrantLock是排他锁
同一时刻只允许一个线程访问
3、可重入性实现
(1)volatile int state----标记锁的个数(线程可见)
(2)AQS---CHL队列,线程队列同步器(双向链表等待队列) ----线程排队(下面详述)
(3)CAS---比较并交换----用来更新同步状态
(4)LockSupport---用来对阻塞线程的挂起和唤醒
UNSQFE.park()----阻塞
UNSQFE.unpark() ---结束阻塞
(二) 读写锁---ReetrantRadWriteLocks
1、排他锁和共享锁
排他锁:同一时刻只允许一个线程访问
共享锁:同一时刻允许多个线程访问
2、乐观锁和悲观锁
乐观锁:允许并发,不排队, 需要序列号(序列号)控制共享变量
悲观锁:不允许并发,排队执行
3、读写锁
读锁: 同一时刻允许多个读线程访问---可重入共享锁
写锁: 在写访问时,所有读写线程均阻塞----可重入排他锁
private ReentrantReadWriteLock RWLock=new ReentrantReadWriteLock();
RWLock.readLock().lock;---读锁,读共享---读时所有线程可以进行读操作
RWLock.writeLock().lock;---写锁,写独占---写时所有线程阻塞
4、读写锁优点
读写分离在保证线程安全前提下,使并发性相比排他锁有很大提升
5、读写锁依赖AQS实现同步功能,读写状态就是其同步器的同步状态
6、锁降级
(1) 当某线程已经拿到写锁之后,可在释放写锁之前 再获取读锁,获取读锁后释放写锁
(2) 锁降级:写锁--->读锁 可读可写--->只可读
(3)释放写锁之前获取读锁是必要的,因为避免此时其他线程获取写锁修改了数据,本线程不可见,保证了数据的可见性
拥有写锁:自己可写可读,但其他线程不可读也不可写
拥有读锁:自己和其他线程都可读,但不可写
(三)同步队列 AQS---AbstractQueuedSynchronizer
1、共享资源---volatile int state
state =0--没有被线程占用---可成功获取---获取成功后 state+1
state=1--有被线程占用--获取失败--线程被封装成node节点,放到CLH同步队列中自旋等待
2、AQS借助的两个类:CAS+LockSupport
CAS--比较并交换----AQS使用CAS对该同步状态进行原子操作实现对其值的修改
LockSupport---对阻塞线程的挂起和唤醒()
3、核心方法和类
- 核心方法:
获取锁--acquire----->tryAcquire----成功 setState +1
----失败----addWaiter---加入同步队列
----acquireQueued 不断获取资源
释放锁 ---release---->unparkSuccessor 唤醒头节点的后继节点
- 内部类
Node类---封装等待线程
CLH队列----由Node组成-----Head节点
等待节点
【注】公平锁:谁的钱去节点是Head节点,谁下一个被唤醒获取同步器--FIFO
Condition类---new Condition对象,
一个对象可设置一个条件,可通过创建多个对象的方式为线程设置多个条件
一个对象对应一对await和signal方法---设置对应的唤醒和中断条件
4、AQS模式
【独占式】----加锁 ReetrantLock.lock()---->aquire--->tryAquire--->state
(1)state=0---->true 可同步---->比较并更新state状态:CAS---->state+1
--->设置对象头中拥有此对象的线程:ower=线程名
(2) state=1---->false 不可同步---->加入阻塞队列:addWaiter
---->在队列中不断尝试获取资源 acquireQueued
----->获取失败设为阻塞状态 LockSupport.park()
----释放锁 ReetrantLock.unlock()--->release--->tryRelease---unparkSuccessor
唤醒后继节点
【共享式】----加锁 ---->aquireShared--->tryAquireShared 获取同步状态
state<0 获取共享锁失败
state=0 成功,但后面线程可能获取失败
state>0 成功,后面线程也可能获取成功
-----释放锁---->releaseShared--->tryReleaseShared----> CAS后释放锁
------>依次从尾节点遍历唤醒等待节点
二、synchronized
1、执行原理
- 每个Java对象都有一把锁,占有该对象锁,才能对该对象进行操作。
- 当一个线程遇到synchronized时,则需要占有该对象锁,而其他线程只能在同步代码块外面等待这个线程把同步代码块中的代码块执行结束,归还对象锁,这时才能占有共享对象锁,并进入同步代码块内进行执行。
- 每个对象的monitor记录对象的状态,加锁就成为monitor的唯一持有者.owner=线程名
2、可重入性
(1) 每一个对象在同一时间只与一个锁(monitor)相关联,
一个monitor在同一时间只能被一个线程获得
(2)synchronized有两个字节码:monitorenter----获取对象的monitor---加锁---锁计数器+1
monitorexit------获取对象的monitor---解锁---锁计数器-1
(3)原理
- 一个线程想要操作某个对象,先要获得该对象的锁,即找到对象的monitor
当monitor计数器为0时,才可获得该对象的锁,锁计数器+1,
其他线程再想获取就需等待
- 若已获得该锁的线程,在其他同步块中再次重入该锁,则锁计数器+1,随着重入的次数,计数器会一直累加
- 当释放锁时,每释放一次锁,锁计数器-1,当锁计数器重新为0时,该锁才能被其他线程获取
3、synchronized加锁的三种情况
- 情况一:加在需要同步的代码块之外,方法体内
synchronized(要锁的共享对象){
对象相关的同步代码块
}
【注】方法内要锁的共享对象一般为this,也可为其他全局变量。
若当前对象是方法的局部变量不可用this。(非共享,线程私有)
- 情况二:加在方法上
public synchronized void 方法名(){ 相关同步操作 }
优点:代码写的少了,节简
缺点:锁的只能是this当前类对象,不能是其他全局变量,不灵活
- 情况三:加在run方法中
run(){
synchronized(要锁的共享对象){
对象相关的方法
}
}
【注】此处不可以为this,因为此处this表当前对象--线程对象,一个线程对应一个自己对象,非共享对象
缺点: 扩大了同步范围,同步了整个方法,执行效率降低,同步代码越少,效率越高
4、锁的对象
(1)对象锁
同步代码块锁(自己指定锁对象)【情况一】
方法锁(默认锁对象为this,当前实例对象)【情况二】
(2)类锁
synchronize修饰静态的方法【 public static synchronized void 方法名(){ 相关同步操作 }】
指定锁对象为Class对象 【synchronized(Student.class)】
5、注意
- 一把锁只能同时被一个线程获取,没有获得锁的线程只能等待;
- 每个实例都对应有自己的一把锁(this),不同实例之间互不影响;例外:锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁
- synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁
参考:关键字: synchronized详解 | Java 全栈知识体系
三、Lock和synchronized区别
- 等待可中断:在持有锁的线程长时间不释放锁的时候,等待的线程可以选择放弃等待.
- 公平锁:按照申请锁的顺序来一次获得锁称为公平锁------按申请锁的顺序
ReentrantLock可以通过构造函数实现公平锁. new RenentrantLock(boolean fair)
synchronized的是非公平锁-----锁释放后,任何线程都有机会获得锁
- 锁绑定多个条件:通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能.通过Condition的await(),signal();
synchronized----只能用wait和notify实现一个隐含条件
【注】进程同步机制四大准则
1、空闲让进
当无进程进入临界区时,相应的临界资源处于空闲状态,因而允许一个请求进入临界区的进程立即进入自己的临界区。所以基本上不存在等待进程为n的情况。
2、忙则等待
当已有进程进入自己的临界区时,即相应的临界资源正被访问,因而其它试图进入临界区的进程必须等待,以保证进程互斥地访问临界资源。
3、有限等待
对要求访问临界资源的进程,应保证进程能在有限时间进入临界区,以免陷入“饥饿”状态。
4、让权等待
当进程不能进入自己的临界区时,应立即释放资源,以免进程陷入忙等
参考
(1条消息) Java多线程2---线程同步和异步、线程安全、锁机制_@snow'的博客-CSDN博客