【Java 并发】线程同步

发布时间 2024-01-06 23:21:40作者: LARRY1024

线程同步

条件对象

通常线程进入临界区,却发现需要满足某一个条件后,才能继续执行,这时,就需要使用一个条件对象,来管理那些已经获得了一个锁,但是,却不做有用工作的线程。这些条件对象经常被称为条件变量(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

监视器

volatile