Java 多線程實例詳解(三)。本站提示廣大學習愛好者:(Java 多線程實例詳解(三))文章只能為提供參考,不一定能成為您想要的結果。以下是Java 多線程實例詳解(三)正文
本文重要接著後面多線程的兩篇文章總結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("取錢勝利, 掏出錢數為:" + 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的余額,法式模仿你和你妻子同時在取款機停止取錢操作的場景。屢次運轉此法式,能夠具有多個分歧組合的輸入成果。個中一種能夠的輸入為:
1 取錢勝利, 掏出錢數為:700.0
2 余額為:300.0
3 取錢勝利, 掏出錢數為:700.0
4 余額為:-400.0
也就是說,關於一張只要1000余額的銀行卡,你們一共可以掏出1400,這明顯是有成績的。
經由剖析,成績在於Java多線程情況下的履行的不肯定性。CPU能夠隨機的在多個處於停當狀況中的線程中停止切換,是以,很有能夠湧現以下情形:當thread1履行到//1處代碼時,斷定前提為true,此時CPU切換到thread2,履行//1處代碼,發明仍然為真,然後履行完thread2,接著切換到thread1,接著履行終了。此時,就會湧現上述成果。
是以,講到線程平安成績,實際上是指多線程情況下對同享資本的拜訪能夠會惹起此同享資本的紛歧致性。是以,為防止線程平安成績,應當防止多線程情況下對此同享資本的並發拜訪。
二.同步辦法
對同享資本停止拜訪的辦法界說中加上synchronized症結字潤飾,使得此辦法稱為同步辦法。可以簡略懂得成對此辦法停止了加鎖,其鎖對象為以後辦法地點的對象本身。多線程情況下,當履行此辦法時,起首都要取得此同步鎖(且同時最多只要一個線程可以或許取得),只要當線程履行完此同步辦法後,才會釋放鎖對象,其他的線程才有能夠獲得此同步鎖,以此類推...
在上例中,同享資本為account對象,當應用同步辦法時,可以處理線程平安成績。只需在run()辦法前加上synshronized症結字便可。
public synchronized void run() { // .... }
三.同步代碼塊
正如下面所剖析的那樣,處理線程平安成績其實只需限制對同享資本拜訪的不肯定性便可。應用同步辦法時,使得全部辦法體都成了同步履行狀況,會使得能夠湧現同步規模過年夜的情形,因而,針對須要同步的代碼可以直接另外一種同步方法——同步代碼塊來處理。
同步代碼塊的格局為:
synchronized (obj) { //... }
個中,obj為鎖對象,是以,選擇哪個對象作為鎖是相當主要的。普通情形下,都是選擇此同享資本對象作為鎖對象。
如上例中,最好選用account對象作為鎖對象。(固然,選用this也是可以的,那是由於創立線程應用了runnable方法,假如是直接繼續Thread方法創立的線程,應用this對象作為同步鎖會其實沒有起就任何感化,由於是分歧的對象了。是以,選擇同步鎖時須要非分特別當心...)
四.Lock對象同步鎖
下面我們可以看出,正由於對同步鎖對象的選擇須要如斯當心,有無甚麼簡略點的處理計劃呢?以便利同步鎖對象與同享資本解耦,同時又能很好的處理線程平安成績。
應用Lock對象同步鎖可以便利的處理此成績,獨一須要留意的一點是Lock對象須要與資本對象異樣具有一對一的關系。Lock對象同步鎖普通格局為:
class X { // 顯示界說Lock同步鎖對象,此對象與同享資本具有一對一關系 private final Lock lock = new ReentrantLock(); public void m(){ // 加鎖 lock.lock(); //... 須要停止線程平安同步的代碼 // 釋放Lock鎖 lock.unlock(); } }
五.wait()/notify()/notifyAll()線程通訊
在博文《Java總結篇系列:java.lang.Object》中有說起到這三個辦法,固然這三個辦法重要都是用於多線程中,但現實上都是Object類中的當地辦法。是以,實際上,任何Object對象都可以作為這三個辦法的主調,在現實的多線程編程中,只要同步鎖對象調這三個辦法,能力完成對多線程間的線程通訊。
wait():招致以後線程期待並使其進入到期待壅塞狀況。直到其他線程挪用該同步鎖對象的notify()或notifyAll()辦法來叫醒此線程。
notify():叫醒在此同步鎖對象上期待的單個線程,假如有多個線程都在此同步鎖對象上期待,則會隨意率性選擇個中某個線程停止叫醒操作,只要以後線程廢棄對同步鎖對象的鎖定,才能夠履行被叫醒的線程。
notifyAll():叫醒在此同步鎖對象上期待的一切線程,只要以後線程廢棄對同步鎖對象的鎖定,才能夠履行被叫醒的線程。
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; // 標識賬戶中能否已有存款 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) { // 賬戶中已有人存錢出來,此時以後線程須要期待壅塞 try { System.out.println(Thread.currentThread().getName() + " 開端要履行wait操作" + " -- i=" + i); wait(); // 1 System.out.println(Thread.currentThread().getName() + " 履行了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() + "-- 存錢 -- 履行終了" + " -- i=" + i); } } /** * 取錢 * * @param drawAmount */ public synchronized void draw(double drawAmount, int i) { if (!flag) { // 賬戶中還沒人存錢出來,此時以後線程須要期待壅塞 try { System.out.println(Thread.currentThread().getName() + " 開端要履行wait操作" + " 履行了wait操作" + " -- i=" + i); wait(); System.out.println(Thread.currentThread().getName() + " 履行了wait操作" + " 履行了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() + "-- 取錢 -- 履行終了" + " -- i=" + i); // 3 } } }
下面的例子演示了wait()/notify()/notifyAll()的用法。部門輸入成果為:
取錢線程 開端要履行wait操作 履行了wait操作 -- i=0
存錢線程 存款:700.0 -- i=0
存錢線程-- 存錢 -- 履行終了 -- i=0
存錢線程 開端要履行wait操作 -- i=1
取錢線程 履行了wait操作 履行了wait操作 -- i=0
取錢線程 取錢:700.0 -- i=1
取錢線程-- 取錢 -- 履行終了 -- i=1
取錢線程 開端要履行wait操作 履行了wait操作 -- i=2
存錢線程 履行了wait操作 -- i=1
存錢線程 存款:700.0 -- i=2
存錢線程-- 存錢 -- 履行終了 -- i=2
取錢線程 履行了wait操作 履行了wait操作 -- i=2
取錢線程 取錢:700.0 -- i=3
取錢線程-- 取錢 -- 履行終了 -- i=3
取錢線程 開端要履行wait操作 履行了wait操作 -- i=4
存錢線程 存款:700.0 -- i=3
存錢線程-- 存錢 -- 履行終了 -- i=3
存錢線程 開端要履行wait操作 -- i=4
取錢線程 履行了wait操作 履行了wait操作 -- i=4
取錢線程 取錢:700.0 -- i=5
取錢線程-- 取錢 -- 履行終了 -- i=5
取錢線程 開端要履行wait操作 履行了wait操作 -- i=6
存錢線程 履行了wait操作 -- i=4
存錢線程 存款:700.0 -- i=5
存錢線程-- 存錢 -- 履行終了 -- i=5
存錢線程 開端要履行wait操作 -- i=6
取錢線程 履行了wait操作 履行了wait操作 -- i=6
取錢線程 取錢:700.0 -- i=7
取錢線程-- 取錢 -- 履行終了 -- i=7
取錢線程 開端要履行wait操作 履行了wait操作 -- i=8
存錢線程 履行了wait操作 -- i=6
存錢線程 存款:700.0 -- i=7
由此,我們須要留意以下幾點:
1.wait()辦法履行後,以後線程立刻進入到期待壅塞狀況,厥後面的代碼不會履行;
2.notify()/notifyAll()辦法履行後,將叫醒此同步鎖對象上的(隨意率性一個-notify()/一切-notifyAll())線程對象,然則,此時還並沒有釋放同步鎖對象,也就是說,假如notify()/notifyAll()前面還有代碼,還會持續停止,曉得以後線程履行終了才會釋放同步鎖對象;
3.notify()/notifyAll()履行後,假如左面有sleep()辦法,則會使以後線程進入到壅塞狀況,然則同步對象鎖沒有釋放,仍然本身保存,那末必定時刻後照樣會持續履行此線程,接上去同2;
4.wait()/notify()/nitifyAll()完成線程間的通訊或協作都是基於分歧對象鎖的,是以,假如是分歧的同步對象鎖將掉去意義,同時,同步對象鎖最好是與同享資本對象堅持逐個對應關系;
5.當wait線程叫醒後並履行時,是接著前次履行到的wait()辦法代碼前面持續往下履行的。
固然,下面的例子絕對來講比擬簡略,只是為了簡略示例wait()/notify()/noitifyAll()辦法的用法,但其實質上說,曾經是一個簡略的臨盆者-花費者形式了。
系列文章:
java 多線程實例講授 (一)
Java 多線程實例詳解(二)
Java 多線程實例詳解(三)