java:使用域变量实现线程同步(附带源码)
一、项目背景详细介绍
在多线程编程中,可见性和有序性(禁止指令重排序)是保证线程安全的两大挑战。Java 提供了 volatile 关键字,用于标记共享变量,使得对该变量的读写:
-
可见性保证:一个线程修改了
volatile变量的值,其他线程能立即读取到最新值。 -
禁止重排序:对
volatile变量的读写操作前后的指令,不会被编译器或 CPU 重排序,形成“内存屏障”。
相比于重量级的锁(synchronized、ReentrantLock),volatile 具有更小的开销,适合用于:
-
实现状态标志(flag),通知其他线程开始或结束某项任务;
-
实现简单的累加器或双重检查锁定(DCL)中的安全发布;
-
实现生产者—消费者场景下的无阻塞可见性通信。
本项目旨在通过示例演示如何使用 volatile 关键字实现线程同步与通信,展示其使用场景、局限性以及注意事项。
二、项目需求详细介绍
-
可见性标志
-
使用
volatile boolean running控制后台线程的启动与停止; -
主线程修改
running=false,后台线程能立即跳出循环并结束。
-
-
双重检查锁定(DCL)单例
-
使用
volatile修饰单例实例,结合双重检查锁定,确保线程安全懒加载; -
示范
getInstance()方法在高并发下的正确性与性能。
-
-
无锁状态机
-
使用
volatile int state或enum类型,在线程间切换状态并驱动不同操作分支; -
示例:工作线程根据
state值执行STATE_IDLE、STATE_BUSY等逻辑。
-
-
生产者—消费者(循环缓冲区)
-
基于固定长度的数组和两个
volatile指针head、tail实现无锁环形队列; -
仅通过读写
volatile变量保证可见性,演示单生产者单消费者(SPSC)场景。
-
-
注意事项
-
仅能保证对单个变量的原子读写,不适用于复合操作(如
count++); -
对于需要复合原子性场景,仍需使用原子变量或锁。
-
三、相关技术详细介绍
-
volatile关键字-
在 Java 内存模型(JMM)中,
volatile变量的写操作会立刻刷新到主内存,读操作会从主内存获取; -
编译器和 CPU 在
volatile读写前后插入内存屏障,禁止指令重排序。
-
-
双重检查锁定(DCL)
-
经典单例实现模式,结合
volatile可保证实例安全发布; -
写法示例:
-
if (instance == null) {
synchronized (Class.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
-
无锁队列原理
-
基于循环数组、
volatile指针以及 CAS 或有序写入,适用于 SPSC 场景; -
需防止指令重排序带来的写入先读后等问题。
-
-
内存屏障与重排序
-
volatile写相当于“StoreStore + StoreLoad 屏障”,volatile读相当于“LoadLoad + LoadStore 屏障”; -
保证了可见性和顺序性,但并不保证复合操作的原子性。
-
四、实现思路详细介绍
-
停止标志示例
-
定义
class Worker implements Runnable:-
内部
private volatile boolean running = true; -
run()方法中while (running) { /* do work */ } -
提供
stop()方法设置running = false,实现安全停止。
-
-
-
DCL 单例
-
定义
class Singleton:-
private static volatile Singleton instance; -
public static Singleton getInstance()实现双重检查锁定。
-
-
-
状态枚举示例
-
定义
volatile State state,在多个线程中根据不同state进行切换和逻辑分支。
-
-
环形缓冲区(SPSC)
-
定义固定长度数组
T[] buffer; -
private volatile int head, tail; -
produce(T item):buffer[tail] = item; tail = (tail + 1) % capacity; -
consume():item = buffer[head]; head = (head + 1) % capacity; -
适用于单生产者单消费者,无需锁。
-
/*
* =====================================================
* File: Worker.java
* 示例1:使用 volatile boolean 作为停止标志的后台线程
* =====================================================
*/
package com.example.volatilesync;
public class Worker implements Runnable {
// 运行标志,volatile 保证可见性
private volatile boolean running = true;
@Override
public void run() {
System.out.printf("%s 启动%n", Thread.currentThread().getName());
while (running) {
// 模拟工作
try {
Thread.sleep(200);
System.out.printf("%s 正在工作%n", Thread.currentThread().getName());
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
}
}
System.out.printf("%s 停止%n", Thread.currentThread().getName());
}
// 安全停止线程
public void stop() {
running = false;
}
}
/*
* =====================================================
* File: Singleton.java
* 示例2:双重检查锁定的线程安全单例
* =====================================================
*/
package com.example.volatilesync;
public class Singleton {
// volatile 保证 instance 发布时的可见性和禁止重排序
private static volatile Singleton instance;
private Singleton() {
// 防止反射攻击
if (instance != null) throw new IllegalStateException("Already initialized");
}
public static Singleton getInstance() {
if (instance == null) {
synchronized (Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
/*
* =====================================================
* File: StateMachine.java
* 示例3:使用 volatile 枚举状态驱动无锁状态机
* =====================================================
*/
package com.example.volatilesync;
public class StateMachine {
public enum State { IDLE, BUSY, PAUSED }
// volatile 确保状态切换的可见性
private volatile State state = State.IDLE;
public void setState(State newState) {
System.out.printf("状态从 %s 切换到 %s%n", state, newState);
state = newState;
}
public void doWork() {
while (state != State.PAUSED) {
if (state == State.BUSY) {
System.out.println("处理业务...");
} else {
System.out.println("空闲等待...");
}
try { Thread.sleep(200); } catch (InterruptedException e) { break; }
}
System.out.println("状态机暂停");
}
}
/*
* =====================================================
* File: SpscRingBuffer.java
* 示例4:单生产者单消费者无锁环形队列(SPSC),基于 volatile head/tail
* =====================================================
*/
package com.example.volatilesync;
@SuppressWarnings("unchecked")
public class SpscRingBuffer<T> {
private final T[] buffer;
private final int capacity;
// volatile 指针保证可见性和有序性
private volatile int head = 0;
private volatile int tail = 0;
public SpscRingBuffer(int capacity) {
this.capacity = capacity + 1; // 留一个空位区分满/空
this.buffer = (T[]) new Object[this.capacity];
}
// 生产者写入,失败返回 false
public boolean offer(T item) {
int next = (tail + 1) % capacity;
if (next == head) {
return false; // 队满
}
buffer[tail] = item;
tail = next; // volatile 写
return true;
}
// 消费者读取,队空返回 null
public T poll() {
if (head == tail) {
return null; // 队空
}
T item = buffer[head];
buffer[head] = null;
head = (head + 1) % capacity; // volatile 写
return item;
}
public boolean isEmpty() {
return head == tail;
}
}
/*
* =====================================================
* File: VolatileSyncDemo.java
* 主类:演示所有示例的运行
* =====================================================
*/
package com.example.volatilesync;
import java.util.concurrent.*;
public class VolatileSyncDemo {
public static void main(String[] args) throws InterruptedException {
System.out.println("=== Worker 示例 ===");
Worker worker = new Worker();
Thread wThread = new Thread(worker, "Worker-Thread");
wThread.start();
Thread.sleep(800);
worker.stop();
wThread.join();
System.out.println("\n=== Singleton DCL 示例 ===");
ExecutorService exec = Executors.newFixedThreadPool(2);
Callable<Singleton> task = Singleton::getInstance;
Future<Singleton> f1 = exec.submit(task);
Future<Singleton> f2 = exec.submit(task);
System.out.println("实例1: " + f1.get());
System.out.println("实例2: " + f2.get());
exec.shutdown();
System.out.println("\n=== StateMachine 示例 ===");
StateMachine sm = new StateMachine();
Thread smThread = new Thread(sm::doWork, "StateMachine-Thread");
smThread.start();
Thread.sleep(500);
sm.setState(StateMachine.State.BUSY);
Thread.sleep(500);
sm.setState(StateMachine.State.PAUSED);
smThread.join();
System.out.println("\n=== SPSC RingBuffer 示例 ===");
SpscRingBuffer<Integer> rb = new SpscRingBuffer<>(4);
Thread producer = new Thread(() -> {
for (int i = 1; i <= 6; i++) {
while (!rb.offer(i)) {
// 队满时自旋
}
System.out.println("生产: " + i);
}
}, "SPSC-Producer");
Thread consumer = new Thread(() -> {
for (int i = 1; i <= 6; i++) {
Integer v;
while ((v = rb.poll()) == null) {
// 队空时自旋
}
System.out.println("消费: " + v);
}
}, "SPSC-Consumer");
producer.start();
consumer.start();
producer.join();
consumer.join();
}
}
代码详细解读
-
Worker
-
volatile boolean running作为停止标志; -
run()中循环检查running,在主线程调用stop()后立即可见并退出。
-
-
Singleton
-
private static volatile Singleton instance,双重检查锁定(DCL)确保线程安全懒加载; -
volatile防止instance的重排序或部分构造可见。
-
-
StateMachine
-
volatile State state提供线程间状态切换的可见性; -
doWork()根据当前state执行不同逻辑,及时响应外部调用setState()。
-
-
SpscRingBuffer
-
单生产者单消费者模型,仅使用
volatilehead/tail索引保证读写的可见性与有序性; -
offer()和poll()分别自旋进行非阻塞读写。
-
-
VolatileSyncDemo
-
演示上述四种示例的运行流程,包含启动、操作和停止,并打印结果。
-
项目详细总结
本项目通过四个典型示例展示了 volatile 在 Java 并发编程中的核心作用:
-
状态标志:使用
volatile boolean安全地控制线程生命周期。 -
双重检查锁定:结合
volatile实现高效、线程安全的延迟单例。 -
状态机驱动:
volatile枚举状态保证不同线程对状态切换的及时可见。 -
无锁队列:SPSC 场景下仅用
volatile索引即可实现轻量级、无锁的环形缓冲区。
volatile 提供了低成本的可见性和有序性保障,但仅限于单变量的读写操作,无法满足复合操作的原子性需求。复杂场景仍需结合原子变量类或锁机制。
项目常见问题及解答
-
volatile能否替代锁?
只能替代对单个变量的同步,不支持复合操作的原子性与互斥。 -
为什么 DCL 需要
volatile?
防止对象构造过程与引用赋值的重排序,确保线程看到的instance已完全构造。 -
SPSC 队列可否用于多生产者多消费者?
不能,因缺乏互斥与原子复合操作;MPMC 场景需更复杂的 CAS 或锁。 -
自旋等待会不会 CPU 占用过高?
会,在高延迟或容量紧张场景下应引入Thread.yield()或阻塞策略。 -
volatile与AtomicInteger有何区别?volatile仅保证读写可见性;AtomicInteger除可见性外,还提供原子复合操作(如incrementAndGet())。
扩展方向与性能优化
-
结合 CAS 实现无锁多生产者多消费者队列
使用AtomicReferenceArray或VarHandle构建 MPMC 队列。 -
结合
LongAdder/DoubleAdder优化高并发累加
在热点写入场景下优于AtomicLong。 -
动态调整自旋/阻塞策略
根据负载和延迟动态切换自旋或阻塞,平衡吞吐与延迟。 -
使用
VarHandle替代volatile
Java 9+ 提供更灵活的内存屏障和访问模式(如getAcquire/setRelease)。 -
无锁状态机框架化
基于volatile状态和事件驱动,实现通用工作流或任务状态管理框架。
更多推荐
所有评论(0)