synchronized 被称为重量级锁 , 但是 1.6 版本后得到了优化 , 相对轻量了很多, 它可以保证在同一个时刻,只有一个线程可以执行某个方法或者某个代码块 .
对象头结构
// 原理 -->
1 Java 对象头 和 Monitor
|-> 对象头 :
|-> Mark Word(标记字段)、Klass Pointer(类型指针)
|-> Klass Pointer : 类元数据指针,决定是何数据
|-> Mark Word : 自身运行时数据 (hashcode,锁状态,偏向,标志位等)
|-> Monitor :
|-> 互斥 :一个 Monitor 锁在同一时刻只能被一个线程占用
// 关系 -->
- Monitor 是一种对象类型 , 任何Java 对象都可以是 Monitor 对象
- 当Java 对象被 synchronized 修饰时 , 就可以当成 Monitor 对象进行处理
// Mark Word 和 Class Metadata Address 组成结构
--------------------------------------------------------------------------------------------------
虚拟机位数 头对象结构 说明
|---------|-----------------------|---------------------------------------------------------------|
32/64bit Mark Word 存储对象的hashCode、锁信息或分代年龄或GC标志等信息
32/64bit Class Metadata Address 类型指针指向对象的类元数据,JVM通过这个指针确定该对象是哪个类的实例。
--------------------------------------------------------------------------------------------------
32 位虚拟机 Mark Word >>>>
64 位虚拟机 Mark Word >>>>
数据结构
// Monitor 的实现方式 @ https://blog.csdn.net/javazejian/article/details/70768369
ObjectMonitor中有两个队列以及一个区域
_WaitSet 和 _EntryList,用来保存ObjectWaiter对象列表( 每个等待锁的线程都会被封装成ObjectWaiter对象)
_owner (指向持有ObjectMonitor对象的线程) 区域
Monitor 指令
monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处,JVM要保证每个monitorenter必须有对应的monitorexit与之匹配
查看汇编情况 :
// Step 1 : 准备简单的Demo
public class SynchronizedService {
public void method() {
synchronized (this) {
System.out.println("synchronized 代码块");
}
}
}
// Step 2 : 查看汇编码
javap -c -s -v -l SynchronizedService.class
// Step 3 : 注意其中 3 和 13 以及 19
public void method();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter
4: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
7: ldc #3 // String synchronized 代码块
9: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
12: aload_1
13: monitorexit
14: goto 22
17: astore_2
18: aload_1
19: monitorexit
20: aload_2
21: athrow
22: return
处理逻辑详情 :
// synchronized 源码分析 @ https://blog.csdn.net/javazejian/article/details/70768369
// 具体的流程可以参考上面博客的具体分析, 此处仅总结 , 大佬们肝真好
// synchronized 代码块底层逻辑 ( monitorenter 和 monitorexit )
> synchronized 同步语句块的实现使用的是 monitorenter 和 monitorexit 指令,
其中 monitorenter 指令指向同步代码块的开始位置,monitorexit 指令则指明同步代码块的结束位置
Start : 当执行monitorenter指令时,当前线程将试图获取 objectref(即对象锁) 所对应的 monitor 的持有权
Thread-1 : objectref.monitor = 0 --> 获取monitor --> 设置计数器值为 1
Thread-2 : 发现objectref.monitor = 0 --> 阻塞等待 --> Thread-1 执行 monitorexit ,
计数器归 0 --> Thread-2 正常流程获取获取monitor
注意点 :
编译器将会确保无论方法通过何种方式完成,方法中调用过的每条 monitorenter 指令都有执行其对应 monitorexit 指令 ,
方法异常时通过异常处理器处理异常结束
synchronized 方法底层逻辑 (ACC_SYNCHRONIZED标识)
异常处理 : 如果一个同步方法执行期间抛 出了异常,并且在方法内部无法处理此异常,那这个同步方法所持有的monitor将在异常抛到同步方法之外时自动释放
synchronized 内存级原理
// 最后生成的汇编语言
lock cmpxchg %r15, 0x16(%r10) 和 lock cmpxchg %r10, (%r11)
// 加锁方式 ,当前实例 ,当前class , 自定义object
> synchronized(this)
> synchronized(object)
> synchronized(class) 或者静态代码块
synchronized关键字最主要的三种使用方式:
这里再提一下:synchronized关键字加到非 static 静态方法上是给对象实例上锁。
另外需要注意的是 尽量不要使用 synchronized(String a), 部分字符串常量会缓冲到常量池里面, 不过可以试试 new String("a")
解释 : synchronized 提供了一种独占式的加锁方式 ,其添加和释放锁的方式由JVM实现
阻塞 : 当 synchronized 尝试获取锁的时候,获取不到锁,将会一直阻塞
谈谈 synchronized和ReenTrantLock 的区别
synchronized 与等待唤醒机制 (notify/notifyAll和wait) 等待唤醒机制需要处于synchronized代码块或者synchronized方法中 , 调用这几个方法前必须拿到当前对象的监视器monitor对象
synchronized 与 线程中断 线程的中断操作对于正在等待获取的锁对象的synchronized方法或者代码块并不起作用
锁可以按照以下等级进行升级 : 偏向锁 -> 轻量级锁 -> 重量级锁 , 锁的升级是单向的
锁清除 :
Java虚拟机在 JIT编译时 (可以简单理解为当某段代码即将第一次被执行时进行编译,又称即时编译),通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过这种方式消除没有必要的锁,可以节省毫无意义的请求锁时间
Java 常见的锁
内部锁 :
公平锁/非公平锁
可重入锁 可重入锁指在一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁,ReentrantLock与Synchronized都是可重入的。
独享锁/共享锁 独享锁是指一个锁只能一个线程独有,共享锁指一个锁可被多个线程共享,对于ReadWriteLock,读锁是共享锁,写锁是独享所。
互斥锁/读写锁 独享锁/共享锁是一种广义的说法,互斥锁/读写锁是其具体实现。
乐观锁/悲观锁
分段锁
对象头的变化可以看下图 , 说的很清楚了 @ https://www.cnblogs.com/jhxxb/p/10983788.html
// 之前知道 , 锁的状态改变是单向的 , 由 偏向锁 -> 轻量级锁 -> 重量级锁 ,我们分别捋一下
// 偏向锁 -> 偏向锁, 即重偏向操作
1 对象先偏向于某个线程, 执行完同步代码后 , 进入安全点时,若需要重偏向,会把类对象中 epoch 值增加
2 退出安全点后 , 当有线程需要尝试获取偏向锁时, 直接检查类实例对象中存储的 epoch 值与类对象中存储的 epoch 值是否相等, 如果不相等, 则说明该对象的偏向锁已经无效了, 可以尝试对此对象重新进行偏向操作。
// 偏向锁 -> 轻量级锁
1 当发现对象已被锁定 ,且 ThreadID 不是自己 , 转为 偏向锁 , 在该线程的栈帧中建立 Lock Record 空间
// 这要从机制说起 , 每一种锁都有各自的特点
> 偏向锁
- 优点 : 无 CAS ,消耗少 , 性能高 , 可重入
- 缺点 : 锁竞争时撤销锁消耗高
- 场景 : 同一个线程执行同步代码
> 轻量级锁
- 优点 : 竞争的线程不会阻塞
- 缺点 : 轻量级锁未获取锁时会通过自旋获取 , 消耗资源
- 场景 : 线程交替执行同步块或者同步方法,追求响应时间,锁占用时间很短
> 重量级锁
- 优点 : 线程竞争不使用自旋 , 只会唤醒和等待
- 缺点 : 造成线程阻塞 , 锁的改变也消耗资源
- 场景 : 追求吞吐量,锁占用时间较长
// 针对不同的需求 , 选择合适的锁 , 达到业务目的
synchronized 是一个修饰符 , 我们需要从 C 的角度去看
Step 1 : 下载 OpenJDK 代码 https://blog.csdn.net/leisure_life/article/details/108367675
Step 2 : 根据代码索引 .c 文件
// TODO
public void operation(Integer check) {
/**
* 校验无锁情况
*/
public void functionShow(Integer check) {
logger.info("------> check is {} <-------", check);
if (check == 0) {
showNum = 100;
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else if (check == 1) {
showNum = 200;
}
logger.info("------> check is Over {} :{}", check, showNum);
}
/**
* 同步方法 , 校验 synchronized 方法
*/
synchronized public void functionShowSynchronized(Integer check) {
logger.info("------> check is {} <-------", check);
if (check == 0) {
showNum = 100;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else if (check == 1) {
showNum = 200;
}
logger.info("------> check is Over synchronized {} :{}", check, showNum);
}
/**
* 校验 synchronized 代码块
*/
public void statementShowSynchronized(Integer check) {
logger.info("------> check is {} <-------", check);
synchronized (this) {
if (check == 0) {
showNum = 100;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else if (check == 1) {
showNum = 200;
}
}
logger.info("------> check is Over synchronized {} :{}", check, showNum);
}
// 校验 代码块以 Class 为对象
public void classShowSynchronized(Integer check) {
logger.info("check is {} <-------", check);
synchronized (CommonTO.class) {
if (check == 0) {
showNum = 100;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else if (check == 1) {
showNum = 200;
}
}
logger.info("check is Over synchronized {} :{}", check, showNum);
}
// 同步代码块 Object
public void objectShowSynchronized(Integer check) {
logger.info("check is {} <-------", check);
synchronized (lock) {
if (check == 0) {
showNum = 100;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else if (check == 1) {
showNum = 200;
}
}
logger.info("check is Over synchronized {} :{}", check, showNum);
}
// 同步代码块 Object
public void objectStringShowSynchronized(Integer check) {
logger.info("check is {} <-------", check);
synchronized (lock2) {
if (check == 0) {
showNum = 100;
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
} else if (check == 1) {
showNum = 200;
}
}
logger.info("check is Over synchronized {} :{}", check, showNum);
}
阅读量:2029
点赞量:0
收藏量:0