Synchronized&Lock&AQS
😊1.java锁
😊2.Synchronized锁的使用与原理
加锁方式:(1)、同步方法锁,进入方法前获取当前类的实例锁
(2)、 同步静态方法锁,进入方法前获取当前类对象锁
(3)、同步代码块,给指定对象加锁,进入代码块前获取指定对象锁
💖 2.1底层原理:JVM内置锁通过Synchronized使用,通过内部对象Monuitor(监视器锁)实现,基于进入与退出Monitor对象实现方法与代码快同步,监视器锁的实现依赖底层操作系统Mutex lock--(互斥锁:互斥锁是互斥标志。它充当一段代码的网守,允许一个线程进入并阻止对所有其他线程的访问。这样可以确保被控制的代码一次只能被单个线程访问。只要确保完成后释放互斥量即可) --实现,它是一个重量级锁性能较低。
💖2.2Monuitor详解
如果T1 T2 Tn都到了Monuitor临界点他们共同竞争Monuitor对应的管成对象,此时T1拿到了管成对象,则其他线程会被放到waitset这个集合队列中,拿到监视器锁的线程此时此刻就会进入方法或者同步代码块执行业务代码,T1走完之后T1会释放Monuitor对象,之后会去唤醒正在等待的线程,这些线程再去抢夺锁。
😊3.对象内存结构
对象头:比如hash码,对象所属的年代,对象锁,锁状态标志,偏向锁(线程) ID ,偏向时间,数组长度(数组对象) 等,
对象实际数据:即创建对象时,对象中成员方法变量,方法时。
对齐填充:对象的大小必须时8字节的整数倍
💖 3.1实例对象在内存中存储在哪里呢?
答: (1)实例对象存储再堆区时:实例对象内存存在堆区,实例引用存在栈上,实例的元数据class存在方法区或者元空间(方法区中存放的是类型信息、常量、静态变量、即时编译器编译后的代码缓存、域信息、方法信息等。随着JDK的发展,方法区中存放的内容也在发生变化。并不绝对。通常情况下放的是这些内容)。
(2)如果对象没有发生对象逃逸(如果一个对象被发现只能从一个线程访问到,那么对于这个对象的操作可以不考虑同步)的话,那么所有对象都将被存储在堆中,如果发生逃逸对象则有可能逃逸到栈内存。
💖 3.2锁的粗化:一个append方法就要加一个锁来保证线程安全,因为要做四次添加操作,jvm会优化加一个总的锁。
💖 3.3锁的消除:以下同步块加的锁毫无意义,不同线程访问到不同的对象锁是没有意义的,jvm会进行逃逸分析不会加锁。
💖 3.4轻量级锁的应用场景:
线程见交替执行场景,轻量级锁是在并发量少,产生线程竞争较少的场景,如果出现了抢夺锁的情况,如下图T1线程在执行任务,T2在想要获取同一把锁,此时会自旋等待机会再次获取锁,他不会将线程阻塞。在jdk1.7自旋的次数会根据上一次的自旋次数进行自适应自旋的次数。
😊4.锁的升级过程:
💖4.1 无锁状态升级到轻量级锁过程
(1).线程1访问同步块,检查标志为01和是否偏向0,后获取偏向锁,修改Mark Word中线程id指向自己,从无锁状态改为偏向锁状态,开始执行代码逻辑,在执行过程中,线程2也来到临界点准备访问同步块,检查是否偏向线程id是否是自己,CAS准备偏向线程ID是否是自己,此时线程1正占用锁,所以修改失败,再线程1的执行到达安全点时要求线程1撤销偏向锁,升级为轻量级锁,如果刚好再要求撤销时线程1执行完毕了已经退出同步代码块,解锁后将ThreadID置空同时将偏向由1改为0,线程2重新获取偏向锁执行。
💖4.2 无锁状态升级到重量级锁过程
(1),在线程访问同步代码块的时候当前线程会在栈上开辟一块空间LockRecord,同时复制MakerWork过来,并且同时会定义一个叫做owner的指针变量,锁从偏向锁升级到轻量级锁的时候会把MakerWork前面30位定义为指针,指向栈内存中开辟的LockRecord空间,owner指针也会指向堆内存中的MakerWork空间,
(2),线程1和线程2同时访问同步代码块同时会开辟一块LockRecord空间同时复制MarkWord过来,然后线程1和线程2同时开始CAS修改,拿到执行权的线程1修改Mark Word中的指针指向地址,升级为轻量级锁,此时线程1开始执行同步体,线程2来进行第一次修改,修改失败进入自旋,如果线程1执行完逻辑代码之后,可能会自旋成功,如果自旋次数过多失败的话,先会向系统底层Pthread线程调度库,申请一个互斥量此时又会发生用户态到内核态的转变(这个过程非常耗费资源) ,申请完互斥量后会将Mark Word中的前30位指向不再指向线程1开辟的LockRecord空间,转而指向我们的重量级锁依靠底层的互斥量,之后调用Pthread库中的Pthread_mutex_loc将线程阻塞挂起,此时线程1执行完之后想要释放轻量级锁发现MarkWord中前30位被修改了,他会释放轻量级锁并且会唤醒阻塞的线程,进行新一轮的锁竞争。
😊5.同步框架AQS
💖5.1 AQS简介:等待队列,条件队列,独占获取,共享获取等。
💖5.2 可重入锁和非可重入锁区别
一个线程可能带着多个子流程,在这些子流程执行完之后才可以释放锁,这些子流程都可以获取同一把锁,这是可重入锁,线程的子流程不可以获取同一把锁这是不可重入锁,下面是一个经典例子:
💖5.3 并发包内部基本实现
💖5.4 条件队列和等待队列
(1)每个节点都有自己,结点状态waitStatus
这里我们说下Node。Node结点是对每一个等待获取资源的线程的封装,其包含了需要同步的线程本身及其等待状态,如是否被阻塞、是否等待唤醒、是否已经被取消等。变量waitStatus则表示当前Node结点的等待状态,共有5种取值CANCELLED、SIGNAL、CONDITION、PROPAGATE、0。
CANCELLED(1):表示当前结点已取消调度。当timeout或被中断(响应中断的情况下),会触发变更为此状态,进入该状态后的结点将不会再变化。
SIGNAL(-1):表示后继结点在等待当前结点唤醒。后继结点入队时,会将前继结点的状态更新为SIGNAL。 CONDITION(-2):表示结点等待在Condition上,当其他线程调用了Condition的signal()方法后,CONDITION状态的结点将从等待队列转移到同步队列中,等待获取同步锁。
PROPAGATE(-3):共享模式下,前继结点不仅会唤醒其后继结点,同时也可能会唤醒后继的后继结点。
0:新结点入队时的默认状态。注意,负值表示结点处于有效等待状态,而正值表示结点已被取消。所以源码中很多地方用>0、<0来判断结点的状态是否正常。
等待队列相当于,数据结构中的双向链表。
条件队列相当于单向链表,prev前驱指针和next后驱指针都为空
💖5.5 非公平锁和公平锁
非公平就是不用进去队列,直接跟队列中的紧接着要访问同步锁的线程进行竞争,竞争成功则拿到锁执行逻辑代码,竞争失败则到队列中排队。
上一篇下一篇