Java - Lock & synchronized 使用

By sunwc 2023-03-24 Practice

方式一、 使用鎖

import java.util.concurrent.locks.ReentrantLock;

/**
 * 演示案例:銀行有一個帳戶
 * 有兩個存戶分別向同一個帳戶存3000元,每次存1000,存3次。
 * 每次存完輸出帳戶餘額
 * @author sunwc
 * @create 2023-03-24 下午 12:23
 */
public class ConcurrentAccount {

    public static void main(String[] args) {

        // 帳戶為共享資源,只new一次
        Account account = new Account();
        Depositor depositor = new Depositor(account);

        Thread t1 = new Thread(depositor);
        Thread t2 = new Thread(depositor);
        t1.setName("存戶1");
        t2.setName("存戶2");

        t1.start();
        t2.start();
    }

}

/**
 * 帳戶類
 */
class Account {

    /**
     * 存款餘額
     */
    private double balance;

    /**
     * 存入金額並輸出帳戶餘額
     * @param amount
     */
    synchronized void deposit(int amount) {// 同步鎖默認為this

        if (amount > 0) {
            this.balance += amount;

            // 看會不會增加執行緒不安全問題
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName() + " 帳戶餘額:" + this.balance);
        }
    }
}

/**
 * 存戶類
 */
class Depositor implements Runnable {

    private Account account;

    public Depositor(Account account) {
        this.account = account;
    }

    /**
     * 鎖,設置執行緒先進先出
     */
    private ReentrantLock reentrantLock = new ReentrantLock(true);
    
    @Override
    public void run() {
        // 每個存戶 每次存1000、存3次
        for (int i = 0; i < 3; i++) {

            try {
                reentrantLock.lock();
                // 存1000
                account.deposit(1000);

            } finally {
                reentrantLock.unlock();
            }
        }
    }
}

輸出結果:

存戶1 帳戶餘額:1000.0
存戶2 帳戶餘額:2000.0
存戶1 帳戶餘額:3000.0
存戶2 帳戶餘額:4000.0
存戶1 帳戶餘額:5000.0
存戶2 帳戶餘額:6000.0

方式二、使用 synchronized 機制

/**
 * @author sunwc
 * @create 2023-03-24 下午 12:52
 */
public class ConcurrentAccount2 {

    public static void main(String[] args) {
        Depositor2 depositor1 = new Depositor2();
        Depositor2 depositor2 = new Depositor2();

        depositor1.setName("存戶1");
        depositor2.setName("存戶2");

        depositor1.start();
        depositor2.start();

    }
}

/**
 * 帳戶類
 */
class Account2 {

    /**
     * 存款餘額
     */
    private double balance;

    public double getBalance() {
        return balance;
    }

    public void setBalance(double balance) {
        this.balance = balance;
    }
}

/**
 * 存戶類
 */
class Depositor2 extends Thread {

    /**
     * 帳戶(共享資源)
     */
    private static Account2 account;

    /**
     * constrctor在創建存戶時一併創建帳戶
     */
    public Depositor2() {

        synchronized (Depositor.class) {
            if (account == null) {
                account = new Account2();
            }
        }
    }

    @Override
    public void run() {

        for (int i = 0; i < 3; i++) {
            // 存1000
            synchronized (Depositor2.class) {
                deposit(1000);
            }

        }
    }

    /**
     * 存錢操作
     * @param amount
     */
    private void deposit(int amount) {

        if (amount > 0) {
            // 先取得餘額
            double currnetBalance = account.getBalance();
            // 增加存款金額
            currnetBalance += amount;
            account.setBalance(currnetBalance);

            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

            System.out.println(Thread.currentThread().getName() + " 存錢成功,帳戶餘額:" + account.getBalance());
        }
    }
}

輸出結果:

存戶1 存錢成功,帳戶餘額:1000.0
存戶2 存錢成功,帳戶餘額:2000.0
存戶2 存錢成功,帳戶餘額:3000.0
存戶1 存錢成功,帳戶餘額:4000.0
存戶1 存錢成功,帳戶餘額:5000.0
存戶2 存錢成功,帳戶餘額:6000.0

總結

我們在下面兩篇文章已經了解到解決執行緒不安全的兩種方式

有關透過鎖 Lock 解決執行緒不安全問題,可以參考我的另一篇文章 Java - Lock - 解決執行緒不安全

有關透過實現類 implements interface Runnable 達成 Multi-threading,可以參考我的另一篇文章 Java - Thread 執行緒(二) - interface Runnable

本篇文章是舉例另一個可能會造成執行緒不安全的現實例子,能夠對於同步的機制更加印象深刻