在Java并发编程中,volatile和synchronized是两个至关重要的关键字,它们帮助开发者解决多线程环境下的可见性、原子性和有序性问题。
一. 简介
1. Volatile
volatile是一个类型修饰符,用来确保对一个变量的读取和写入操作不会被编译器重排序,并且每次读取都直接从主内存中获取最新值,写入时也直接写入主内存,从而保证了多线程环境下的可见性。但需要注意的是,volatile不能保证复合操作(如count++)的原子性。
volatile关键字用于确保变量的可见性(visibility)和有序性(ordering),但不保证原子性(atomicity)。
2. Synchronized
synchronized关键字可以用于方法或代码块,它提供了一种锁机制,确保同一时间只有一个线程可以执行特定的代码段,从而达到线程互斥的效果。synchronized不仅保证了可见性,还确保了操作的原子性以及有序性,是更强大的并发控制工具。
二、用法
1. Volatile 的使用
public class VolatileExample {
private volatile int count = 0;
public void increment() {
count++;
}
public int getCount() {
return count;
}
}
在这个例子中,虽然count使用了volatile,但increment()方法并不是线程安全的,因为count++不是原子操作。然而,getCount()能保证读取到最新的count值。
2 Synchronized 的使用
- 方法级别
public class SynchronizedExample {
private int count = 0;
public synchronized void increment() {
count++;
}
public synchronized int getCount() {
return count;
}
}
- 代码块级别
public class SynchronizedBlockExample {
private int count = 0;
private Object lock = new Object();
public void increment() {
synchronized (lock) {
count++;
}
}
public int getCount() {
synchronized (lock) {
return count;
}
}
}
三、常用案例实例
- Volatile 示例:确保一个标志的可见性
public class VolatileExample {
// 使用volatile关键字确保变量的可见性
private volatile boolean running = true;
public void start() {
new Thread(() -> {
while (running) {
// 模拟一些工作
System.out.println("Thread is running...");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println("Thread is stopping...");
}).start();
}
public void stop() {
running = false; // 当running被设置为false时,其他线程会立即看到这个改变
}
public static void main(String[] args) {
VolatileExample example = new VolatileExample();
example.start();
// 模拟一段时间后停止线程
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
example.stop(); // 设置running为false,期望线程停止
}
}
- Synchronized 解决并发修改问题
考虑一个经典的银行账户转账问题:
public class BankAccount {
private double balance;
public synchronized void deposit(double amount) {
balance += amount;
}
public synchronized void withdraw(double amount) {
if (amount <= balance) {
balance -= amount;
} else {
System.out.println("Insufficient balance.");
}
}
public synchronized double getBalance() {
return balance;
}
}
在这个例子中,通过synchronized关键字确保了存取款操作的原子性和一致性。