java 对象头markword,显式锁和AQS 笔记

线程同步的原语

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 比较:

  1. 可以尝试获取锁,如果获取失败, 可以做些其他事情…过后再来获取锁, 但是synchronized 关键字就会一直阻塞
  2. 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实现要点:

  1. 重入性处理:获取锁时比较当前锁独占线程是否是当前线程
  2. 如果是当前线程则累加state值, 因为有对应的加锁次数,解锁时也需要减掉相应次数

ReadWriteLock实现要点:

  1. state为int类型,则包含32位bit,取高16位为读锁状态,低16位为写锁状态
  2. 重入性处理:也要累加加锁次数,而且读锁为共享锁,如何保证这么多线程同时记录读锁重入加锁次数?采用ThreadLocal来记录每个线程自己的加锁次数
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值