首頁  >  文章  >  Java  >  Java使用synchronized修飾方法來同步執行緒的實例演示

Java使用synchronized修飾方法來同步執行緒的實例演示

高洛峰
高洛峰原創
2017-01-05 15:34:081415瀏覽

Java中可以使用關鍵字synchronized進行執行緒同步控制,實現關鍵資源順序訪問,避免因多執行緒並發執行導致的資料不一致性等問題。 synchronized的原理是物件監視器(鎖),只有取得到監視器的執行緒才能繼續執行,否則執行緒會等待取得監視器。 Java中每個物件或類別都有一把鎖與之相關聯,對於物件來說,監視的是這個物件的實例變量,對於類別來說,監視的是類別變數(一個類別本身是類別Class的對象,所以與類別關聯的鎖也是物件鎖)。 synchronized關鍵字使用方式有兩種:synchronized方法和synchronized區塊。這兩個監視區域都和一個引入對象相關聯,當到達這個監視區域時,JVM就會鎖住這個引用對象,當離開時會釋放這個引用對像上的鎖(有異常退出時,JVM會釋放鎖)。物件鎖是JVM內部機制,只需要寫同步方法或同步區塊即可,操作監視區域時JVM會自動取得鎖定或釋放鎖定。

示例1

先來看第一個示例,在java中,同一個對象的臨界區,在同一時間只有一個允許被訪問(都是非靜態的synchronized方法):

package concurrency;
 
public class Main8 {
  public static void main(String[] args) {
    Account account = new Account();
    account.setBalance(1000);
    Company company = new Company(account);
    Thread companyThread = new Thread(company);
    Bank bank = new Bank(account);
    Thread bankThread = new Thread(bank);
    System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
    companyThread.start();
    bankThread.start();
    try {
      //join()方法等待这两个线程运行完成
      companyThread.join();
      bankThread.join();
      System.out.printf("Account : Final Balance: %f\n", account.getBalance());
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}
/*帐户*/
class Account{
  private double balance;
  /*将传入的数据加到余额balance中*/
  public synchronized void addAmount(double amount){
    double tmp = balance;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tmp += amount;
    balance = tmp;
  }
  /*将传入的数据从余额balance中扣除*/
  public synchronized void subtractAmount(double amount){
    double tmp = balance;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tmp -= amount;
    balance = tmp;
  }
  public double getBalance() {
    return balance;
  }
  public void setBalance(double balance) {
    this.balance = balance;
  }
}
/*银行*/
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);
    }
  }
}
/*公司*/
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);
    }
  }
}

你已經開發了一個銀行帳戶的模擬應用,它能夠對餘額進行充值和扣除。這個程式透過呼叫100次addAmount()方法對帳戶進行充值,每次存入1000;然後透過呼叫100次subtractAmount()方法對帳戶餘額進行扣除,每次扣除1000;我們期望帳戶的最終餘額與起初餘額相等,我們透過synchronized關鍵字實現了。

如果想查看共享資料的並發存取問題,只需要將addAmount()和subtractAmount()方法聲明中的synchronized關鍵字刪除即可。在沒有synchronized關鍵字的情況下,列印出來的餘額值並不一致。如果你多次執行這個程序,你將會得到不同的結果。因為JVM並不保證執行緒的執行順序,所以每次執行的時候,執行緒將以不同的順序讀取並且修改帳戶的餘額,造成最終結果也是不一樣的。

一個物件的方法採用synchronized關鍵字進行聲明,只能被一個執行緒存取。如果執行緒A正在執行一個同步方法syncMethodA(),執行緒B要執行這個物件的其他同步方法syncMethodB(),執行緒B將被阻塞直到執行緒A訪問完畢。但如果線程B訪問的是同一個類別的不同對象,那麼兩個線程都不會被阻塞。

範例2

示範同一個物件上的靜態synchronized方法與非靜態synchronized方法可以在同一時間點被多個執行緒存取的問題,驗證一下。

package concurrency;
 
public class Main8 {
  public static void main(String[] args) {
    Account account = new Account();
    account.setBalance(1000);
    Company company = new Company(account);
    Thread companyThread = new Thread(company);
    Bank bank = new Bank(account);
    Thread bankThread = new Thread(bank);
    System.out.printf("Account : Initial Balance: %f\n", account.getBalance());
    companyThread.start();
    bankThread.start();
    try {
      //join()方法等待这两个线程运行完成
      companyThread.join();
      bankThread.join();
      System.out.printf("Account : Final Balance: %f\n", account.getBalance());
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
  }
}
/*帐户*/
class Account{
  /*这里改为静态变量*/
  private static double balance = 0;
  /*将传入的数据加到余额balance中,注意是用static修饰过的*/
  public static synchronized void addAmount(double amount){
    double tmp = balance;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tmp += amount;
    balance = tmp;
  }
  /*将传入的数据从余额balance中扣除*/
  public synchronized void subtractAmount(double amount){
    double tmp = balance;
    try {
      Thread.sleep(10);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    tmp -= amount;
    balance = tmp;
  }
  public double getBalance() {
    return balance;
  }
  public void setBalance(double balance) {
    this.balance = balance;
  }
}
/*银行*/
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);
    }
  }
}
/*公司*/
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);
    }
  }
}

我只是把上個例子中的,balance加了static關鍵字修改,addAmount()方法也可以static關鍵字修飾。執行結果大家可以自己測試一下,每次執行都是不一樣的結果!

一些總結:

synchronized是透過軟體(JVM)實現的,簡單易用,即使在JDK5之後有了Lock,仍然被廣泛地使用。

synchronized實際上是非公平的,新來的線程有可能立即獲得監視器,而在等待區中等候已久的線程可能再次等待,不過這種搶佔的方式可以預防飢餓。

synchronized只有鎖只與一個條件(是否取得鎖)相關聯,不靈活,後來Condition與Lock的結合解決了這個問題。

多執行緒競爭一個鎖時,其餘未得到鎖的執行緒只能不停的嘗試獲得鎖,而不能中斷。高並發的情況下會導致效能下降。 ReentrantLock的lockInterruptibly()方法可以優先考慮回應中斷。 一個執行緒等待時間過長,它可以中斷自己,然後ReentrantLock回應這個中斷,不再讓這個執行緒繼續等待。有了這個機制,使用ReentrantLock時就不會像synchronized那樣產生死鎖了。

更多Java使用synchronized修飾方法來同步線程的實例演示相關文章請關注PHP中文網!


陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn