【Java 并发】线程同步
线程同步
条件对象
通常线程进入临界区,却发现需要满足某一个条件后,才能继续执行,这时,就需要使用一个条件对象,来管理那些已经获得了一个锁,但是,却不做有用工作的线程。这些条件对象经常被称为条件变量(Conditional Variable)。
一个锁对象可以关联一个或者多个相关的条件对象,我们可以使用锁对象的 newCondition()
方法获得一个条件对象。
一旦一个线程调用条件对象的 await()
方法,它将进入该条件的等待集,它将一直阻塞,直到有另外的线程调用 signalAll()
方法为止。
以下面的银行转账为例,转账操作需要加锁控制,当某一个用户转账时,需要对余额条件进行判断,不满足转出金额,就需要等待有进账金额时,才能转出。
【代码实现】
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Bank {
private final double[] accounts;
private final Lock bankLock;
private final Condition sufficientFunds;
Bank(double[] accounts) {
this.accounts = accounts;
bankLock = new ReentrantLock();
sufficientFunds = bankLock.newCondition(); // 创建一个锁的条件对象
}
public void transfer(int fromUser, int toUser, double amount) throws InterruptedException {
bankLock.lock();
try {
while (accounts[fromUser] < amount) {
System.out.println(Thread.currentThread().getName() + " will be waiting");
sufficientFunds.await(); // 阻塞,直到其他线程调用 signalAll() 方法为止
System.out.println(Thread.currentThread().getName() + " is awake now");
}
System.out.println(Thread.currentThread().getName() + ": [" + fromUser + "] = " + accounts[fromUser] +
", [" + toUser + "] = " + accounts[toUser]);
accounts[fromUser] -= amount;
accounts[toUser] += amount;
System.out.println(Thread.currentThread().getName() + ": [" + fromUser + "] -> [" + toUser + "]: " + amount);
sufficientFunds.signalAll();
} finally {
bankLock.unlock();
}
}
}
测试代码如下:
public class BankTest {
public static void main(String[] args) throws InterruptedException {
Bank bank = new Bank(new double[]{1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000});
Thread t1 = new Thread(() -> {
try {
bank.transfer(0, 1, 2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
Thread t2 = new Thread(() -> {
try {
bank.transfer(2, 0, 3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.start();
Thread.sleep(5000);
t2.start();
}
}
运行结果如下:
Thread-0 will be waiting
Thread-1: [2] = 3000.0, [0] = 1000.0
Thread-1: [2] -> [0]: 3000.0
Thread-0 awake now
Thread-0: [0] = 4000.0, [1] = 2000.0
Thread-0: [0] -> [1]: 2000.0
synchronized 关键字
Java 中的每一个对象都有一个内部锁,如果一个方法使用 synchronized 关键字声明,那么,对象的锁将保护整个方法。
同时,内部对象锁只有一个相关条件,即
-
wait()
:方法添加一个线程到等待集; -
notifyAll()
或者notify()
方法:解除等待阻塞状态
由于 wait、notify、notifyAll 是 Object 类的 final 方法,所以,Condition 类中对应的方法为了避免命名冲突,所以命名为了 await、signal、signalAll。
public class BankV2 {
private final double[] accounts;
BankV2(double[] accounts) {
this.accounts = accounts;
}
public synchronized void transfer(int fromUser, int toUser, double amount) throws InterruptedException {
while (accounts[fromUser] < amount) {
System.out.println(Thread.currentThread().getName() + " will be waiting");
this.wait(); // 阻塞,直到其他线程调用 notifyAll() 方法为止
System.out.println(Thread.currentThread().getName() + " is awake now");
}
System.out.println(Thread.currentThread().getName() + ": [" + fromUser + "] = " + accounts[fromUser] +
", [" + toUser + "] = " + accounts[toUser]);
accounts[fromUser] -= amount;
accounts[toUser] += amount;
System.out.println(Thread.currentThread().getName() + ": [" + fromUser + "] -> [" + toUser + "]: " + amount);
this.notifyAll(); // 唤醒等待的线程
}
}
测试代码如下:
public class BankTest {
public static void main(String[] args) throws InterruptedException {
BankV2 bank = new BankV2(new double[]{1000, 2000, 3000, 4000, 5000, 6000, 7000, 8000, 9000});
Thread t1 = new Thread(() -> {
try {
bank.transfer(0, 1, 2000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
Thread t2 = new Thread(() -> {
try {
bank.transfer(2, 0, 3000);
} catch (InterruptedException e) {
throw new RuntimeException(e);
}
});
t1.start();
Thread.sleep(5000);
t2.start();
}
}
运行结果如下:
Thread-0 will be waiting
Thread-1: [2] = 3000.0, [0] = 1000.0
Thread-1: [2] -> [0]: 3000.0
Thread-0 awake now
Thread-0: [0] = 4000.0, [1] = 2000.0
Thread-0: [0] -> [1]: 2000.0