2.1.線程的同步和協作_基本同步

基本同步

同步概述

多個線程共享一個資源的場景非常常見,比如多個線程讀或者寫相同的數據等。爲了防止共享資源可能出現的錯誤或數據不一致,需要有一些機制來防止錯誤的發生。


臨界區(Critical Section)是指訪問共享資源的代碼塊,這個代碼塊在同一時間只允許一個線程執行。Java提供了同步機制來實現臨界區。當一個線程試圖訪問一個臨界區時,它使用同步機制查看是不是已經有其他線程進入了臨界區,如果美歐它就可以進入臨界區;如果已經有線程進入臨界區,它就被同步機制掛起,直到進入的線程離開這個臨界區。如果在等待進入臨界區的線程不止一個,JVM會選擇其中的一個,其餘將繼續等待。


Java中的同步機制有兩種:

  • synchronized關鍵字機制
  • Lock接口及其實現(JDK5.0引入)

本節討論synchronized機制,這是最基本最常用的同步機制。後面的章節討論Lock機制。


synchronized

JAVA中最基本的同步方式就是使用synchronized關鍵字。每一個使用synchronized關鍵字聲明的方法都是臨界區,一個對象的臨界區在同一時間只有一個允許被訪問。synchronized關鍵字也可以聲明在靜態方法上,用synchronized聲明的靜態方法同時只能夠被一個執行線程訪問,但是其他線程可以訪問這個對象的非靜態方法。這點要特別注意,因爲兩個線程可以同時訪問一個對象的兩個方法,一個是靜態的,另一個是非靜態的。如果兩個方法都改變了相同的數據,將會出現數據不一致的錯誤。


synchronized是可重入的。


下面以銀行轉賬的經典示例來說明synchronized關鍵字的用法,這個示例使用兩個線程訪問同一個賬戶對象,一個線程轉錢到此賬戶,一個線程從此賬戶中取錢,如果沒有同步機制那麼最後賬戶的餘額是不對的,同步機制保證賬戶的餘額最終是正確的,下面的示例將在轉入和轉出時使用synchronized關鍵子進行同步。其實更本質的是在修改賬戶餘額時需要同步。

public class AccoutSynchronized {

    public static void  main(String[] args){
        final Account account = new Account(1000);
        System.out.println("main:初始賬戶。賬戶餘額:" + account.getBalance());

        System.out.println("main:創建工作線程。");
        Thread addThread = new Thread(new AddWorker(account));
        Thread subThread = new Thread(new SubWorker(account));

        System.out.println("main:啓動工作線程。");
        addThread.start();
        subThread.start();

        try {
            System.out.println("main:等待工作線程執行完成。");
            addThread.join();
            subThread.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        System.out.println("main:工作線程執行完成。賬戶餘額:" + account.getBalance());

        System.out.println("main:退出。");
    }
}

/** 轉入線程**/
class AddWorker implements Runnable{

    private Account account;

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

    @Override
    public void run(){
        System.out.println("轉入工作線程啓動。");
        for(int i=0; i<100; i++){
            account.addAmount(1000);
        }

        System.out.println("轉入工作線程完成。");
    }
}

/** 轉出線程**/
class SubWorker implements Runnable{

    private Account account;

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

    @Override
    public void run(){
        System.out.println("轉出工作線程啓動。");
        for(int i=0; i<100; i++){
            account.subAmount(1000);
        }
        System.out.println("轉出工作線程完成。");
    }
}

/** 賬戶 **/
class Account{
    private double balance;

    public Account(double balance){
        this.balance = balance;
    }

    /** 轉入賬戶 */
    public synchronized void addAmount(double amount){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        balance += amount;
    }

    /** 轉出賬戶 */
    public synchronized void subAmount(double amount){
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        balance -= amount;
    }

    public double getBalance(){
        return this.balance;
    }
}

程序執行日誌如下:

main:初始賬戶。賬戶餘額:1000.0
main:創建工作線程。
main:啓動工作線程。
main:等待工作線程執行完成。
轉入工作線程啓動。
轉出工作線程啓動。
轉出工作線程完成。
轉入工作線程完成。
main:工作線程執行完成。賬戶餘額:1000.0
main:退出。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章