网站首页 > 文章精选 正文
引言
Synchronized 是 Java 中常用的同步机制,用于确保多个线程在访问共享资源时的互斥性。然而,许多 Java 程序员都不了解 Synchronized 的优化技巧和原理,从而导致程序性能和并发性能的下降。在本篇技术博客中,我们将详细探讨 Synchronized 的锁优化和获取锁的流程,并提供相关的 Java 代码示例。
大家好,这里是互联网技术学堂,留下你的点赞、关注、分享,支持一下吧,谢谢。
章节一:Synchronized 的锁优化
Synchronized 的锁优化主要包括以下几个方面:
1. 细粒度锁
在实际应用中,对于一些共享资源,我们可以采用细粒度锁来提高并发性能。比如,在一个对象中,有多个方法需要访问同一个共享资源,但这些方法并不是同时执行的,我们可以将这些方法中需要访问共享资源的代码块加上 Synchronized 关键字,以达到保证共享资源的互斥性的目的。这样做的好处是,可以减小锁的粒度,提高并发性能。
示例代码:
public class Counter {
private int count;
public synchronized void increment() {
count++;
}
public synchronized void decrement() {
count--;
}
}
上述代码中,使用 Synchronized 关键字修饰了 increment 和 decrement 方法,以确保对 count 的操作是原子性的。这样做的缺点是,如果在高并发的情况下,所有的线程都需要获取 Counter 对象的锁,会导致性能下降。因此,我们可以采用下面介绍的锁优化技巧来提高并发性能。
2. 锁粗化
锁粗化是指将多个连续的加锁、解锁操作合并成一个较大的代码块,以减少加锁、解锁的次数,从而提高并发性能。在实际应用中,如果发现某个对象的多个方法都需要加锁、解锁,可以将它们合并成一个大的代码块,这样做的好处是减少了加锁、解锁的次数,从而减少了线程间的竞争,提高了并发性能。
示例代码:
public class Counter {
private int count;
public void increment() {
synchronized (this) {
count++;
}
}
public void decrement() {
synchronized (this) {
count--;
}
}
}
上述代码中,将 increment 和 decrement 方法中的 Synchronized 关键字去掉,并在方法中加上一个同步代码块,以确保对 count 的操作是原子性的。这样做的好处是,避免了在高并发情况下,所有的线程都需要获取 Counter 对象的锁,从而提高了并发。
3. 锁分离
锁分离是指将一个对象中的多个共享资源,分别使用不同的锁来保护,以减小锁的粒度,提高并发性能。在实际应用中,如果一个对象中有多个共享资源,我们可以使用不同的锁来保护它们,这样做的好处是,可以减小锁的粒度,提高并发性能。
示例代码:
public class Counter {
private int count1;
private int count2;
private Object lock1 = new Object();
private Object lock2 = new Object();
public void incrementCount1() {
synchronized (lock1) {
count1++;
}
}
public void decrementCount1() {
synchronized (lock1) {
count1--;
}
}
public void incrementCount2() {
synchronized (lock2) {
count2++;
}
}
public void decrementCount2() {
synchronized (lock2) {
count2--;
}
}
}
上述代码中,将 Counter 对象中的 count1 和 count2 分别使用 lock1 和 lock2 两个对象来保护,以确保对它们的操作是原子性的。这样做的好处是,避免了在高并发情况下,所有的线程都需要获取 Counter 对象的锁,从而提高了并发性能。
4. 自旋锁
自旋锁是指在获取锁失败时,线程不会被阻塞,而是会不断地尝试获取锁,直到获取成功为止。在低并发的情况下,自旋锁可以减少线程上下文切换的开销,提高并发性能。但是,在高并发的情况下,自旋锁会占用大量的 CPU 资源,从而导致性能下降。
示例代码:
public class Counter {
private int count;
private volatile boolean lock = false;
public void increment() {
while (lock) {
// 自旋等待锁释放
}
lock = true;
count++;
lock = false;
}
public void decrement() {
while (lock) {
// 自旋等待锁释放
}
lock = true;
count--;
lock = false;
}
}
章节二:获取锁的流程分析
在使用 Synchronized 时,线程获取锁的流程分为以下几个步骤:
1. 尝试获取锁
当一个线程执行到 Synchronized 代码块或方法时,它会尝试获取锁。
2. 获取锁成功
轻量级锁的释放也是通过 CAS 操作来进行的,主要步骤如下:
- 取出在获取轻量级锁保存在 Displaced Mark Word 中的数据;
- 用 CAS 操作将取出的数据替换当前对象的 Mark Word 中,如果成功,则说明释放锁成功;
- 如果 CAS 操作替换失败,说明有其他线程尝试获取该锁,则需要在释放锁的同时需要唤醒被挂起的线程。
3. 获取锁失败
如果当前锁已经被其他线程持有,则当前线程会进入阻塞状态,直到获取到锁为止。
4. 阻塞状态
当线程进入阻塞状态时,它会进入一个等待队列,等待其他线程释放锁并通知它。在 Java 中,等待队列是通过内部锁对象的 wait() 和 notify() 方法实现的。
5. 通知等待线程
当一个线程释放锁并通知等待队列中的其他线程时,它会通过内部锁对象的 notify() 或 notifyAll() 方法来通知等待队列中的线程。
6. 竞争锁
当多个线程同时竞争同一个锁时,它们会进入锁池。在锁池中,所有等待锁的线程会处于阻塞状态,直到其中一个线程获取到锁并进入同步代码块或方法中。
7. 释放锁
当线程执行完同步代码块或方法后,会释放锁,并通知等待队列中的其他线程。
章节三:Synchronized 锁优化
Synchronized 锁优化可以分为以下几个方面:
1. 减小锁粒度
减小锁粒度是指将一个大的同步代码块或方法,拆分成多个小的同步代码块或方法,以减小锁的粒度,提高并发性能。在实际应用中,如果一个对象中有多个共享资源,我们可以使用不同的锁来保护它们,这样做的好处是,可以减小锁的粒度,提高并发性能。
2. 选择合适的锁类型
在 Java 中,Synchronized 可以分为对象锁和类锁两种。如果多个线程需要竞争同一个对象的资源,我们可以使用对象锁;如果多个线程需要竞争同一个类的资源,我们可以使用类锁。在实际应用中,我们需要根据具体情况来选择合适的锁类型,以提高并发性能。
3. 使用可重入锁
可重入锁是指同一个线程可以重复获取同一个锁而不会死锁的锁。在 Java 中,Synchronized 就是一种可重入锁。如果一个线程已经获取了某个锁,那么它在获取该锁的过程中,可以重复获取多次,而不会出现死锁现象。
4. 避免过度同步
过度同步是指在同步代码块或方法中,不必要的代码也被同步了,从而导致性能下降。在实际应用中,我们应该避免过度同步,只同步必要的代码块或方法,以提高并发性能。
5. 减小同步代码块或方法的执行时间
同步代码块或方法的执行时间越短,线程在获取锁和释放锁的时间就会越短,从而提高并发性能。在实际应用中,我们可以通过将一些非同步的代码移到同步代码块或方法外,或者使用局部变量来减小同步代码块或方法的执行时间。
6. 使用 CAS 原子操作
CAS 原子操作是一种无锁并发机制,它能够提高并发性能,避免锁竞争。在 Java 中,AtomicInteger、AtomicLong、AtomicBoolean 等类就是使用 CAS 原子操作实现的。如果我们需要对一个共享变量进行增、减、比较等操作,并且不需要进行复杂的计算,那么使用 CAS 原子操作可以提高并发性能。
7. 使用分段锁
分段锁是指将一个共享资源分成多个段,对每个段分别加锁。在 Java 中,ConcurrentHashMap 就是使用分段锁来保护共享资源的。如果多个线程需要竞争同一个共享资源,并且这个共享资源可以分成多个独立的段,那么使用分段锁可以提高并发性能。
章节四:示例代码
下面是一个简单的示例代码,演示了 Synchronized 锁的使用流程和优化方式。
public class SynchronizedDemo {
private int count = 0;
public synchronized void add() {
count++;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedDemo demo = new SynchronizedDemo();
// 创建多个线程,同时对 count 变量进行加 1 操作
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
demo.add();
}
}).start();
}
// 等待所有线程执行完毕
Thread.sleep(1000);
// 输出 count 变量的值
System.out.println(demo.count);
}
}
在上面的示例代码中,我们创建了多个线程,同时对 count 变量进行加 1 操作。由于 count 变量是共享资源,我们需要对 add() 方法进行同步,以避免多个线程同时对 count 变量进行操作。在 add() 方法中,我们使用了 synchronized 关键字来获取锁,确保在同一时刻只有一个线程可以执行该方法。在 main() 方法中,我们使用 Thread.sleep() 方法来等待所有线程执行完毕,并输出 count 变量的值。
在实际应用中,我们需要对 Synchronized 锁进行优化,以提高并发性能。我们可以根据具体情况,选择合适的锁方式和优化策略。下面是一个优化后的示例代码,演示了如何使用 ReentrantLock 来代替 synchronized 关键字。
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void add() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockDemo demo = new ReentrantLockDemo();
// 创建多个线程,同时对 count 变量进行加 1 操作
for (int i = 0; i < 10; i++) {
new Thread(() -> {
for (int j = 0; j < 10000; j++) {
demo.add();
}
}).start();
}
// 等待所有线程执行完毕
Thread.sleep(1000);
// 输出 count 变量的值
System.out.println(demo.count);
}
}
在上面的示例代码中,我们使用了 ReentrantLock 来代替 synchronized 关键字。在 add() 方法中,我们首先调用 lock() 方法获取锁,在 try-catch-finally 代码块中执行业务逻辑,最后调用 unlock() 方法释放锁。由于 ReentrantLock 是可重入锁,因此同一个线程可以多次获取锁,而不会造成死锁的情况。
总结
在实际应用中,我们需要根据具体情况,选择合适的同步方式和优化策略,以提高并发性能。我们需要根据业务逻辑的复杂程度、共享资源的访问频率和并发度、线程之间的竞争关系等因素,综合考虑选择合适的同步方式和优化策略。
猜你喜欢
- 2024-12-26 Java高级:条件队列与同步器Synchronizer的原理+AQS的应用
- 2024-12-26 浅谈Java多线程与并发原理 java多线程并发调用接口
- 2024-12-26 Java 基础(四)集合源码解析 List java集合linkedlist
- 2024-12-26 synchronized和lock的区别 54.synchronized 和 lock 有什么区别?
- 2024-12-26 异步 vs 同步:程序员必备的核心知识,理解这两者差异,你就是高手
- 2024-12-26 ArrayList 、 LinkedList、Vector的区别
- 2024-12-26 java面试基础题(实战后的总结) java面试必考300题
- 2024-12-26 synchronized底层细究(硬核) synchronized底层原理是什么
- 2024-12-26 100+道高频Java面试题 java面试高频知识点
- 2024-12-26 如何看到 synchronized 背后的“monitor 锁”?
- 最近发表
- 标签列表
-
- newcoder (56)
- 字符串的长度是指 (45)
- drawcontours()参数说明 (60)
- unsignedshortint (59)
- postman并发请求 (47)
- python列表删除 (50)
- 左程云什么水平 (56)
- 计算机网络的拓扑结构是指() (45)
- 稳压管的稳压区是工作在什么区 (45)
- 编程题 (64)
- postgresql默认端口 (66)
- 数据库的概念模型独立于 (48)
- 产生系统死锁的原因可能是由于 (51)
- 数据库中只存放视图的 (62)
- 在vi中退出不保存的命令是 (53)
- 哪个命令可以将普通用户转换成超级用户 (49)
- noscript标签的作用 (48)
- 联合利华网申 (49)
- swagger和postman (46)
- 结构化程序设计主要强调 (53)
- 172.1 (57)
- apipostwebsocket (47)
- 唯品会后台 (61)
- 简历助手 (56)
- offshow (61)