背景
在多线程环境下同时访问共享资源会出现一些数据问题,此关键字就是用来保证线程安全的解决这一问题。
内存可见的问题
在了解synchronized
之前先了解一下java内存模型,如下图:
- 线程1去主内存获取x的值读入本地内存此时x的值为1,进行运算x+1此时线程1的x值为2,然后写入主内存;
- 此时在线程1先入主内存之前,此时线程2去主内存读取了x的值,它读取到的值是1;
- 最后x的值在主内存里的值是2,线程2读取到的是1,出现了内存不可见的问题。
synchronized关键字的使用方式
修饰方法
public class SynchronizedMethodExample {
private int counter = 0;
// synchronized 修饰的方法
public synchronized void increment() {
// 这里的操作是原子的,同一时刻只有一个线程能够执行
counter++;
System.out.println("Incremented counter to: " + counter);
}
public static void main(String[] args) {
SynchronizedMethodExample example = new SynchronizedMethodExample();
// 创建多个线程,同时调用 increment 方法
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
example.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
example.increment();
}
});
// 启动线程
thread1.start();
thread2.start();
try {
// 等待两个线程执行完毕
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印最终的计数器值
System.out.println("Final counter value: " + example.counter);
}
}
由于 synchronized 修饰了 increment 方法,保证了同一时刻只有一个线程能够执行该方法。因此,两个线程在执行 increment 方法时会互斥,不会同时对 counter 进行操作。
执行结果:
Incremented counter to: 1
Incremented counter to: 2
Incremented counter to: 3
Incremented counter to: 4
Incremented counter to: 5
Incremented counter to: 6
Incremented counter to: 7
Incremented counter to: 8
Incremented counter to: 9
Incremented counter to: 10
Final counter value: 10
修饰同步代码块
public class SynchronizedBlockExample {
private int counter = 0;
private final Object lockObject = new Object(); // 用于同步的对象
public void increment() {
// 一些非同步的代码
synchronized (lockObject) {
// 需要同步的代码块
counter++;
System.out.println("Incremented counter to: " + counter);
}
// 继续执行非同步的代码
}
public static void main(String[] args) {
SynchronizedBlockExample example = new SynchronizedBlockExample();
// 创建多个线程,同时调用 increment 方法
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
example.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
example.increment();
}
});
// 启动线程
thread1.start();
thread2.start();
try {
// 等待两个线程执行完毕
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印最终的计数器值
System.out.println("Final counter value: " + example.counter);
}
}
在这个例子中,increment 方法包含了一个同步的代码块,使用 synchronized (lockObject) 对 counter 进行递增操作。由于使用了 lockObject 作为同步对象,保证了两个线程在执行同步代码块时是互斥的,不会同时对 counter 进行操作。
执行结果:
Incremented counter to: 1
Incremented counter to: 2
Incremented counter to: 3
Incremented counter to: 4
Incremented counter to: 5
Incremented counter to: 6
Incremented counter to: 7
Incremented counter to: 8
Incremented counter to: 9
Incremented counter to: 10
Final counter value: 10
修饰静态方法
public class SynchronizedStaticMethodExample {
private static int counter = 0;
// synchronized 修饰的静态方法
public static synchronized void increment() {
// 这里的操作是原子的,同一时刻只有一个线程能够执行
counter++;
System.out.println("Incremented counter to: " + counter);
}
public static void main(String[] args) {
// 创建多个线程,同时调用静态方法 increment
Thread thread1 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
SynchronizedStaticMethodExample.increment();
}
});
Thread thread2 = new Thread(() -> {
for (int i = 0; i < 5; i++) {
SynchronizedStaticMethodExample.increment();
}
});
// 启动线程
thread1.start();
thread2.start();
try {
// 等待两个线程执行完毕
thread1.join();
thread2.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 打印最终的计数器值
System.out.println("Final counter value: " + SynchronizedStaticMethodExample.counter);
}
}
在这个例子中,increment 方法是一个静态方法,使用 synchronized 修饰。由于是静态方法,它锁定的是整个类的 Class 对象。两个线程无法同时调用 increment 方法,确保了对 counter 的递增操作是线程安全的。
Incremented counter to: 1
Incremented counter to: 2
Incremented counter to: 3
Incremented counter to: 4
Incremented counter to: 5
Incremented counter to: 6
Incremented counter to: 7
Incremented counter to: 8
Incremented counter to: 9
Incremented counter to: 10
Final counter value: 10
synchronized原理
java对象的组成
对象头:
ObjectMonitor
HotSpot虚拟机源码中ObjectMonitor.hpp。
ObjectMonitor::ObjectMonitor() {
_header = NULL; // 监视器头部,用于保存状态信息
_count = 0; // 计数器,用于记录监视器的使用次数
_waiters = 0; // 等待线程数
_recursions = 0; // 当前线程对该锁的递归次数
_object = NULL; // 监视的对象
_owner = NULL; // 拥有锁的线程
_WaitSet = NULL; // 等待队列,存储等待该锁的线程
_WaitSetLock = 0 ; // 用于保护等待队列的锁
_Responsible = NULL ; // 释放锁的线程
_succ = NULL ; // 后继监视器
_cxq = NULL ; // 入口等待队列
FreeNext = NULL ; // 空闲监视器链表的下一个
_EntryList = NULL ; // 入口列表
_SpinFreq = 0 ; // 自旋频率
_SpinClock = 0 ; // 自旋时钟
OwnerIsThread = 0 ; // 拥有者是否为线程
}
ObjectMonitor.hpp
是 HotSpot 虚拟机(OpenJDK 的默认虚拟机实现)中用于实现对象监视器的头文件。对象监视器在 Java 中由 synchronized
关键字提供支持,用于实现多线程之间的同步。以下是对 ObjectMonitor.hpp
的一些关键部分的简要解释:
- ObjectMonitor 头部(header):
ObjectMonitor
的头部包含了一系列标志位,用于表示锁的状态、等待队列的状态等。这些标志位在字节层面上被设置和检查,以进行对锁的操作。一些常见的标志位有:
- 等待队列(WaitSet):
ObjectMonitor
中包含一个等待队列,用于存储等待该锁的线程。线程在等待队列中等待时,它会进入等待状态,直到被唤醒。等待队列的管理涉及到线程的入队和出队等操作。 - 线程入队和出队:
ObjectMonitor
定义了一些方法,用于线程的入队和出队操作。例如:
void enter(Handle h)
:线程尝试进入临界区。void exit()
:线程退出临界区。void wait(bool, jlong, jlong)
:线程进入等待状态。void notify()
和void notifyAll()
:唤醒一个或所有等待线程。
- 锁的状态转换:
ObjectMonitor
定义了一些方法来实现锁状态的转换,例如从无锁到轻量级锁、从轻量级锁到重量级锁等。这些状态的转换涉及到了底层的原子操作和 CAS(Compare and Swap)等机制。 - 适应性自旋锁:
HotSpot 虚拟机中的ObjectMonitor
还包括适应性自旋锁的机制,该机制用于在获取锁时进行自旋,以避免线程进入阻塞状态。适应性自旋锁的目标是根据程序运行时的历史信息来动态调整自旋次数,以提高性能。
-
当多个线程同时访问同步代码块时,首先会进入到
EntryList
中,然后通过CAS的方式尝试将Monitor
中的owner
字段设置为当前线程,同时count
加1,若发现之前的owner
的值就是指向当前线程的,recursions
也需要加1。如果CAS尝试获取锁失败,则进入到EntryList中; -
当获取锁的线程调用wait()方法,则会将owner设置为null,同时
count
减1,recursions
减1,当前线程加入到WaitSet中,等待被唤醒; -
当前线程执行完同步代码块时,则会释放锁,
count
减1,recursions
减1。当recursions的值为0时,说明线程已经释放了锁.
synchronized作用于同步代码块的字节码指令
在Java中,synchronized
作用于同步代码块的字节码指令主要涉及到 monitorenter
和 monitorexit
指令。这两个指令用于实现监视器(monitor)的进入和退出,即获取和释放锁。
以下是一个简单的Java同步代码块的例子:
public class SynchronizedExample {
private static final Object lock = new Object();
public void synchronizedMethod() {
synchronized (lock) {
// 同步代码块
// ...
}
}
}
public synchronizedMethod()V
L0
LINENUMBER 7 L0
GETSTATIC SynchronizedExample.lock : Ljava/lang/Object;
DUP
ASTORE 1
MONITORENTER // monitorenter 指令,获取锁
// 同步代码块的字节码指令
// ...
L1
LINENUMBER 9 L1
ALOAD 1
MONITOREXIT // monitorexit 指令,释放锁
ATHROW
GETSTATIC SynchronizedExample.lock : Ljava/lang/Object;
: 获取lock
字段的值,即获取锁对象。DUP
: 复制栈顶数值,用于后续的ASTORE 1
操作。ASTORE 1
: 将锁对象的引用存储到局部变量1。MONITORENTER
: 进入监视器,即获取锁。如果锁已经被其他线程占用,当前线程将阻塞等待。- 同步代码块的具体实现。
ALOAD 1
: 将之前存储的锁对象引用加载到栈顶。MONITOREXIT
: 退出监视器,即释放锁。ATHROW
: 抛出异常,确保在任何情况下都会释放锁。
synchronized作用于方法字节码指令
以下是一个简单的例子,演示了 synchronized
修饰方法的字节码指令:
public class SynchronizedMethodExample {
private int counter = 0;
public synchronized void synchronizedMethod() {
counter++;
}
}
public class SynchronizedMethodExample {
private int counter;
public SynchronizedMethodExample() {
counter = 0;
}
public synchronized void synchronizedMethod() {
// 获取锁
monitorenter
try {
// 同步代码块
counter++;
} finally {
// 释放锁
monitorexit
}
}
}
在这个例子中:
synchronized锁的优化
JDK1.5之前,synchronized
是属于重量级锁,重量级需要依赖于底层操作系统的Mutex Lock
实现,然后操作系统需要切换用户态和内核态,这种切换的消耗非常大,所以性能相对来说并不好。
总结
synchronized
是 Java 中用于实现线程同步的关键字,它提供了对代码块、方法以及静态方法的同步支持。以下是关于 synchronized
锁的总结:
- 对象锁:
- 方法锁:
- 代码块锁:
- 锁的释放:
- 偏向锁、轻量级锁和重量级锁:
- 可重入性:
总体而言,synchronized
是一种简单而有效的同步机制,用于确保多线程程序中对共享资源的安全访问。然而,在一些高并发场景下,可能需要考虑使用更灵活的锁机制,如 java.util.concurrent
包中提供的锁。
参考
https://zhuanlan.zhihu.com/p/377423211
https://juejin.cn/post/6973571891915128846
原文地址:https://blog.csdn.net/qq_36649893/article/details/134795964
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.7code.cn/show_40292.html
如若内容造成侵权/违法违规/事实不符,请联系代码007邮箱:suwngjj01@126.com进行投诉反馈,一经查实,立即删除!