synchronized
关键字是 Java 中用于实现线程同步的一种机制。它能够保证在同一时刻只有一个线程可以执行被 synchronized
修饰的代码,从而解决多线程访问共享资源时的线程安全问题。以下是对 synchronized
关键字的详细解释:
基本概念
synchronized
可以用在方法上或代码块中,分别称为同步方法和同步代码块。它通过获取对象的监视器(Monitor)锁来实现互斥访问。
使用示例
-
同步方法: 在实例方法前加上
synchronized
关键字,这样该方法在同一时间只能被一个线程访问。public synchronized void syncMethod() { // 线程安全的代码 }
-
同步代码块: 在代码块前加上
synchronize
关键字,并指定一个对象作为锁,这样可以更精细地控制同步范围。public void syncBlock() { synchronized (this) { // 线程安全的代码 } }
实现原理
Synchronize
的实现依赖于对象头中的监视器(Monitor),并通过以下机制保证线程安全:
-
对象头: 每个 Java 对象都有一个对象头,其中包含了锁状态、对象标识、GC 标记等信息。对象头是 Monitor 的基础,当线程进入同步代码块时,会尝试获取对象头中的锁。
-
Monitor: Monitor 是一种同步工具,用于管理线程对共享资源的访问。每个对象都关联一个 Monitor。当线程进入同步代码块时,会尝试获取 Monitor 锁,获取成功后可以进入临界区执行代码。
-
锁的实现:
- 偏向锁:在无竞争情况下,线程持有锁时不会进行同步操作,减少了加锁和解锁的开销。
- 轻量级锁:在存在竞争但不激烈的情况下,使用 CAS 操作尝试获取锁,适用于短期内会释放锁的场景。
- 重量级锁:在竞争激烈的情况下,使用操作系统的互斥量来实现锁机制,适用于长时间持有锁的场景。
类锁和对象锁
- 对象锁:使用实例方法或
synchronized (this)
来锁住当前实例对象,不同对象的实例可以并行访问。 - 类锁:使用静态方法或
synchronized (ClassName.class)
来锁住整个类,所有对象共享同一把锁。
Monitor 的实现机制
Monitor 是通过对象头中的 Mark Word 来实现的。当一个线程进入同步块或方法时,会尝试获取 Monitor 的所有权,如果获取成功,则可以进入临界区执行代码。如果获取失败,则进入等待队列,直到锁被释放。
ReentrantLock
是 Java 中的一种显式锁(Explicit Lock),它提供了比 synchronized
关键字更灵活和强大的锁机制。它属于 java.util.concurrent.locks
包,可以显式地加锁和解锁,并提供了可重入、可中断、公平与非公平锁等特性。
基本使用
ReentrantLock
的基本使用方式如下:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void performTask() {
lock.lock(); // 获取锁
try {
// 临界区代码
System.out.println(Thread.currentThread().getName() + " is performing a task.");
} finally {
lock.unlock(); // 释放锁
}
}
public static void main(String[] args) {
ReentrantLockExample example = new ReentrantLockExample();
Runnable task = example::performTask;
Thread thread1 = new Thread(task, "Thread 1");
Thread thread2 = new Thread(task, "Thread 2");
thread1.start();
thread2.start();
}
}
主要特性
-
可重入性:
ReentrantLock
是可重入的,意味着同一个线程可以多次获取同一个锁而不会发生死锁。每次获取锁后,计数器会加1,释放锁时计数器减1,当计数器为0时锁才真正释放。 -
公平锁和非公平锁:
- 公平锁:线程按照请求锁的顺序获取锁,避免线程饥饿。可以通过构造函数
new ReentrantLock(true)
创建公平锁。 - 非公平锁:线程获取锁的顺序不保证,可能会导致线程饥饿,但性能较高。默认是非公平锁,可以通过构造函数
new ReentrantLock(false)
创建非公平锁。
- 公平锁:线程按照请求锁的顺序获取锁,避免线程饥饿。可以通过构造函数
-
可中断:
ReentrantLock
支持响应中断,线程在等待锁时可以被中断。使用lockInterruptibly()
方法来获取锁。lock.lockInterruptibly();
-
条件变量:
ReentrantLock
提供了条件变量(Condition),可以实现更加复杂的等待/通知机制。通过newCondition()
方法创建条件变量。Condition condition = lock.newCondition();
公平锁和非公平锁示例
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockFairExample {
private final ReentrantLock fairLock = new ReentrantLock(true); // 公平锁
public void performTask() {
fairLock.lock();
try {
System.out.println(Thread.currentThread().getName() + " got the lock.");
} finally {
fairLock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockFairExample example = new ReentrantLockFairExample();
Runnable task = example::performTask;
for (int i = 0; i < 5; i++) {
new Thread(task, "Thread " + i).start();
}
}
}
可中断锁示例
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockInterruptibleExample {
private final ReentrantLock lock = new ReentrantLock();
public void performTask() {
try {
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName() + " is performing a task.");
} finally {
lock.unlock();
}
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getName() + " was interrupted.");
}
}
public static void main(String[] args) {
ReentrantLockInterruptibleExample example = new ReentrantLockInterruptibleExample();
Runnable task = example::performTask;
Thread thread1 = new Thread(task, "Thread 1");
Thread thread2 = new Thread(() -> {
example.performTask();
thread1.interrupt();
}, "Thread 2");
thread1.start();
thread2.start();
}
}
条件变量示例
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockConditionExample {
private final ReentrantLock lock = new ReentrantLock();
private final Condition condition = lock.newCondition();
public void awaitCondition() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " is waiting.");
condition.await();
System.out.println(Thread.currentThread().getName() + " is woken up.");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
} finally {
lock.unlock();
}
}
public void signalCondition() {
lock.lock();
try {
System.out.println(Thread.currentThread().getName() + " is signaling.");
condition.signal();
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
ReentrantLockConditionExample example = new ReentrantLockConditionExample();
Thread waiter = new Thread(example::awaitCondition, "Waiter");
Thread signaler = new Thread(example::signalCondition, "Signaler");
waiter.start();
try {
Thread.sleep(1000); // 确保 waiter 线程先执行
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
}
signaler.start();
}
}
总结
ReentrantLock
提供了比 synchronized
更灵活和强大的锁机制,包括可重入性、公平锁、非公平锁、可中断锁和条件变量等特性。它适用于需要更复杂同步控制的场景。希望这个解释对你有所帮助!如果你有更多问题或需要进一步的详细信息,请随时告诉我。
在Java中,悲观锁和乐观锁是处理并发访问时常用的两种机制。它们的主要区别在于对资源竞争的态度和处理方式。下面是对这两种锁机制的详细解释:
悲观锁(Pessimistic Lock)
特点:
- 锁定策略:假设会频繁发生冲突,每次操作前都会加锁,以防止其他线程修改数据。
- 实现方式:通过
synchronized
关键字或ReentrantLock
来实现。 - 适用场景:适用于冲突较多的场景,确保数据修改时的绝对安全。
示例:
import java.util.concurrent.locks.ReentrantLock;
public class PessimisticLockExample {
private final ReentrantLock lock = new ReentrantLock();
public void performTask() {
lock.lock(); // 获取锁
try {
// 临界区代码
System.out.println(Thread.currentThread().getName() + " is performing a task.");
} finally {
lock.unlock(); // 释放锁
}
}
public static void main(String[] args) {
PessimisticLockExample example = new PessimisticLockExample();
Runnable task = example::performTask;
Thread thread1 = new Thread(task, "Thread 1");
Thread thread2 = new Thread(task, "Thread 2");
thread1.start();
thread2.start();
}
}
乐观锁(Optimistic Lock)
特点:
- 锁定策略:假设不会频繁发生冲突,每次操作前不加锁,只在提交时检查冲突。
- 实现方式:通常通过版本号或CAS(Compare-And-Swap)操作来实现。
- 适用场景:适用于冲突较少的场景,提高并发性能。
示例:
在数据库操作中,乐观锁通常通过增加一个版本号字段来实现。在Java中,java.util.concurrent.atomic
包提供了一些原子类,可以用于实现乐观锁。
import java.util.concurrent.atomic.AtomicInteger;
public class OptimisticLockExample {
private final AtomicInteger version = new AtomicInteger(0);
public void performTask() {
int currentVersion;
int newVersion;
do {
currentVersion = version.get(); // 获取当前版本
newVersion = currentVersion + 1; // 计算新版本
} while (!version.compareAndSet(currentVersion, newVersion)); // CAS 操作
// 临界区代码
System.out.println(Thread.currentThread().getName() + " updated version to " + newVersion);
}
public static void main(String[] args) {
OptimisticLockExample example = new OptimisticLockExample();
Runnable task = example::performTask;
Thread thread1 = new Thread(task, "Thread 1");
Thread thread2 = new Thread(task, "Thread 2");
thread1.start();
thread2.start();
}
}
区别
-
冲突处理:
- 悲观锁:每次操作都加锁,防止并发修改,适用于高冲突场景。
- 乐观锁:只在提交时检查冲突,适用于低冲突场景,提高并发性能。
-
实现方式:
- 悲观锁:依赖于同步机制,如
synchronized
和ReentrantLock
。 - 乐观锁:通过版本号和CAS操作实现。
- 悲观锁:依赖于同步机制,如
-
适用场景:
- 悲观锁:数据竞争激烈,冲突频繁的场景。
- 乐观锁:数据竞争较少,冲突不频繁的场景。
希望这些解释能帮助你理解Java中的悲观锁和乐观锁。如果你有更多问题或需要进一步的详细信息,请随时告诉我!
在并发编程中,ABA问题是指在两个比较操作之间,一个变量的值从A变成B再变回A,这样导致程序误以为值没有改变,从而出现潜在的数据一致性问题。为了解决这个问题,Java提供了一些工具和方法,其中 AtomicStampedReference
和 AtomicMarkableReference
是比较常用的解决方案。
AtomicStampedReference
特点:
- 解决方案:通过使用“标记”来跟踪对象的版本。每次更新时,不仅更新对象本身,还更新一个关联的标记,从而检测到ABA问题。
- 使用场景:适用于需要跟踪版本的情况,比如节点的CAS操作。
示例:
import java.util.concurrent.atomic.AtomicStampedReference;
public class AtomicStampedReferenceExample {
public static void main(String[] args) {
String initialRef = "initial";
int initialStamp = 0;
AtomicStampedReference<String> atomicStampedRef = new AtomicStampedReference<>(initialRef, initialStamp);
int[] stampHolder = new int[1];
String ref = atomicStampedRef.get(stampHolder);
System.out.println("Initial reference: " + ref + ", stamp: " + stampHolder[0]);
boolean isUpdated = atomicStampedRef.compareAndSet(initialRef, "newRef", initialStamp, initialStamp + 1);
System.out.println("Updated: " + isUpdated);
ref = atomicStampedRef.get(stampHolder);
System.out.println("Updated reference: " + ref + ", stamp: " + stampHolder[0]);
}
}
AtomicMarkableReference
特点:
- 解决方案:通过使用一个布尔标记来标记对象的状态。每次更新时,不仅更新对象,还更新标记,从而检测到ABA问题。
- 使用场景:适用于需要标记对象状态的情况,比如链表节点是否被删除。
示例:
import java.util.concurrent.atomic.AtomicMarkableReference;
public class AtomicMarkableReferenceExample {
public static void main(String[] args) {
String initialRef = "initial";
AtomicMarkableReference<String> atomicMarkableRef = new AtomicMarkableReference<>(initialRef, false);
boolean[] markHolder = new boolean[1];
String ref = atomicMarkableRef.get(markHolder);
System.out.println("Initial reference: " + ref + ", marked: " + markHolder[0]);
boolean isUpdated = atomicMarkableRef.compareAndSet(initialRef, "newRef", false, true);
System.out.println("Updated: " + isUpdated);
ref = atomicMarkableRef.get(markHolder);
System.out.println("Updated reference: " + ref + ", marked: " + markHolder[0]);
}
}
总结
- AtomicStampedReference:通过标记来跟踪对象的版本,适用于需要版本控制的场景。
- AtomicMarkableReference:通过布尔标记来标记对象状态,适用于需要标记状态的场景。
两者都可以有效解决ABA问题,但具体使用哪种方式取决于你的应用场景。如果需要跟踪版本和变更次数,可以使用 AtomicStampedReference
;如果需要标记对象状态,可以使用 AtomicMarkableReference
。