1. 前言
在Java开发中,多线程编程是一个常见而重要的话题。随着计算机硬件的发展,多核处理器的普及以及对性能的不断追求,充分利用多线程来实现并发执行成为了提高程序效率和性能的必要手段。然而,多线程编程中存在诸多问题,如线程安全和协调等,而Java提供了一些并发工具来解决这些问题。本文将深入探讨三大并发利器:Synchronized、ReentrantLock和CAS。
2. Synchronized实现原理
Synchronized是Java中最基本也是最常用的同步机制,它的实现原理涉及到Java的对象头和监视器锁。
2.1 对象头
每个Java对象都有一个对象头,用于存储对象的元信息。对象头中包含了Mark Word和Klass Pointer等字段。其中Mark Word用于存储对象的标记信息,如锁状态、GC标记等;Klass Pointer指向对象所属的类的元数据。
2.2 监视器锁
在Synchronized中,每个对象都有一个关联的监视器锁(也称为内部锁或互斥锁)。监视器锁是基于对象的,每个对象只有一个锁。当一个线程要执行被Synchronized修饰的代码块时,首先要获取该代码块关联对象的锁,如果锁被其他线程占用,则需要等待。
Synchronized的实现是通过对象头中的Mark Word来实现的。当一个线程获取到锁时,会将对象头中的Mark Word的标记位修改为锁定状态,其他线程在尝试获取锁时会发现该标记位已被锁定,于是进入等待队列。当持有锁的线程释放锁时,会将对象头中的Mark Word还原。
2.3 示例代码
下面是一个简单的示例代码,展示了Synchronized的用法:
public class SynchronizedDemo {
private static int count = 0;
private static final Object lock = new Object();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
synchronized (lock) {
for (int i = 0; i < 100000; i++) {
count++;
}
}
});
Thread t2 = new Thread(() -> {
synchronized (lock) {
for (int i = 0; i < 100000; i++) {
count++;
}
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Count: " + count);
}
}
在上述代码中,通过Synchronized关键字保证了count的原子性操作,避免了多线程访问的竞争问题。
3. ReentrantLock的实现原理(AQS)
ReentrantLock是Java中另一个常用的同步机制,与Synchronized相比,ReentrantLock提供了更丰富的功能,如可重入、公平性等。它的实现基于AQS(AbstractQueuedSynchronizer)。
3.1 AQS简介
AQS是Java中用于实现同步器的框架,是ReentrantLock、CountDownLatch等同步工具的基础。AQS使用一个FIFO队列来管理访问同步资源的线程。
AQS核心组件包括Sync Queue(同步队列)、Condition Queue(条件队列)、状态标记等。
3.2 ReentrantLock的实现
在ReentrantLock的内部,有一个Sync类,用于实现锁获取和释放等操作。Sync类有两个子类:NonfairSync和FairSync,分别用于实现非公平锁和公平锁。
ReentrantLock的实现原理可以概括为以下几个步骤:
非公平锁和公平锁的获取:根据锁的状态和线程是否已经持有锁来判断是否能够获取锁。在非公平锁中,获取锁的线程只需要判断锁的状态和自身是否已经持有锁即可;而在公平锁中,获取锁的线程还需要判断是否存在排在自己前面的等待线程。
锁释放:当一个线程释放锁时,需要将当前持有锁的线程设置为null,并通知等待队列中的下一个线程获取锁。
同步队列(Sync Queue)和条件队列(Condition Queue)的管理:AQS使用两个队列来管理线程的等待和唤醒。同步队列用于存储等待获取锁的线程,按照FIFO的顺序进行管理。条件队列用于通过Condition接口提供的await()和signal()等方法来实现线程的等待和唤醒。
3.3 示例代码
下面是一个使用ReentrantLock实现线程安全的计数器的示例代码:
import java.util.concurrent.locks.ReentrantLock;
public class ReentrantLockDemo {
private static int count = 0;
private static final ReentrantLock lock = new ReentrantLock();
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
lock.lock();
try {
for (int i = 0; i < 100000; i++) {
count++;
}
} finally {
lock.unlock();
}
});
Thread t2 = new Thread(() -> {
lock.lock();
try {
for (int i = 0; i < 100000; i++) {
count++;
}
} finally {
lock.unlock();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Count: " + count);
}
}
在上述代码中,通过ReentrantLock关键字保证了count的原子性操作。通过lock()方法获取锁,并在finally块中使用unlock()方法释放锁,确保线程在任何情况下都能释放锁。
4. CAS的实现原理
CAS(Compare and Swap)是一种乐观锁机制,利用CPU的原子指令实现无锁并发。它的实现依赖于底层硬件的支持,并在Java中通过Unsafe类提供了相应的接口。
4.1 CAS操作步骤
CAS操作包含三个基本操作步骤:读取内存值、比较内存值与期望值、更新内存值。当期望值与内存值相同时,将新值写入内存;否则,不进行更新。
在Java中,CAS操作通常使用AtomicInteger、AtomicBoolean等原子类来实现。这些原子类内部使用了Unsafe类提供的CAS操作,保证了操作的原子性和线程安全性。
4.2 示例代码
下面是一个使用CAS实现线程安全的计数器的示例代码:
import java.util.concurrent.atomic.AtomicInteger;
public class CASDemo {
private static AtomicInteger count = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
count.incrementAndGet();
}
});
Thread t2 = new Thread(() -> {
for (int i = 0; i < 100000; i++) {
count.incrementAndGet();
}
});
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println("Count: " + count.get());
}
}
在上述代码中,使用AtomicInteger类来保证了count的原子性操作。通过incrementAndGet()方法实现自增操作,无需加锁即可保证线程安全。