线程同步的原语
1.mutex:互斥量 pthread_mutex函数
2...
无锁不可偏向:如果计算了hashcode,则该对象不可偏向(hashcode把偏向线程id的存储位置占用了),
显式锁:手动声明的锁,一般是Lock/ReadWriteLock接口子类
隐式锁:synchronized 锁膨胀过程:
如果一个锁对象第一次被一个线程获取,是偏向锁(偏向锁未关闭的情况下),再次加锁也是偏向锁
两个线程交替执行获取该对象锁时,膨胀为轻量锁;
如果一个锁对象计算过hashcode,则第一次被获取就是轻量锁
如果多个线程竞争该对象,则膨胀为重量锁
Lock lock = new XXXLock();
lock.lock(); -->加锁,(成功就继续进行,不成功就阻塞等待解锁)
lock.unlock(); -->解锁
lock.tryLock(); -->尝试加锁 (如果不成功直接就return false;)
Lock 与 synchronized 比较:
- 可以尝试获取锁,如果获取失败, 可以做些其他事情…过后再来获取锁, 但是synchronized 关键字就会一直阻塞
- synchronized 关键字消耗会少一些,因为Lock是对象实例,所以一个只需要最基本锁定解锁的场景, 直接使用sync关键字就可以,sync底层使用的是mutex锁,属于重量级锁,没有自旋锁,其中自旋的部分是为了等待其他线程锁膨胀,并不是为了获取锁,jvm源码中有解释
常用的Lock锁 :ReentrantLock(可重入锁) ,ReadWriteLock(读写锁)
使用显式锁的范式
Lock lock = new ReentrantLock();
public void xxxmethod(){
lock.lock();
try{
//do xxxxxxx
}catch(Exception e){
//do xxxxxxxxx
}finally{ //必须在finally里解锁, 否则抛异常会造成死锁
lock.unlock();
}
}
ReentrantLock 可重入锁:什么是可重入?同一个线程来调用同一个lock获取锁,不会被阻塞, 场景(递归操作),不会被自己锁住
锁的公平性:多个线程来争夺同一个锁未争取到的情况下,这些线程都在等待, 当锁被释放后, 先等待的线程优先获得锁,后来的线程继续等待,(先来先拿)这个叫公平锁, 如果锁被释放后,等待的线程无顺序抢锁,这种锁就叫非公平锁,非公平锁效率更好一些,减少上下文切换
synchronized关键 字是非公平锁
ReentrantLock锁的公平性,是在构造函数中指定的,缺省是非公平锁
ReadWriteLock 读写锁接口: 允许多个线程读取,允许一个线程修改, 读多写少的场景,实现类是ReentrantReadWriteLock,包含两个锁
ReadWriteLock lock = new ReentrantReadWriteLock();
Lock readLock = lock.readLock();
Lock writeLock = lock.readLock();
--> 当有读线程获取了read锁时,其他读线程不受影响;但写线程获取wirte锁时,会被阻塞
--> 当有写线程获取了write锁时,读写线程都会被阻塞
-->写独占,读共享,读写互斥
Condition接口 :显式锁中的等待和唤醒机制,实例从lock中newCondition()方法获取
Lock lock = new ReentrantLock();
Condition cdt = lock.newCondition();
cdt.await(); //线程等待
cdt.signal(); //线程唤醒
**可以讲, Lock的lock/unlock 代表了synchronized块/方法, condition的await()/signal()/signalAll() -->代表获取了对象锁的Object的wait()/notify()/notifyAll()
=================================
AbstractQueuedSynchronizer 抽象类(AQS)–>CLH队列锁的一个实现
是并发编程里很基础的一个组件.效率高是因为未使用系统级线程,
先来了解LockSupport类(锁支持类),用来构建线程阻塞通知等等等组件的工具类
LockSupport.parkxxx (Object); -->阻塞当前线程
LockSupport.unpark(Thread); -->唤醒线程
CLH队列锁 : (三个人名首字母)大部分PC机实现锁都是用这种队列锁机制,基于链表的锁的实现,链表中的元素为 QNode(包含两个域:myPred(前驱节点的引用),locked(是否获取锁)) 具体Node的数据结构 -->查看AQS类源代码可以看到
当前一个node的locked域变成了false,表示锁释放了, 下一个node在不停自旋判断上一个node状态,当发现这个情况就会去拿锁
AQS的设计模式:模板方法模式,所以很多方法需要子类去实现
抛出UnSupportedOperationException()的方法需要重写,按需.
tryAcquire(); //独占式获取锁
tryAcquireShared(); //共享式获取锁
tryRelease(); //锁释放
tryReleaseShared(); //锁释放
isHeldExclusively(); //判断当前是否为锁占用状态
AQS–>NODE结构{
线程信息:thread,
线程类型: SHARED:共享线程
EXCLUSIVE:独占线程
线程状态waitStatus: SIGNAL:线程已准备好,解锁就可执行
CONDITION:条件等待中
PROPAGATE:传播(共享锁情况下)
CANCELLED:取消的
prev:前置节点
next:下一个节点
nextWaiter:等待condition条件的下一个节点
}
同步队列中node的变化
独占式锁获得/释放过程
独占锁是每次对一个线程解锁… 共享锁是每次对一批线程解锁
Condition 阻塞与唤醒
线程调用condition.await()后, 线程会包装成一个包含nextWaiter的Node加入condition下的节点链表(单向),调用condition.signal()时,会从链表头取一个node进行唤醒,调用condition.signalAll(),会从链表头依次唤醒后续的node线程
ReentrantLock实现要点:
- 重入性处理:获取锁时比较当前锁独占线程是否是当前线程
- 如果是当前线程则累加state值, 因为有对应的加锁次数,解锁时也需要减掉相应次数
ReadWriteLock实现要点:
- state为int类型,则包含32位bit,取高16位为读锁状态,低16位为写锁状态
- 重入性处理:也要累加加锁次数,而且读锁为共享锁,如何保证这么多线程同时记录读锁重入加锁次数?采用ThreadLocal来记录每个线程自己的加锁次数