最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

Java 多線程實(shí)例詳解(三)

 更新時(shí)間:2016年09月05日 16:26:13   作者:Corn  
本文主要介紹 java 線程安全的知識,這里整理了相關(guān)資料及實(shí)現(xiàn)示例代碼,有興趣的小伙伴可以參考下

本文主要接著前面多線程的兩篇文章總結(jié)Java多線程中的線程安全問題。

一.一個典型的Java線程安全例子

public class ThreadTest {

 public static void main(String[] args) {
  Account account = new Account("123456", 1000);
  DrawMoneyRunnable drawMoneyRunnable = new DrawMoneyRunnable(account, 700);
  Thread myThread1 = new Thread(drawMoneyRunnable);
  Thread myThread2 = new Thread(drawMoneyRunnable);
  myThread1.start();
  myThread2.start();
 }

}

class DrawMoneyRunnable implements Runnable {

 private Account account;
 private double drawAmount;

 public DrawMoneyRunnable(Account account, double drawAmount) {
  super();
  this.account = account;
  this.drawAmount = drawAmount;
 }

 public void run() {
  if (account.getBalance() >= drawAmount) { //1
   System.out.println("取錢成功, 取出錢數(shù)為:" + drawAmount);
   double balance = account.getBalance() - drawAmount;
   account.setBalance(balance);
   System.out.println("余額為:" + balance);
  }
 }
}

class Account {

 private String accountNo;
 private double balance;

 public Account() {

 }

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

 public String getAccountNo() {
  return accountNo;
 }

 public void setAccountNo(String accountNo) {
  this.accountNo = accountNo;
 }

 public double getBalance() {
  return balance;
 }

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

}

上面例子很容易理解,有一張銀行卡,里面有1000的余額,程序模擬你和你老婆同時(shí)在取款機(jī)進(jìn)行取錢操作的場景。多次運(yùn)行此程序,可能具有多個不同組合的輸出結(jié)果。其中一種可能的輸出為:

1 取錢成功, 取出錢數(shù)為:700.0
2 余額為:300.0
3 取錢成功, 取出錢數(shù)為:700.0
4 余額為:-400.0

也就是說,對于一張只有1000余額的銀行卡,你們一共可以取出1400,這顯然是有問題的。

經(jīng)過分析,問題在于Java多線程環(huán)境下的執(zhí)行的不確定性。CPU可能隨機(jī)的在多個處于就緒狀態(tài)中的線程中進(jìn)行切換,因此,很有可能出現(xiàn)如下情況:當(dāng)thread1執(zhí)行到//1處代碼時(shí),判斷條件為true,此時(shí)CPU切換到thread2,執(zhí)行//1處代碼,發(fā)現(xiàn)依然為真,然后執(zhí)行完thread2,接著切換到thread1,接著執(zhí)行完畢。此時(shí),就會出現(xiàn)上述結(jié)果。

因此,講到線程安全問題,其實(shí)是指多線程環(huán)境下對共享資源的訪問可能會引起此共享資源的不一致性。因此,為避免線程安全問題,應(yīng)該避免多線程環(huán)境下對此共享資源的并發(fā)訪問。

二.同步方法

對共享資源進(jìn)行訪問的方法定義中加上synchronized關(guān)鍵字修飾,使得此方法稱為同步方法??梢院唵卫斫獬蓪Υ朔椒ㄟM(jìn)行了加鎖,其鎖對象為當(dāng)前方法所在的對象自身。多線程環(huán)境下,當(dāng)執(zhí)行此方法時(shí),首先都要獲得此同步鎖(且同時(shí)最多只有一個線程能夠獲得),只有當(dāng)線程執(zhí)行完此同步方法后,才會釋放鎖對象,其他的線程才有可能獲取此同步鎖,以此類推...

在上例中,共享資源為account對象,當(dāng)使用同步方法時(shí),可以解決線程安全問題。只需在run()方法前加上synshronized關(guān)鍵字即可。

 public synchronized void run() {
  
 // ....
 
}

三.同步代碼塊

正如上面所分析的那樣,解決線程安全問題其實(shí)只需限制對共享資源訪問的不確定性即可。使用同步方法時(shí),使得整個方法體都成為了同步執(zhí)行狀態(tài),會使得可能出現(xiàn)同步范圍過大的情況,于是,針對需要同步的代碼可以直接另一種同步方式——同步代碼塊來解決。

同步代碼塊的格式為:

 synchronized (obj) {
    
  //...
 
 }

其中,obj為鎖對象,因此,選擇哪一個對象作為鎖是至關(guān)重要的。一般情況下,都是選擇此共享資源對象作為鎖對象。

如上例中,最好選用account對象作為鎖對象。(當(dāng)然,選用this也是可以的,那是因?yàn)閯?chuàng)建線程使用了runnable方式,如果是直接繼承Thread方式創(chuàng)建的線程,使用this對象作為同步鎖會其實(shí)沒有起到任何作用,因?yàn)槭遣煌膶ο罅?。因此,選擇同步鎖時(shí)需要格外小心...)

四.Lock對象同步鎖

上面我們可以看出,正因?yàn)閷ν芥i對象的選擇需要如此小心,有沒有什么簡單點(diǎn)的解決方案呢?以方便同步鎖對象與共享資源解耦,同時(shí)又能很好的解決線程安全問題。

使用Lock對象同步鎖可以方便的解決此問題,唯一需要注意的一點(diǎn)是Lock對象需要與資源對象同樣具有一對一的關(guān)系。Lock對象同步鎖一般格式為:

class X {
 
 // 顯示定義Lock同步鎖對象,此對象與共享資源具有一對一關(guān)系
 private final Lock lock = new ReentrantLock();
 
 public void m(){
  // 加鎖
  lock.lock();
  
  //... 需要進(jìn)行線程安全同步的代碼
  
  // 釋放Lock鎖
  lock.unlock();
 }
 
}

 五.wait()/notify()/notifyAll()線程通信

在博文《Java總結(jié)篇系列:java.lang.Object》中有提及到這三個方法,雖然這三個方法主要都是用于多線程中,但實(shí)際上都是Object類中的本地方法。因此,理論上,任何Object對象都可以作為這三個方法的主調(diào),在實(shí)際的多線程編程中,只有同步鎖對象調(diào)這三個方法,才能完成對多線程間的線程通信。

wait():導(dǎo)致當(dāng)前線程等待并使其進(jìn)入到等待阻塞狀態(tài)。直到其他線程調(diào)用該同步鎖對象的notify()或notifyAll()方法來喚醒此線程。

notify():喚醒在此同步鎖對象上等待的單個線程,如果有多個線程都在此同步鎖對象上等待,則會任意選擇其中某個線程進(jìn)行喚醒操作,只有當(dāng)前線程放棄對同步鎖對象的鎖定,才可能執(zhí)行被喚醒的線程。

notifyAll():喚醒在此同步鎖對象上等待的所有線程,只有當(dāng)前線程放棄對同步鎖對象的鎖定,才可能執(zhí)行被喚醒的線程。

package com.qqyumidi;

public class ThreadTest {

 public static void main(String[] args) {
  Account account = new Account("123456", 0);

  Thread drawMoneyThread = new DrawMoneyThread("取錢線程", account, 700);
  Thread depositeMoneyThread = new DepositeMoneyThread("存錢線程", account, 700);

  drawMoneyThread.start();
  depositeMoneyThread.start();
 }

}

class DrawMoneyThread extends Thread {

 private Account account;
 private double amount;

 public DrawMoneyThread(String threadName, Account account, double amount) {
  super(threadName);
  this.account = account;
  this.amount = amount;
 }

 public void run() {
  for (int i = 0; i < 100; i++) {
   account.draw(amount, i);
  }
 }
}

class DepositeMoneyThread extends Thread {

 private Account account;
 private double amount;

 public DepositeMoneyThread(String threadName, Account account, double amount) {
  super(threadName);
  this.account = account;
  this.amount = amount;
 }

 public void run() {
  for (int i = 0; i < 100; i++) {
   account.deposite(amount, i);
  }
 }
}

class Account {

 private String accountNo;
 private double balance;
 // 標(biāo)識賬戶中是否已有存款
 private boolean flag = false;

 public Account() {

 }

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

 public String getAccountNo() {
  return accountNo;
 }

 public void setAccountNo(String accountNo) {
  this.accountNo = accountNo;
 }

 public double getBalance() {
  return balance;
 }

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

 /**
  * 存錢
  * 
  * @param depositeAmount
  */
 public synchronized void deposite(double depositeAmount, int i) {

  if (flag) {
   // 賬戶中已有人存錢進(jìn)去,此時(shí)當(dāng)前線程需要等待阻塞
   try {
    System.out.println(Thread.currentThread().getName() + " 開始要執(zhí)行wait操作" + " -- i=" + i);
    wait();
    // 1
    System.out.println(Thread.currentThread().getName() + " 執(zhí)行了wait操作" + " -- i=" + i);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  } else {
   // 開始存錢
   System.out.println(Thread.currentThread().getName() + " 存款:" + depositeAmount + " -- i=" + i);
   setBalance(balance + depositeAmount);
   flag = true;

   // 喚醒其他線程
   notifyAll();

   // 2
   try {
    Thread.sleep(3000);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
   System.out.println(Thread.currentThread().getName() + "-- 存錢 -- 執(zhí)行完畢" + " -- i=" + i);
  }
 }

 /**
  * 取錢
  * 
  * @param drawAmount
  */
 public synchronized void draw(double drawAmount, int i) {
  if (!flag) {
   // 賬戶中還沒人存錢進(jìn)去,此時(shí)當(dāng)前線程需要等待阻塞
   try {
    System.out.println(Thread.currentThread().getName() + " 開始要執(zhí)行wait操作" + " 執(zhí)行了wait操作" + " -- i=" + i);
    wait();
    System.out.println(Thread.currentThread().getName() + " 執(zhí)行了wait操作" + " 執(zhí)行了wait操作" + " -- i=" + i);
   } catch (InterruptedException e) {
    e.printStackTrace();
   }
  } else {
   // 開始取錢
   System.out.println(Thread.currentThread().getName() + " 取錢:" + drawAmount + " -- i=" + i);
   setBalance(getBalance() - drawAmount);

   flag = false;

   // 喚醒其他線程
   notifyAll();

   System.out.println(Thread.currentThread().getName() + "-- 取錢 -- 執(zhí)行完畢" + " -- i=" + i); // 3
  }
 }

}

上面的例子演示了wait()/notify()/notifyAll()的用法。部分輸出結(jié)果為:

取錢線程 開始要執(zhí)行wait操作 執(zhí)行了wait操作 -- i=0
存錢線程 存款:700.0 -- i=0
存錢線程-- 存錢 -- 執(zhí)行完畢 -- i=0
存錢線程 開始要執(zhí)行wait操作 -- i=1
取錢線程 執(zhí)行了wait操作 執(zhí)行了wait操作 -- i=0
取錢線程 取錢:700.0 -- i=1
取錢線程-- 取錢 -- 執(zhí)行完畢 -- i=1
取錢線程 開始要執(zhí)行wait操作 執(zhí)行了wait操作 -- i=2
存錢線程 執(zhí)行了wait操作 -- i=1
存錢線程 存款:700.0 -- i=2
存錢線程-- 存錢 -- 執(zhí)行完畢 -- i=2
取錢線程 執(zhí)行了wait操作 執(zhí)行了wait操作 -- i=2
取錢線程 取錢:700.0 -- i=3
取錢線程-- 取錢 -- 執(zhí)行完畢 -- i=3
取錢線程 開始要執(zhí)行wait操作 執(zhí)行了wait操作 -- i=4
存錢線程 存款:700.0 -- i=3
存錢線程-- 存錢 -- 執(zhí)行完畢 -- i=3
存錢線程 開始要執(zhí)行wait操作 -- i=4
取錢線程 執(zhí)行了wait操作 執(zhí)行了wait操作 -- i=4
取錢線程 取錢:700.0 -- i=5
取錢線程-- 取錢 -- 執(zhí)行完畢 -- i=5
取錢線程 開始要執(zhí)行wait操作 執(zhí)行了wait操作 -- i=6
存錢線程 執(zhí)行了wait操作 -- i=4
存錢線程 存款:700.0 -- i=5
存錢線程-- 存錢 -- 執(zhí)行完畢 -- i=5
存錢線程 開始要執(zhí)行wait操作 -- i=6
取錢線程 執(zhí)行了wait操作 執(zhí)行了wait操作 -- i=6
取錢線程 取錢:700.0 -- i=7
取錢線程-- 取錢 -- 執(zhí)行完畢 -- i=7
取錢線程 開始要執(zhí)行wait操作 執(zhí)行了wait操作 -- i=8
存錢線程 執(zhí)行了wait操作 -- i=6
存錢線程 存款:700.0 -- i=7

由此,我們需要注意如下幾點(diǎn):

1.wait()方法執(zhí)行后,當(dāng)前線程立即進(jìn)入到等待阻塞狀態(tài),其后面的代碼不會執(zhí)行;

2.notify()/notifyAll()方法執(zhí)行后,將喚醒此同步鎖對象上的(任意一個-notify()/所有-notifyAll())線程對象,但是,此時(shí)還并沒有釋放同步鎖對象,也就是說,如果notify()/notifyAll()后面還有代碼,還會繼續(xù)進(jìn)行,知道當(dāng)前線程執(zhí)行完畢才會釋放同步鎖對象;

3.notify()/notifyAll()執(zhí)行后,如果右面有sleep()方法,則會使當(dāng)前線程進(jìn)入到阻塞狀態(tài),但是同步對象鎖沒有釋放,依然自己保留,那么一定時(shí)候后還是會繼續(xù)執(zhí)行此線程,接下來同2;

4.wait()/notify()/nitifyAll()完成線程間的通信或協(xié)作都是基于不同對象鎖的,因此,如果是不同的同步對象鎖將失去意義,同時(shí),同步對象鎖最好是與共享資源對象保持一一對應(yīng)關(guān)系;

5.當(dāng)wait線程喚醒后并執(zhí)行時(shí),是接著上次執(zhí)行到的wait()方法代碼后面繼續(xù)往下執(zhí)行的。

當(dāng)然,上面的例子相對來說比較簡單,只是為了簡單示例wait()/notify()/noitifyAll()方法的用法,但其本質(zhì)上說,已經(jīng)是一個簡單的生產(chǎn)者-消費(fèi)者模式了。

 系列文章:

java 多線程實(shí)例講解 (一)
Java 多線程實(shí)例詳解(二)
Java 多線程實(shí)例詳解(三)

相關(guān)文章

  • Java?stream流中peek用法簡單示例

    Java?stream流中peek用法簡單示例

    這篇文章主要給大家介紹了關(guān)于Java?stream流中peek用法的相關(guān)資料,Java Stream中的peek()方法也是用于查看每個元素,但不改變流的操作的方法,文中通過代碼介紹的需要的朋友可以參考下
    2023-12-12
  • Spring MVC登錄注冊以及轉(zhuǎn)換json數(shù)據(jù)

    Spring MVC登錄注冊以及轉(zhuǎn)換json數(shù)據(jù)

    本文主要介紹了Spring MVC登錄注冊以及轉(zhuǎn)換json數(shù)據(jù)的相關(guān)知識。具有很好的參考價(jià)值。下面跟著小編一起來看下吧
    2017-04-04
  • Java正則表達(dá)式的替換和分組功能

    Java正則表達(dá)式的替換和分組功能

    這篇文章主要給大家介紹了關(guān)于Java正則表達(dá)式的替換和分組功能的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2020-09-09
  • Java關(guān)于桶排序的知識點(diǎn)總結(jié)

    Java關(guān)于桶排序的知識點(diǎn)總結(jié)

    這篇文章給大家總結(jié)了關(guān)于JAVA中J桶排序的相關(guān)知識點(diǎn)和用法分享,有興趣的讀者跟著學(xué)習(xí)下。
    2018-04-04
  • Spring定時(shí)服務(wù)QuartZ原理及代碼案例

    Spring定時(shí)服務(wù)QuartZ原理及代碼案例

    這篇文章主要介紹了Spring定時(shí)服務(wù)QuartZ原理及代碼案例,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-11-11
  • Java的Finalizer引發(fā)的內(nèi)存溢出問題及解決

    Java的Finalizer引發(fā)的內(nèi)存溢出問題及解決

    本文介紹了Java中的Finalizer機(jī)制,解釋了當(dāng)類實(shí)現(xiàn)finalize()方法時(shí),JVM的行為和潛在的風(fēng)險(xiǎn),通過一個示例程序,展示了實(shí)現(xiàn)finalize()方法會導(dǎo)致大量對象存活,最終引發(fā)OutOfMemoryError,文章分析了GC日志,解釋了Finalizer線程和主線程之間的競爭
    2025-03-03
  • Spring Cloud Gateway層限流實(shí)現(xiàn)過程

    Spring Cloud Gateway層限流實(shí)現(xiàn)過程

    這篇文章主要介紹了Spring Cloud Gateway層限流實(shí)現(xiàn)過程,具有很好的參考價(jià)值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2024-08-08
  • java中BigDecimal的操作方法

    java中BigDecimal的操作方法

    這篇文章主要介紹了java中BigDecimal的操作方法,較為詳細(xì)的分析了BigDecimal類在進(jìn)行商業(yè)計(jì)算時(shí)的應(yīng)用方法,精度以及注意事項(xiàng)等問題,需要的朋友可以參考下
    2014-12-12
  • java 實(shí)現(xiàn)MD5加密算法的簡單實(shí)例

    java 實(shí)現(xiàn)MD5加密算法的簡單實(shí)例

    這篇文章主要介紹了java 實(shí)現(xiàn)MD5加密算法的簡單實(shí)例的相關(guān)資料,這里提供實(shí)例幫助大家應(yīng)用這樣的加密算法,需要的朋友可以參考下
    2017-09-09
  • java項(xiàng)目中使用 Lombok遇到的問題小結(jié)

    java項(xiàng)目中使用 Lombok遇到的問題小結(jié)

    這篇文章主要介紹了java項(xiàng)目中使用 Lombok遇到的問題小結(jié),需要的朋友可以參考下
    2018-07-07

最新評論

弋阳县| 白河县| 罗平县| 孝昌县| 紫云| 长沙市| 贺兰县| 山西省| 乌苏市| 桂平市| 丘北县| 贵南县| 浙江省| 芜湖市| 兴山县| 舞阳县| 长沙县| 开封县| 桐城市| 宁海县| 和顺县| 门源| 龙游县| 仁化县| 象山县| 兴化市| 温宿县| 南丹县| 苍溪县| 阿鲁科尔沁旗| 宿迁市| 成安县| 聊城市| 泰和县| 桃园市| 北流市| 荣成市| 浪卡子县| 永寿县| 肇源县| 惠来县|