1.锁
2.volitaile
3.final
4.轻量级锁,偏向锁,重量级锁
关于Synchronized的维基百科定义
https://wiki.openjdk.java.net/display/HotSpot/Synchronization
主要的图如下
synchronized(可重入)的作用范围
1.静态方法(锁住的是Class实例),字节码方法的访问标记包含,ACC_SYNCHRONIZED
2.实例方法(锁住的是当前类实例)
3.代码块(锁住的是当前类),编译后包含monitorenter和monitoreixt(可能会有多个,因为要确保锁的正常执行路径以及异常执行路径上都能够被解锁)
锁的代价由低到高
偏向锁,轻量级锁,重量级锁
偏向锁:针对的是锁仅会被同一线程持有的情况。
只会在第一次请求时采用CAS操作,在锁对象的标记字段中记录下当前线程的地址。
在之后的运行过程中,持有该偏向锁的线程加锁操作将直接返回。
轻量级锁:针对的是多个线程在不同时间段申请同一把锁的情况。
采用CAS操作,将锁对象的标记字段替换为一个指针,指向当前线程栈上的一块空间,存储着锁对象原本的标记字段。
重量级锁:针对的是多个线程同时竞争同一把锁的情况。
java虚拟机会采用自适应(根据以往自旋等待时是否能够获得锁,动态调整自旋时间,即循环数目,自旋也会影响公平的锁机制,处于阻塞状态的线程,并没有办法立刻竞争被释放的锁,但处于自旋状态的线程,很有可能立刻能获取到锁),自旋(当执行任务的时间小于线程调度的时间时是划算的),来避免在面对非常小的synchronized代码块时,仍然会被阻塞,唤醒的情况。
其次关于偏向锁和轻量级锁的区分
对象头的标记字段(mark word),最后两位是用来表示该对象的锁状态.
00代表轻量级锁
01代表无锁(或者偏向锁)
10代表重量级锁
11代表垃圾回收算法的标记
当进行加锁的时候,java虚拟机会首先判断是不是重量级锁。
不是,则
①在当前线程的栈中划出一块空间,作为该锁的记录,并且将锁对象的标记字段复制到锁记录中。
②尝试用CAS操作替换锁对象的标记字段
假设锁对象标记字段为X...XYZ
java虚拟机会首先比较该字段是否为X...X01
如果是,则替换为刚才分配的锁记录的地址。
如果不是,X...X01有两种可能
①该线程重复获取同一把锁,此时,java虚拟机会将锁记录清零,代表该锁被重复获取。
②其他线程持有该锁,java虚拟机会将这把锁膨胀为重量级锁,并且阻塞当前线程。
当进行解锁操作时
如果当前锁记录的值为0(会有多个锁记录,可以把所有锁记录看成一个栈结构,加锁即压入一条锁记录,解锁即弹出一条锁记录,当前锁即栈顶的锁记录),代表重复进入同一把锁,直接返回
否则,java虚拟机会尝试用CAS操作,比较锁对象的标记字段的值是否为当前锁记录的地址。
如果是,则替换锁记录中的值,也就是锁对象原本的标记字段。此时,成功释放这把锁
如果不是,则意味着已经膨胀为重量级锁。此时会进入重量级锁的释放过程,唤醒因为竞争该锁而被阻塞的线程。
32位系统中Mark Word的32bit空间中的25bit用于存储对象哈希码(HashCode),4bit用于存储对象的分代年龄,2bit用于存储锁标志位,1bit固定为0
HotSpot虚拟机对象头Mark Word
存储内容 | 标志位 | 状态 |
---|---|---|
对象哈希码,对象分代年龄 | 01 | 未锁定 |
指向锁记录的指针 | 00 | 轻量级锁 |
指向重量级锁的指针 | 10 | 膨胀(重量级锁定) |
空,不需要记录信息 | 11 | GC标记 |
偏向线程ID,偏向时间戳,对象分代年龄 | 01 | 可偏向 |