(二)線程同步_1---同步一個方法

同步一個方法(Synchronizing a method)

在併發編程中,最多見的情景莫過於多線程共享同一資源的情景了,例如多個線程同時讀寫相同的數據或者同時訪問相同的文件或數據庫鏈接,若是不採起一個控制機制,那麼就會形成數據的不一致甚至是錯誤發生;java

爲了解決這些問題,引入了臨界區域(critical section)的概念,一個臨界區域是指一個用來控制資源訪問的代碼塊,以保證被訪問的資源不會同時被多個線程訪問,從而保證了數據的一致性;算法

java中提供了Synchronization機制,當一個線程訪問一個臨界區域時,會首先使用這些同步機制找出是否有多個線程在同時執行這個臨界區域代碼塊,若是不是,那麼這個線程正常訪問,若是是,那麼這個線程將會掛起,直到正在執行這個臨界區域代碼塊的線程結束後才恢復,當有多個線程被掛起,在恢復後JVM採用必定算法選擇哪一個線程將會訪問這個臨界區域代碼塊;數據庫

Java中提供的最基本的同步機制有兩個:編程

  • 關鍵字synchronized
  • Lock接口以及其實現

在接下來的實例中,將會展現最基本的方法同步,即利用synchronized關鍵字去控制一個方法的併發訪問;若是一個對象有多個方法的聲明都有synchronized關鍵字,那麼在同一時刻只有一個線程可以訪問這些方法中的其中一個;若是有另一個線程在同一時刻試圖訪問這個對象的任何一個帶有synchronized聲明的方法,都將會被掛起,直到第一個線程執行完這個方法;換句話說,方法聲明帶有synchronized關鍵字的方法都是一個臨界區域(critical section),對於同一個對象,同一時間只容許執行一個臨界區域(critical section)。多線程

舉個通俗的例子:好比一個對象裏面有兩個都聲明瞭synchronized關鍵字的方法,一個方法是讀取文件A,並追加一行信息;另一個方法是讀取文件A,並追加兩行信息;那麼這兩個方法都屬於臨界區域,不可以被兩個線程同時訪問的;若是同時訪問了,那麼兩個方法讀取相同文件A,並同時寫入了不一樣的信息,返回的結果就錯誤了;從這個角度能夠理解爲何同一時間只能有一個線程能夠訪問一個對象中其中一個帶有synchronized關鍵字的方法;併發

然而對於靜態方法,則有不一樣的行爲;一個對象中有多個聲明瞭synchronized的靜態方法和多個非靜態方法,在同一時刻,一個線程只能訪問其中一個靜態同步方法,然而另一個線程能夠訪問其中一個非靜態方法;對於這種狀況須要很是當心,由於在同一時刻,兩個線程能夠訪問一個對象的兩種不一樣類型的同步方法:一個靜態的,一個非靜態的;app

若是這兩個方法控制的是相同的數據,這樣有可能致使數據不一致;ide

下面實現一個例子:兩個線程同時訪問一個對象;一個線程向銀行帳戶中轉錢,一個線程從相同銀行帳戶中取錢,分別觀察利用同步機制和不一樣同步機制的區別性能

動手實現

1.建立一個帳戶對象this

public class Account {
    private double balance;

    public double getBalance() {
        return balance;
    }

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

    public synchronized void addAmount(double amount){
        double tmp=balance;
        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tmp+=amount;
        balance=tmp;
    }

    public synchronized void subtractAmount(double amount) {
        double tmp=balance;
        try {
            Thread.sleep(20);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        tmp-=amount;
        balance=tmp;
    }
}
2.建立一個銀行線程,用來取出錢

public class Bank implements Runnable {
    private Account account;

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

    @Override
    public void run() {
        for (int i=0; i<100; i++){
            account.subtractAmount(1000);
        }
    }
}
3.建立一個公司線程用來轉入錢

public class Company implements Runnable {
    private Account account;

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

    @Override
    public void run() {
        for (int i=0; i<100; i++){
            account.addAmount(1000);
        }
    }
}
4.建立一個監控線程,每隔2毫秒監控上面兩個線程的狀態,能夠用來驗證synchronized同步

public class StatusMonitor implements Runnable{
    private Thread[] threads;

    public StatusMonitor(Thread[] threads) {
        this.threads=threads;
    }

    private String status(){
        StringBuffer sb=new StringBuffer();
        sb.append("status list:\t\n");
        for (Thread thread : threads) {
            sb.append("\t").append(thread.getName()).append(" status:").append(thread.getState()).append("\n");
        }
        return sb.toString();
    }
    @Override
    public void run() {
        boolean flag=true;
        while(flag) {
            System.out.println(status());
            try {
                TimeUnit.MILLISECONDS.sleep(5);
            } catch (InterruptedException e) {
                flag=false;
            }
        }
    }
}
5.Main方法

public class Main {
    public static void main(String[] args) {
        Account account = new Account();
        account.setBalance(1000);

        Company company = new Company(account);
        final Thread companyThread = new Thread(company);

        final Bank bank = new Bank(account);
        final Thread bankThread = new Thread(bank);

        System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
        companyThread.start();
        bankThread.start();

        // Monitor bankThread and companyThread's states
        StatusMonitor statusMonitor = new StatusMonitor(new Thread[]{companyThread, bankThread});
        Thread monitor = new Thread(statusMonitor);
        monitor.start();

        //Waiting for threads finished
        try {
            companyThread.join();
            bankThread.join();
            //Ending monitor
            monitor.interrupt();
            System.out.printf("Account : Final Balance: %f\n", account.getBalance());

        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

能夠觀察監控線程的輸出,這兩個線程基本上都是一個處於阻塞,一個處於運行狀態;

要點

理解synchronized的用法;synchronized關鍵字這種同步機制對性能上有必定的損耗;

synchronized關鍵字指定在方法簽名上,同步的是整個方法;也能夠指定更小的同步塊,如在一個方法內部同步更小的部分:

public void method(){

// java code

synchornized(this){

// java code

}

}

這裏有一個問題,一個方法裏面sychronized同步塊以前的代碼會不會能夠同時被多個線程執行呢?答案是能夠被多個線程執行;下面的例子能夠證明:

public class BlockTest extends Thread {
    private Block block;

    public BlockTest(Block block) {
        this.block=block;
    }

    @Override
    public void run() {
        block.read();
    }

    public static void main(String[] args) {
        Block block=new Block();
        BlockTest thread1=new BlockTest(block);
        BlockTest thread2=new BlockTest(block);
        thread1.start();
        thread2.start();

    }
    private static class Block{
        public void read(){
            System.out.printf("Thread %s start to read.\n", Thread.currentThread().getName());
            synchronized (this){
                System.out.printf("Current thread:%s\n",Thread.currentThread().getName());
                try {
                    TimeUnit.SECONDS.sleep(5);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            System.out.printf("Thread %s read over.\n", Thread.currentThread().getName());
        }
    }
}
一次運行結果:

Thread Thread-0 start to read. Thread Thread-1 start to read. Current thread:Thread-0 Thread Thread-0 read over. Current thread:Thread-1 Thread Thread-1 read over. 從運行結果的輸出過程能夠看到多個線程能夠同時訪問一個方法內部synchronized以前的部分,可是運行到synchronized部分,將只有一個線程繼續運行,其它的線程將會被阻塞;

相關文章
相關標籤/搜索