Java中的关键字synchronized 详解
Java中的关键字synchronized是Java语言中用于线程同步的关键字。它可以修饰方法或代码块,以便在多线程环境中保护共享资源的安全。
1、修饰普通方法
synchronized关键字可以修饰普通方法,以便保护方法内的资源。例如:
```java
private synchronized void synMethod() {
// 方法体
}
```
在这种情况下,synchronized锁的对象实例。
2、修饰静态方法
synchronized关键字也可以修饰静态方法,以便保护类中的静态资源。例如:
```java
private static synchronized void synMethod() {
// 方法体
}
```
在这种情况下,synchronized锁的是当前Class类对象。
3、同步方法块
synchronized关键字还可以用于修饰代码块,以便保护特定的资源。例如:
```java
private void synMethod1() {
synchronized (this) {
// 方法体
}
}
```
在这种情况下,synchronized锁对象实例。
```java
private void synMethod2() {
synchronized (ThreadTest.class) {
// 方法体
}
}
```
在这种情况下,synchronized锁的是当前Class类对象。
锁原理
在了解锁原理之前,先认识一下Java对象头Mark Word。Mark Word是对象头中的一个32位的字段,用于存储对象的锁状态信息。
| 锁状态 | 25 bit | 4 bit | 1 bit | 2 bit |
| --- | --- | --- | --- | --- |
| 无锁 | 对象hashCode | 对象分代年龄 | 01 | 00 |
| 偏向锁 | 线程ID | Epoch | 01 | 01 |
| 轻量锁 | 指向栈中锁记录的指针 | 00 | 00 |
| 重量锁 | 指向互斥量的指针 | 10 | 10 |
| GC标记 | GC标记 | 11 | 11 |
锁原理可以分为三种:偏向锁、轻量锁和重量锁。
偏向锁
偏向锁是Java 6中引入的一种锁机制,它的目的是为了减少无竞争的情况下的锁开销。在这种情况下,线程在访问同步块时,如果没有其他线程对锁进行竞争,并且由同一个线程多次获得锁,也就是单线程运行同步代码,在这种情况下,若是每次还阻塞线程,就代表白白浪费CPU性能。这种情况下,引入了偏向锁概念。
偏向锁的工作流程:
1. 访问同步代码块
2. 判断对象头Mark Word中存储的线程ID是否指向当前线程,如果是,则表明当前是锁的重入,不需要再获得锁,直接执行同步代码
3. 如果不是,则尝试使用CAS算法将线程ID更新至对象头中。
4. 成功,获得锁,执行同步代码。更新失败表明存在锁竞争,等待全局安全点,暂停拥有偏向锁的线程,根据对象头的锁标志位,选择将偏向锁升级为轻量锁或者置为无锁。
轻量锁
轻量锁是Java 6中引入的一种锁机制,它的目的是为了减少竞争的情况下的锁开销。在这种情况下,线程在访问同步块时,先在当前线程的线程栈中创建一个锁记录(Lock Record)区域。把对象头Mark Word拷贝到Lock Record中。利用CAS尝试将对象头Mark Word中的线程指针更新为指向当前线程的指针更新成功,则获得轻量锁。更新失败,检查Mark Word中的指针是否指向当前线程。如果是,则说明是锁的重入现象。执行同步代码块如果不是,则说明此时存在竞争。需要把轻量锁膨胀为重量锁。
重量锁
重量锁是基于对象监视器(Monitor)来实现的。线程在执行同步代码时,需要调用一个Monitor.enter指令。执行退出后,调用Monitor.exit指令。这里看得出,监视器具有排它性,一个时间点只能有一个线程enter成功,其他线程只能阻塞在队列中。所以这种重量锁的操作成本很高。
synchronized关键字是Java语言中用于线程同步的关键字,它可以修饰方法或代码块,以便保护共享资源的安全。但是,synchronized关键字也存在一定的开销,因此在实际应用中需要根据具体情况选择合适的锁机制。