详细使用说明请看Java 多线程之 volatile。
提供可见性,保证有序性。虽然提供了可见性和有序性的保证,但它并不能保证原子性。对于复合操作,如递增或递减操作,仍然需要使用其他机制,如锁或原子类来保证原子性。
使用方法
volatile int count1 = 1;
private volatile int count2 = 2;
volatile boolean flag1 = false;
private volatile boolean flag2 = false;
详细使用说明请看Java 多线程之 synchronized。
synchronized 提供了一种简单而强大的机制来控制多个线程之间的并发访问,确保共享资源的安全性和一致性。它解决了多线程环境中的竞态条件、数据竞争和内存模型等问题,是实现线程安全的重要手段之一。
作用在代码上,相当于给代码块加锁(Lock)
public void performTask() {
// synchronized 作用于代码块
synchronized (lock) {
// 业务逻辑,同步代码块,对共享资源进行操作
}
}
作用在方法上,相当于给整个方法加锁(Lock)
// synchronized 作用在方法上
public synchronized void increment() {
// 业务逻辑,同步代码块,对共享资源进行操作
}
详细使用说明请看Java 多线程之 DCL。
DCL 是 Double-Checked Locking 的缩写,是一种用于在多线程环境下延迟初始化对象的技术。它的目标是在保持高性能的同时,确保只有一个线程执行对象的初始化过程。
DCL 的实现通常基于以下步骤:
使用方法如下,Singleton 是一个在高并发下,多线要使用的延迟初始化单例类
public class Singleton {
private static volatile Singleton instance;
private Singleton() {
// 私有构造函数
}
public static Singleton getInstance() {
if (instance == null) { // 1.检查对象是否已经被创建
synchronized (Singleton.class) { // 2.尝试获取锁
if (instance == null) { // 3.再次检查对象是否已经被创建
instance = new Singleton();// 4.如果对象尚未创建,则进行对象的创建和初始化
}
}// 5.释放锁(synchronized 语句块结束自动释放锁)
}
return instance; // 6.返回对象
}
}
需要注意的是 Singleton instance 对象的定义需要使用 volatile 关键字。
详细使用说明请看Java 多线程之 CAS 。
实现无锁优化,是自旋锁/乐观锁的实现方式。
CAS 是 Compare and Set(比较并设置)的缩写,是一种并发算法,用于实现多线程环境下的原子操作。
CAS 操作涉及三个操作数:内存位置(或称为变量的值)、期望值和新值。它的执行过程是:将内存位置的当前值与期望值进行比较。如果相等,则将新值写入内存位置;如果不相等,则说明其他线程已经修改了内存位置的值,操作失败。
CAS只能检测到预期值是否相等,无法感知到变量值的修改过程中是否发生了其他的并发修改,可能会引发ABA问题。
java.util.concurrent.atomic.Atomic* 开头的类都用 CAS 实现无锁优化,因此在多线程环境中能用这些类就尽量不用悲观锁相关类。
AtomicBoolean atomicBoolean = new AtomicBoolean();
AtomicInteger atomicInteger = new AtomicInteger();
AtomicLong atomicLong = new AtomicLong();
AtomicIntegerArray atomicIntegerArray = new AtomicIntegerArray(new int[5]);
AtomicLongArray atomicLongArray = new AtomicLongArray(new long[5]);
//AtomicIntegerFieldUpdater atomicIntegerFieldUpdater = new AtomicIntegerFieldUpdater();
// 等等 ...
详细使用说明请看Java 多线程之 ReentrantLock。
ReentrantLock 是一个可重入锁,与 synchronized 关键字相比,ReentrantLock 提供了更灵活、更强大的功能,同时也更复杂。
公平锁/非公平锁
直接使用 ReentrantLock 的 lock 和 unlcok 方法,基本功能同 synchronized 关键字。但是他比 synchronized 强大的地方在于他可以设置为公平锁和非公平锁,以及使用可中断获取锁和超时获取锁方法。
class XXXXXX {
private ReentrantLock lock = new ReentrantLock(true); // 公平锁
//private ReentrantLock lock = new ReentrantLock(false); // 非公平锁
public void increment() {
lock.lock(); // 获取锁
// lock.lockInterruptibly(); // 获取可中断的锁
// lock.tryLock(3000, TimeUnit.SECONDS);// 在指时间内获取锁
try {
// 业务逻辑
} finally {
lock.unlock();// 释放锁
}
}
}
条件变量 (Condition)
ReentrantLock 的条件变量(Condition)的使用,实现线程等待/通知机制。
class BoundedQueue<T> {
private Queue<T> queue = new LinkedList<>();
private int capacity;
private ReentrantLock lock = new ReentrantLock();
private Condition notFull = lock.newCondition();
private Condition notEmpty = lock.newCondition();
public BoundedQueue(int capacity) {
this.capacity = capacity;
}
public void enqueue(T item) throws InterruptedException {
lock.lock();
try {
while (queue.size() == capacity) {
// 条件1达到, 业务逻辑1等待
notFull.await();
}
queue.add(item);
// 通知业务逻辑2执行
notEmpty.signalAll();
} finally {
lock.unlock();
}
}
public T dequeue() throws InterruptedException {
lock.lock();
try {
while (queue.isEmpty()) {
// 条件2达到,业务逻辑2等等
notEmpty.await();
}
T item = queue.poll();
// 通知业务逻辑1执行
notFull.signalAll();
return item;
} finally {
lock.unlock();
}
}
}
详细使用说明请看Java 多线程之 ReentrantReadWriteLock。
ReentrantReadWriteLock 是Java中提供的一种读写锁实现,它允许多个线程同时读取共享资源,但在写操作时需要独占访问。它是对传统互斥锁的一种改进,可以提高并发性能。
读写锁的主要目的是在读多写少的场景下,提供更高的并发性能。当多个线程只需读取共享资源时,可以同时获得读锁,从而实现并发读取。而当有线程需要对共享资源进行写操作时,它必须独占地获取写锁,在此期间,其他线程无法获取读锁或写锁,从而确保数据的一致性和完整性。
获取读锁
rwLock.readLock().lock();
try {
// 访问共享资源的读操作
} finally {
rwLock.readLock().unlock();
}
获取写锁
rwLock.writeLock().lock();
try {
// 访问共享资源的写操作
} finally {
rwLock.writeLock().unlock();
}
详细使用说明请看Java 多线程之 CountDownLatch。
CountDownLatch 是Java中提供的一种同步工具类,用于控制多个线程之间的执行顺序和协调。
CountDownLatch 通过一个计数器来实现,该计数器初始化为一个正整数,表示需要等待的线程数目。每个线程执行完一定的任务后,会调用countDown()
方法将计数器减1。当计数器减到0时,表示所有线程已经完成任务,等待在await()
方法处的线程被唤醒,继续执行后续操作。
使用方法
int threadCount = 3;
CountDownLatch latch = new CountDownLatch(threadCount);
for (int i = 0; i < threadCount; i++) {
// 启动一些子线程来执行任务
Thread thread = new Thread(() -> {
// 子线程执行任务
System.out.println("执行业务逻辑");
// 子线程任务完成,计数器减1
latch.countDown();
});
thread.start();
}
// 主线程 等待所有线程完成任务
latch.await();
// 所有子线程完成任务后 主线程继续执行后续操作
System.out.println("执行后续业务");
详细使用说明请看Java 多线程之 CyclicBarrier。
CyclicBarrier(循环屏障)是Java并发编程中的一种同步辅助工具。它允许一组线程相互等待,直到所有线程都到达一个共同的屏障点,然后继续执行后续操作。CyclicBarrier可以用于解决多线程任务的协调和同步问题。
CyclicBarrier 的主要作用是使多个线程在某个点上进行同步,等待所有线程都到达该点后再一起继续执行。
使用方法
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;
public class CyclicBarrierExample {
private static final int THREAD_COUNT = 3;
public static void main(String[] args) {
CyclicBarrier barrier = new CyclicBarrier(THREAD_COUNT, () -> {
// 执行屏障动作(最后一个到达的线程执行该动作)。
// 特别说明:能执行到这里说明,规定数量的线程(THREAD_COUNT)都执行了 barrier.await();
});
for (int i = 0; i < THREAD_COUNT; i++) {
Thread thread = new Thread(() -> {
try {
// 执行业务逻辑
barrier.await(); // 线程到达屏障点,等待其他线程
// 所以线程都到达屏障点,并且已经执行屏障作用后,才会执行这里的代码
} catch (InterruptedException | BrokenBarrierException e) {
e.printStackTrace();
}
});
thread.start();
}
}
}
详细使用说明请看Java 多线程之 Phaser。
Phaser 也是Java并发编程中的一种同步辅助工具,用于线程之间的协调和同步。它提供了比CyclicBarrier和CountDownLatch更灵活和强大的功能,可以用于更复杂的多线程协作场景。
Phaser的主要用途是将多个线程分为多个阶段,并在每个阶段进行同步。每个线程可以独立运行,但在特定的阶段需要等待其他线程到达屏障点。Phaser可以动态地适应线程的注册和注销,可以处理可变数量的参与者。
使用方法
import java.util.concurrent.Phaser;
public class MultiPhaseTask {
private static final int NUM_THREADS = 3;
public static void main(String[] args) {
// 注册线程可以通过构造函数的参数指定,也可以通过 phaser.register(); 方法注册线程。
Phaser phaser = new Phaser(NUM_THREADS) {
@Override
protected boolean onAdvance(int phase, int registeredParties) {
System.out.println("第" + phase + "阶段完成");
return false; // 返回 true 会终止 phaser
}
};
for (int i = 0; i < NUM_THREADS; i++) {
// phaser.register(); // 如果上面造函数的参数没有指定,则启用这句话来注册线程
Thread thread = new Thread(() -> {
// 使用 for 循环来模拟3个阶段
for (int phase = 1; phase <= 3; phase++) {
// 这是第 phase 阶段
System.out.println("执行第 phase 阶段 任务...");
phaser.arriveAndAwaitAdvance(); // 等待其他线程到达屏障点
}
System.out.println("完成所有阶段");
// 解除线程的注册
phaser.arriveAndDeregister();
});
thread.start();
}
}
}
详细使用说明请看Java 多线程之 StampedLock。
StampedLock是Java 8引入的一种读写锁的实现,它提供了一种乐观的读锁(Optimistic Read Lock)和悲观的读锁(Pessimistic Read Lock),和写锁(Write Lock),以及对读-写冲突的解决方案。StampedLock的设计目标是在读多写少的场景下提供更高的并发性能。与传统的读写锁相比,StampedLock更加灵活和高效。
与前面的 ReentrantReadWriteLock 相比,StampedLock 在某些情况下可以提供更高的性能,但并不是在所有情况下都表现更好。StampedLock 的优势主要表现在支持乐观读取机制和条件等待。所以在写操作频繁而读操作较少和存在大量的锁竞争的情况下,直接使用悲观读锁,性能跟ReentrantReadWriteLock是相差不大的。
使用方法
import java.util.concurrent.locks.StampedLock;
public class StampedLockExample {
private Object SharedData;
private final StampedLock lock = new StampedLock();
public void write(Object obj) {
//获取写锁
long stamp = lock.writeLock();
try {
// 执行写业务逻辑,如下
SharedData = obj;
} finally {
// 释放写锁
lock.unlockWrite(stamp);
}
}
// 完全使用乐观读锁
public Object read1() {
while(true){
Object result = null;
// 获取乐观读锁
long stamp = lock.tryOptimisticRead();
// 执行读业务逻辑,如下
result = SharedData;
// 验证共享数据是否被修改,如果没有被修改过,则直接返回。否则重新读
if (lock.validate(stamp)) {
return result;
}
}
}
// 使用乐观读锁 + 非观读锁
public Object read2() {
Object result = null;
// 获取乐观读锁
long stamp = lock.tryOptimisticRead();
// 执行读业务逻辑(这里先读一次),如下
result = SharedData;
// 验证共享数据是否被修改
if (lock.validate(stamp)) {
// 如果共享数据没有被修改过,则直接使用
return result;
}
// 获取悲观读锁
stamp = lock.readLock();
try {
// 执行读业务逻辑,重新读
result = SharedData;
} finally {
// 释放写锁
lock.unlockRead(stamp);
}
return result;
}
}
详细使用说明请看Java 多线程之 Semaphore。
Semaphore(信号量)是一种并发控制机制,用于控制对共享资源的访问。它维护了一个计数器,可以限制同时访问某个资源的线程数量。常用于限制同时访问某个资源的线程数量,例如控制数据库连接池的并发访问、控制线程池的并发任务数、生产者-消费者问题、读者-写者问题等。
使用方法
public class SemaphoreExample {
private Semaphore semaphore = new Semaphore(5); // 允许同时访问资源的线程数量,这里设置为5,表示可以有5个线程同时访问
public void accessResource() {
try {
semaphore.acquire(); // 获取许可证,如果有许可证,则计数器减1;如果没有可用许可证,则阻塞
// 访问共享资源的代码
} catch (InterruptedException e) {
// 处理中断异常
} finally {
semaphore.release(); // 释放资源,计数器加1
}
}
}
详细使用说明请看Java 多线程之 Exchanger。
Exchanger(交换器)是Java并发包中的一个工具类,用于两个线程之间交换数据。它提供了一个同步点,当两个线程都到达该点时,它们可以交换数据,并且在交换完成后继续执行。
使用方法:
import java.util.concurrent.Exchanger;
public class ExchangerExample {
private Exchanger<String> exchanger = new Exchanger<>();
// 线程1
public void thread1() {
try {
// 执行线程1的业务逻辑
String data1 = "线程1的业务数据";
String exchangedData = exchanger.exchange(data1);
// exchangedData 是线程2的业务数据,在这里可以继续处理交换后的数据
// 执行线程1的业务逻辑
} catch (InterruptedException e) {
}
}
// 线程2
public void thread2() {
try {
// 执行线程2的业务逻辑
String data2 = "线程2的业务数据";
String exchangedData = exchanger.exchange(data2);
// exchangedData 是线程1的业务数据,在这里可以继续处理交换后的数据
// 执行线程2的业务逻辑
} catch (InterruptedException e) {
}
}
}
详细使用说明请看Java 多线程之 LockSupport。
LockSupport 是Java并发包中的一个工具类,用于线程的阻塞和唤醒。它提供了一种基于线程的许可(permit)的方式来实现线程的阻塞和唤醒,而不需要显式地使用锁。例如某个条件满足后阻塞线程,然后等待某个条件满足后再继续执行、实现线程间的协作等。
使用方法
import java.util.concurrent.locks.LockSupport;
public class LockSupportExample {
public static void main(String[] args) {
Thread thread1 = new Thread(() -> {
// 执行业务逻辑
LockSupport.park(); // 阻塞当前线程
// 继续执行业务逻辑
});
thread1.start();
try {
Thread.sleep(2000); // 等待2秒钟
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.unpark(thread1); // 唤醒 thread1 线程
}
}