目录
1、Synchronized和ReentrantLock的区别
1、Synchronized和ReentrantLock的区别
Synchronized 和 ReentrantLock 都是Java中用于实现线程同步的工具。
其中,Synchronized 是 Java 的一种语言级别的同步机制,使用简单,可以用于方法或代码块,它会自动获取和释放锁。因而语法较为简洁,不容易出错。
ReentrantLock 是一个在 java.util.concurrent.locks 包中提供的显式锁。它需要手动获取和释放锁,通常使用 lock() 和 unlock() 方法。但是 ReentrantLock 也提供了更多的功能,如尝试获取锁、定时锁、响应中断请求等。
此外,Synchronized 默认是非公平的,它无法保证锁的获取顺序。而 ReentrantLock 可以选择公平锁和非公平锁,使用公平锁会按照线程请求的顺序来获取锁。
2、线程协作和等待/通知机制
Synchronized中的等待和通知机制
在 Synchronized 中使用 wait() 和 notify() 方法来实现条件等待和通知机制。wait() 和 notify() 方法是 Java 中用于线程间通信的关键机制,通常与 synchronized 关键字结合使用。
以下是一个简单的示例(生产者-消费者模式),展示如何使用这两个方法。
import java.util.LinkedList;
import java.util.Queue;
/**
* 共享资源
*/
class SharedResource {
private final Queue<Integer> queue = new LinkedList<>();
/**
* 生产资源
*
* @param value
* @throws InterruptedException
*/
public synchronized void produce(int value) throws InterruptedException {
int LIMIT = 5;
while (queue.size() == LIMIT) {
wait(); // 等待,直到有空间可用
}
queue.add(value);
System.out.println("Produced: " + value);
notify(); // 通知消费者可以消费了
}
/**
* 消费资源
*
* @throws InterruptedException
*/
public synchronized void consume() throws InterruptedException {
while (queue.isEmpty()) {
wait(); // 等待,直到有数据可用
}
int value = queue.poll();
System.out.println("Consumed: " + value);
notify(); // 通知生产者可以生产了
}
}
/**
* 生产者
*/
class Producer implements Runnable {
private final SharedResource resource;
public Producer(SharedResource resource) {
this.resource = resource;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
resource.produce(i);
Thread.sleep(100); // 模拟生产过程
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
/**
* 消费者
*/
class Consumer implements Runnable {
private final SharedResource resource;
public Consumer(SharedResource resource) {
this.resource = resource;
}
@Override
public void run() {
for (int i = 0; i < 10; i++) {
try {
resource.consume();
Thread.sleep(150); // 模拟消费过程
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
public class WaitNotifyExample {
public static void main(String[] args) {
SharedResource resource = new SharedResource();
Thread producerThread = new Thread(new Producer(resource));
Thread consumerThread = new Thread(new Consumer(resource));
producerThread.start();
consumerThread.start();
}
}
wait() 和 notify() 方法在多线程编程中非常有用,能够有效地实现线程间的协作。
ReentrantLock中的等待和通知机制
ReentrantLock 相比synchronized,可以像普通对象一样使用,在ReentrantLock的等待和通知机制中,Java提供了Condition对象,从而可以更加灵活地实现此机制。
下边代码示例,通过Condition对象简单实现了 ReentrantLock 中的等待和通知机制,用法比较简单:
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ConditionExample {
//步骤一:首先,创建一个ReentrantLock实例和与之关联的Condition对象。
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
private int count = 0;
//步骤二:在某个条件不满足时,使用await()方法让当前线程等待,同时释放锁。
public void awaitCondition() {
lock.lock();
try {
while (count < 5) {
condition.await();
}
System.out.println("Count reached: " + count);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
//步骤三:在条件满足时,使用signal()或signalAll()方法通知等待的线程。
public void incrementAndSignal() {
lock.lock();
try {
count++;
System.out.println("Count incremented to: " + count);
if (count >= 5) {
condition.signalAll();
}
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ConditionExample example = new ConditionExample();
// 创建线程等待条件
new Thread(example::awaitCondition).start();
// 模拟计数增量
for (int i = 0; i < 10; i++) {
example.incrementAndSignal();
try {
Thread.sleep(500); // 模拟时间间隔
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
}
}
}
使用 ReentrantLock 和 Condition 可以实现灵活的线程同步机制,允许线程在特定条件下等待并在条件满足时被唤醒。这种机制在需要复杂的线程协作时非常有用。
3、Synchronized的底层是如何实现的?
Synchronized 其底层实现依赖于 Java 虚拟机(JVM)和操作系统的支持。
具体来说,Synchronized 代码块是由一对 monitorenter/monitorexit 指令实现的,Java 通过这些指令负责进行加锁和解锁。Monitor 对象是同步的基本实现单元。
在 Java 6 之前,Monitor 的实现完全是依靠操作系统内部的互斥锁,因为需要进行用户态到内核态的切换,所以同步操作是一个无差别的重量级操作。
现代的(Oracle)JDK 中,JVM 对此进行了改进,提供了三种不同的 Monitor 实现,也就是常说的三种不同的锁:偏斜锁(Biased Locking)、轻量级锁和重量级锁,大大改进了其性能。
4、什么是锁的升级和降级?
所谓锁的升级、降级,就是 JVM 优化 Synchronized 运行的机制,当 JVM 检测到不同的竞争状况时,会自动切换到适合的锁实现,这种切换就是锁的升级、降级。
当没有竞争出现时,默认会使用偏斜锁。JVM 会利用 CAS 操作(compare and swap),在对象头上的 Mark Word 部分设置线程 ID,以表示这个对象偏向于当前线程,所以并不涉及真正的互斥锁。这样做的假设是基于在很多应用场景中,大部分对象生命周期中最多会被一个线程锁定,使用偏斜锁可以降低无竞争开销。
如果有另外的线程试图锁定某个已经被偏斜过的对象,JVM 就需要撤销(revoke)偏斜锁,并切换到轻量级锁实现。轻量级锁依赖 CAS 操作 Mark Word 来试图获取锁,如果重试成功,就使用普通的轻量级锁;否则,进一步升级为重量级锁。
当 JVM 进入安全点(SafePoint)的时候,会检查是否有闲置的 Monitor,然后试图进行降级。
5、ReentrantLock 的实现原理
ReentrantLock 比 Synchronized 提供了更多的功能和灵活性,比如公平锁、非公平锁、可中断的锁等待等。它的实现依赖于 AQS(AbstractQueuedSynchronizer),这是 Java 实现锁和其他同步机制的核心类。
ReentrantLock 通过继承自 AbstractQueuedSynchronizer(AQS)来实现锁的语义。AQS 使用一个整数值 state 来表示锁的状态,并通过 CAS 操作(Compare-And-Swap)实现状态的原子更新。线程通过 CAS 操作来获取锁、释放锁,并在争夺锁失败时进入阻塞队列。
AQS 的工作原理总结图解:
+-------------+
acquire | | tryAcquire
+---------->| AQS Lock |<--------------+
| | | |
| +------+------| |
| | |
| acquire | |
| fail | |
| v |
| +-------------+ |
+---------->| WaitQueue | release |
+------+------+<--------------+
| |
v v
Thread block Thread unpark
至此,全文结束。