目录
深入理解并发编程之 synchronized 和 Lock 的区别
在并发编程领域,理解synchronized
和Lock
(以ReentrantLock
为例)的区别对于编写高效、安全的多线程程序至关重要。这篇博客将详细介绍它们之间的差异。
一、synchronized
关键字
-
基本概念
synchronized
是 Java 中的内置关键字,用于实现方法或代码块的同步。它基于对象的内置锁(monitor)机制。当一个线程访问被synchronized
修饰的代码块或方法时,它会自动获取对象的锁,其他试图访问相同对象的同步代码的线程将被阻塞。 -
示例代码
以下是使用synchronized
关键字的简单示例:
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public int getCount() {
return count;
}
}
在上述代码中,increment
方法被synchronized
修饰,同一时刻只有一个线程能够执行该方法,保证了对count
变量的操作是线程安全的。
二、Lock
接口(以ReentrantLock
为例)
-
基本概念
Lock
是一个接口,ReentrantLock
是其一个重要的实现类。它提供了比synchronized
更灵活的锁机制。例如,可以手动获取和释放锁、尝试非阻塞地获取锁、可中断地获取锁以及超时获取锁等功能。 -
示例代码
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private int count = 0;
private ReentrantLock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
return count;
}
}
在这个示例中,通过ReentrantLock
来实现对count
变量的同步操作。在increment
方法中,首先使用lock()
方法获取锁,然后在finally
块中使用unlock()
方法释放锁,确保无论是否发生异常,锁都能被正确释放。
三、synchronized
和Lock
的区别
-
锁的获取和释放
synchronized
:由 Java 虚拟机自动获取和释放锁,线程执行完同步代码块或方法后自动释放锁。Lock
:需要手动调用lock()
方法获取锁,并在finally
块中调用unlock()
方法释放锁,这给予了程序员更精细的控制,但也增加了出错的可能性(如忘记释放锁)。
-
功能特性
synchronized
:相对简单直接,但功能有限。它是一种隐式的锁机制,不支持尝试获取锁、可中断获取锁等复杂操作。Lock
:功能更丰富。例如,可以使用tryLock()
方法尝试非阻塞地获取锁,如下代码所示:
if (lock.tryLock()) {
try {
// 执行需要同步的代码
} finally {
lock.unlock();
}
} else {
// 处理获取锁失败的情况
}
还可以使用lockInterruptibly()
方法实现可中断地获取锁,这在一些需要响应外部中断信号的场景中非常有用。
-
锁的类型和可重入性
synchronized
:是可重入锁,一个线程可以多次获取同一个对象的锁。例如,一个线程在一个synchronized
方法中调用另一个synchronized
方法(属于同一个对象),不会产生死锁。Lock
:ReentrantLock
也是可重入锁,并且可以通过构造函数指定是否为公平锁。公平锁保证了线程获取锁的顺序是按照请求锁的顺序来的,而非公平锁则允许插队获取锁,可能会提高一定的性能,但可能导致某些线程长时间等待。
-
性能方面(在某些情况下)
- 在低竞争场景下,
synchronized
的性能可能和Lock
差不多,因为JVM
对synchronized
进行了优化。 - 在高竞争场景下,
Lock
可能具有更好的性能,特别是当需要使用复杂的锁功能时,如超时获取锁等。
- 在低竞争场景下,
四、前端展示(使用 Vue)
我们可以创建一个简单的 Vue 组件来模拟synchronized
和Lock
的使用情况对比:
<template>
<div>
<h2>synchronized 和 Lock 对比演示</h2>
<button @click="incrementWithSynchronized">使用 synchronized 递增</button>
<button @click="incrementWithLock">使用 Lock 递增</button>
<p>synchronized 计数: {
{ synchronizedCount }}</p>
<p>Lock 计数: {
{ lockCount }}</p>
</div>
</template>
<script>
import SynchronizedExample from './SynchronizedExample.js';
import LockExample from './LockExample.js';
export default {
data() {
return {
synchronizedCount: 0,
lockCount: 0,
synchronizedObj: new SynchronizedExample(),
lockObj: new LockExample()
};
},
methods: {
incrementWithSynchronized() {
this.synchronizedObj.increment();
this.synchronizedCount = this.synchronizedObj.getCount();
},
incrementWithLock() {
this.lockObj.increment();
this.lockCount = this.lockObj.getCount();
}
}
};
</script>
<style>
/* 样式代码 */
</style>
通过这个前端界面,我们可以直观地看到在多线程模拟环境下(这里简化为通过按钮点击模拟多线程操作),synchronized
和Lock
对共享变量操作的效果。
总之,synchronized
和Lock
各有优缺点,在实际的并发编程中,需要根据具体的应用场景来选择合适的同步机制,以实现高效、安全的多线程程序。