synchronized和ReentrantLock区别和联系
温馨提示:这篇文章已超过430天没有更新,请注意相关的内容是否还可用!
synchronized 和 ReentrantLock 都是 Java 中用于实现线程同步的机制,它们都可以用于控制多个线程对共享资源的访问。以下是它们的一些比较:
-
锁的获取方式:
- synchronized:通过关键字 synchronized 可以直接对代码块或方法进行加锁,Java 虚拟机会自动管理锁的获取和释放。
- ReentrantLock:通过 ReentrantLock 类的实例来获取锁,并通过 lock() 方法来加锁,unlock() 方法来释放锁。需要手动管理锁的获取和释放。
-
可重入性:
可重入性指的是同一个线程在持有锁的情况下,可以再次获取这个锁,而不会被自己持有的锁所阻塞。这个特性在并发编程中是非常重要的,因为它允许线程在调用一个已经拥有锁的同步方法时,不会因为锁的重入而发生死锁。
让我们通过一个示例来说明可重入性:
public class ReentrantExample {
private final Object lock = new Object();
public void outer() {
synchronized (lock) {
System.out.println("Outer method");
inner();
}
}
public void inner() {
synchronized (lock) {
System.out.println("Inner method");
}
}
public static void main(String[] args) {
ReentrantExample example = new ReentrantExample();
example.outer();
}
}
在这个示例中,outer() 方法和 inner() 方法都使用了同一个锁对象 lock 进行同步。当线程调用 outer() 方法时,它会获取 lock 上的锁,并执行 inner() 方法。在 inner() 方法中,由于它也在同一个锁上进行同步,所以即使是在 outer() 方法已经持有锁的情况下,也能够再次获得这个锁,而不会被阻塞。
同样的原理也适用于 ReentrantLock。当一个线程持有 ReentrantLock 锁时,它可以多次调用 lock() 方法来再次获取这个锁,而不会被自己持有的锁所阻塞。这种可重入性确保了线程在执行同步代码块时不会因为持有锁而发生死锁。
- 灵活性:
- synchronized:synchronized 关键字的使用比较简单,可以直接修饰方法或代码块,不需要显示地创建锁对象。但是它的灵活性相对较差,例如不能设置超时时间、不能中断正在等待的线程等。
- ReentrantLock:ReentrantLock 提供了比较丰富的功能,例如可以设置公平/非公平锁、可中断的获取锁、定时的获取锁等。
确实,ReentrantLock 提供了一些高级功能,包括:
公平/非公平锁: 可以通过构造函数来指定 ReentrantLock 是公平锁还是非公平锁。公平锁会按照线程的请求顺序来获取锁,而非公平锁则允许线程插队获取锁。默认情况下,ReentrantLock 是非公平的。
可中断的获取锁: ReentrantLock 提供了 lockInterruptibly() 方法,允许线程在等待锁的过程中被中断。如果一个线程在等待锁时被中断,它会收到 InterruptedException 异常。
定时的获取锁: ReentrantLock 提供了 tryLock(long time, TimeUnit unit) 方法,允许线程在指定的时间范围内尝试获取锁。如果在指定的时间内没有获取到锁,该方法会返回 false。
下面是一个示例,演示了如何使用 ReentrantLock 的这些高级功能:
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockAdvancedExample {
public static void main(String[] args) throws InterruptedException {
Lock fairLock = new ReentrantLock(true); // 创建公平锁
Lock nonFairLock = new ReentrantLock(); // 创建非公平锁
// 公平锁示例
Thread fairThread1 = new Thread(() -> {
fairLock.lock();
try {
System.out.println("Fair lock acquired by Thread 1");
Thread.sleep(1000); // 模拟占用锁的一段时间
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
fairLock.unlock();
}
});
Thread fairThread2 = new Thread(() -> {
fairLock.lock();
try {
System.out.println("Fair lock acquired by Thread 2");
} finally {
fairLock.unlock();
}
});
// 非公平锁示例
Thread nonFairThread1 = new Thread(() -> {
nonFairLock.lock();
try {
System.out.println("Non-fair lock acquired by Thread 1");
Thread.sleep(1000); // 模拟占用锁的一段时间
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
nonFairLock.unlock();
}
});
Thread nonFairThread2 = new Thread(() -> {
nonFairLock.lock();
try {
System.out.println("Non-fair lock acquired by Thread 2");
} finally {
nonFairLock.unlock();
}
});
// 启动线程
fairThread1.start();
fairThread2.start();
nonFairThread1.start();
nonFairThread2.start();
// 等待线程执行完毕
fairThread1.join();
fairThread2.join();
nonFairThread1.join();
nonFairThread2.join();
System.out.println("All threads completed.");
}
}
在这个示例中,我们创建了两个 ReentrantLock 实例,一个是公平锁,另一个是非公平锁。然后创建了两个线程分别尝试获取这两种锁,演示了公平锁和非公平锁的区别。
-
性能:
- 通常情况下,synchronized 的性能会比 ReentrantLock 好,因为它是由 JVM 内部实现的,具有较低的开销。
- 但在一些特殊情况下,ReentrantLock 的性能可能会优于 synchronized,例如需要支持公平锁、需要手动管理锁的释放等情况。
-
可读性:
- synchronized 相对于 ReentrantLock 来说,使用更简单、更直观,对于一些简单的场景,代码可读性更好。
- ReentrantLock 由于需要手动管理锁的获取和释放,可能会使得代码相对复杂一些,但也提供了更多的灵活性和控制能力。
好的,让我们通过几个示例来说明 synchronized 和 ReentrantLock 的用法和区别。
示例1: 使用 synchronized 进行线程同步
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
public static void main(String[] args) throws InterruptedException {
SynchronizedExample example = new SynchronizedExample();
Runnable task = () -> {
for (int i = 0; i
在上面的示例中,increment() 方法和 getCount() 方法都被 synchronized 修饰,这意味着在同一时间只有一个线程可以执行这两个方法中的任意一个。这样就保证了 count 的线程安全性。
示例2: 使用 ReentrantLock 进行线程同步
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private int count = 0;
private Lock lock = new ReentrantLock();
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public int getCount() {
lock.lock();
try {
return count;
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ReentrantLockExample example = new ReentrantLockExample();
Runnable task = () -> {
for (int i = 0; i
在这个示例中,我们使用了 ReentrantLock 来实现线程同步。通过调用 lock() 和 unlock() 方法来手动管理锁的获取和释放。这样就保证了在同一时间只有一个线程可以执行 increment() 和 getCount() 方法中的任意一个。
综上所述,对于大多数情况下的简单线程同步需求,可以使用 synchronized。而对于一些特殊的需求,例如需要更多的灵活性、更精细的控制、更好的性能等,可以选择使用 ReentrantLock。
