在 Java 多线程编程中,线程安全是一个核心问题。当多个线程同时访问共享资源时,可能会导致数据不一致或其他不可预期的结果。synchronized
关键字和Lock
接口是 Java 中实现线程同步的两种主要方式,本文将深入探讨它们的工作原理、使用场景及源码实现,并通过代码样例解析其线程安全机制。
线程安全是指当多个线程访问某个类时,不管运行时环境采用何种调度方式或者这些线程将如何交替执行,并且在主调代码中不需要任何额外的同步或协同,这个类都能表现出正确的行为。
Java 内存模型规定了线程之间的可见性和有序性,其核心概念包括:
synchronized
关键字可以修饰方法或代码块,确保同一时刻只有一个线程可以执行该代码:
public class SynchronizedExample {
private int count = 0;
// 同步方法
public synchronized void increment() {
count++;
}
// 同步代码块
public void decrement() {
synchronized (this) {
count--;
}
}
// 静态同步方法
public static synchronized void staticMethod() {
// ...
}
}
在 Java 中,每个对象都有一个对象头(Object Header),其中包含了 Mark Word。当对象被synchronized
修饰时,Mark Word 会存储指向 Monitor 对象的指针。
Monitor 是 Java 中实现同步的基础,它是一个对象级的同步机制,本质上是一个锁的实现。每个 Java 对象都可以关联一个 Monitor,当一个线程尝试访问被synchronized
修饰的代码块时,它必须先获得该对象的 Monitor。
通过javap -v
命令查看编译后的字节码,可以看到synchronized
代码块使用monitorenter
和monitorexit
指令实现:
public void decrement();
descriptor: ()V
flags: ACC_PUBLIC
Code:
stack=2, locals=3, args_size=1
0: aload_0
1: dup
2: astore_1
3: monitorenter // 进入同步块
4: aload_0
5: dup
6: getfield #2 // Field count:I
9: iconst_1
10: isub
11: putfield #2 // Field count:I
14: aload_1
15: monitorexit // 正常退出同步块
16: goto 24
19: astore_2
20: aload_1
21: monitorexit // 异常退出同步块
22: aload_2
23: athrow
24: return
在 JDK 1.6 之前,synchronized 是一个重量级锁,性能较低。JDK 1.6 引入了锁升级机制,优化了 synchronized 的性能:
public interface Lock {
void lock(); // 获取锁
void lockInterruptibly() throws InterruptedException; // 可中断获取锁
boolean tryLock(); // 尝试非阻塞获取锁
boolean tryLock(long time, TimeUnit unit) throws InterruptedException; // 超时获取锁
void unlock(); // 释放锁
Condition newCondition(); // 获取等待通知组件
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class LockExample {
private final Lock lock = new ReentrantLock();
private int count = 0;
public void increment() {
lock.lock();
try {
count++;
} finally {
lock.unlock();
}
}
public void decrement() {
lock.lock();
try {
count--;
} finally {
lock.unlock();
}
}
}
ReentrantLock 的核心是基于 AQS(AbstractQueuedSynchronizer)实现的。AQS 是一个用于构建锁和同步器的框架,它使用一个整型的 state 变量来表示锁的状态,并维护一个 FIFO 队列来管理等待线程。
public class ReentrantLock implements Lock, java.io.Serializable {
private final Sync sync;
abstract static class Sync extends AbstractQueuedSynchronizer {
// ...
}
static final class NonfairSync extends Sync {
// 非公平锁实现
}
static final class FairSync extends Sync {
// 公平锁实现
}
}
ReentrantLock 支持公平锁和非公平锁两种模式:
// 创建公平锁
Lock fairLock = new ReentrantLock(true);
// 创建非公平锁(默认)
Lock nonfairLock = new ReentrantLock();
以非公平锁为例,lock () 方法的实现:
final void lock() {
if (compareAndSetState(0, 1))
setExclusiveOwnerThread(Thread.currentThread());
else
acquire(1);
}
public final void acquire(int arg) {
if (!tryAcquire(arg) &&
acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
selfInterrupt();
}
protected final boolean tryAcquire(int acquires) {
return nonfairTryAcquire(acquires);
}
final boolean nonfairTryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0) // overflow
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
ReentrantLock 的可重入性通过 state 变量实现:当同一个线程再次获取锁时,state 值递增;释放锁时,state 值递减。当 state 值为 0 时,表示锁已完全释放。
protected final boolean tryRelease(int releases) {
int c = getState() - releases;
if (Thread.currentThread() != getExclusiveOwnerThread())
throw new IllegalMonitorStateException();
boolean free = false;
if (c == 0) {
free = true;
setExclusiveOwnerThread(null);
}
setState(c);
return free;
}
特性 | synchronized | Lock |
---|---|---|
语法 | 内置语言关键字 | 接口,需要显式调用 lock () 和 unlock () |
锁的获取 | 自动获取和释放 | 手动获取和释放,必须在 finally 中释放 |
可中断性 | 不可中断 | 可中断(lockInterruptibly ()) |
公平性 | 非公平 | 可选择公平或非公平 |
锁的状态 | 无法判断 | 可以判断(isLocked ()) |
条件变量 | 单一条件变量 | 可以创建多个条件变量 |
性能 | JDK 1.6 后优化,轻量级锁性能接近 Lock | 高并发场景下性能更优 |
public class BankAccount {
private double balance;
public BankAccount(double balance) {
this.balance = balance;
}
// 同步方法实现线程安全
public synchronized void deposit(double amount) {
balance += amount;
}
// 同步方法实现线程安全
public synchronized void withdraw(double amount) {
if (balance >= amount) {
balance -= amount;
} else {
throw new IllegalArgumentException("余额不足");
}
}
public synchronized double getBalance() {
return balance;
}
}
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class BankAccount {
private double balance;
private final Lock lock = new ReentrantLock();
public BankAccount(double balance) {
this.balance = balance;
}
public void deposit(double amount) {
lock.lock();
try {
balance += amount;
} finally {
lock.unlock();
}
}
public void withdraw(double amount) {
lock.lock();
try {
if (balance >= amount) {
balance -= amount;
} else {
throw new IllegalArgumentException("余额不足");
}
} finally {
lock.unlock();
}
}
public double getBalance() {
lock.lock();
try {
return balance;
} finally {
lock.unlock();
}
}
}
通过synchronized
和Lock
,Java 提供了强大而灵活的线程同步机制,开发者可以根据具体场景选择合适的同步方式,确保多线程程序的安全性和性能。