Java synchronized锁详解
Synchronized关键字的两种用法
- 修饰实例方法
作用于非静态方法,此时synchronized保护的是TheadA的一个对象的method方法,是对象锁
- 修饰静态方法
作用于静态方法,此时锁为类锁,不同对象使用该方法时都会发生互斥现象.
- 修饰代码块
当其为this时,则是对象锁,当起为this.getClass()时,则是类锁.
Synchronized底层原理
1.理解Java对象头和管程(monitor)
-
实例变量:存放类的属性数据信息,包括父类的属性信息,如果是数组的实例部分还包括数组的长度,这部分内存按4字节对齐。
-
填充数据:由于虚拟机要求对象起始地址必须是8字节的整数倍。填充数据不是必须存在的,仅仅是为了字节对齐,这点了解即可。
-
对象头的主要结构是由Mark Word和Class Metadata Address组成
Mark Word 字段储存着,锁的状态,轻量级锁指针,重量级锁指针.
重量级锁也就是通常说synchronized的对象锁,锁标识位为10,其中指针指向的 monitor对象(也称为管程或监视器锁)的起始地址。每个对象都存在着一个 monitor 与之关联,对象与其 monitor 之间的关系有存在多种实现方式,如monitor可以与对象一起创建销毁或当线程试图获取对象锁时自动生成,但当一个 monitor 被某个线程持有后,它便处于锁定状态Monitor由ObjectMonitior类实现
ObjectMonitor中有三个部分
-
waitSet队列
-
EntryList队列
-
owner区域
多线程访问同步代码(monitorenter)时.
-
进入EntryList
-
获取要访问对象的monitor
-
Monitor.owner=当前线程;
-
monitor.count++;
-
if 线程调用wait方法,monitor.owner=null,monitor–,线程进入waitSet区堵塞
-
if 线程调用notify方法,waitSet中的任意一个线程根据不同的策略进入owner区或者entryList区.
-
线程运行完毕,monitor.owner=null.monitor–,线程离开
2.Syn底层实现
有Synchronized标志的方法将有一个ACC_SYNCHRONIZED标志来表示其为一个同步方法,当线程访问到标志时, 执行monitorenter指令,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权,并设置monitor计数器值为1, 直到正在执行线程执行完毕,即monitorexit指令被执行,执行线程将释放 monitor(锁)并设置计数器值为0 ,其他线程将有机会持有 monitor
3.Java对Synchronized锁的优化
无锁->偏向锁->轻量级锁->重量级锁
-
偏向锁:
当锁不仅不存在多线程竞争,而且总是由同一线程多次获得,因此为了减少同一线程获取锁(会涉及到一些CAS操作,耗时)的代价而引入偏向锁。偏向锁的核心思想是,如果一个线程获得了锁,那么锁就进入偏向模式,此时Mark Word 的结构也变为偏向锁结构,当这个线程再次请求锁时,无需再做任何同步操作,即获取锁的过程,这样就省去了大量有关锁申请的操作,从而也就提供程序的性能。
-
轻量级锁:
轻量级锁所适应的场景是线程交替执行同步块的场合,如果存在同一时间访问同一锁的场合,就会导致轻量级锁膨胀为重量级锁。
-
自旋锁:
基于在大多数情况下,线程持有锁的时间都不会太长,如果直接挂起操作系统层面的线程可能会得不偿失,毕竟操作系统实现线程之间的切换时需要从用户态转换到核心态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,因此自旋锁会假设在不久将来,当前的线程可以获得锁,因此虚拟机会让当前想要获取锁的线程做几个空循环(这也是称为自旋的原因),一般不会太久,可能是50个循环或100循环,在经过若干次循环后,如果得到锁,就顺利进入临界区。如果还不能获得锁,那就会将线程在操作系统层面挂起
4关于synchronized 可能需要了解的关键点
1. synchronized的可重入性