亚欧洲精品在线观看,窝窝影院午夜看片,久久国产成人午夜av影院宅,午夜91,免费国产人成网站,ts在线视频,欧美激情在线一区

java語言

Java多線程的定義狀態(tài)和屬性

時間:2024-12-25 12:34:35 java語言 我要投稿
  • 相關(guān)推薦

Java多線程的定義狀態(tài)和屬性

  同步一直是java多線程的難點(diǎn),在我們做android開發(fā)時也很少應(yīng)用,但這并不是我們不熟悉同步的理由。希望這篇文章能使更多的人能夠了解并且應(yīng)用java的同步。

  在多線程的應(yīng)用中,兩個或者兩個以上的線程需要共享對同一個數(shù)據(jù)的存取。如果兩個線程存取相同的對象,并且每一個線程都調(diào)用了修改該對象的方法,這種情況通常成為競爭條件。

  競爭條件最容易理解的例子就是:比如火車賣票,火車票是一定的,但賣火車票的窗口到處都有,每個窗口就相當(dāng)于一個線程,這么多的線程共用所有的火車票這個資源。并且無法保證其原子性,如果在一個時間點(diǎn)上,兩個線程同時使用這個資源,那他們?nèi)〕龅幕疖嚻笔且粯拥模ㄗ惶栆粯樱,這樣就會給乘客造成麻煩。解決方法為,當(dāng)一個線程要使用火車票這個資源時,我們就交給它一把鎖,等它把事情做完后在把鎖給另一個要用這個資源的線程。這樣就不會出現(xiàn)上述情況。

  1. 鎖對象

  synchronized關(guān)鍵字自動提供了鎖以及相關(guān)的條件,大多數(shù)需要顯式鎖的情況使用synchronized非常的方便,但是等我們了解ReentrantLock類和條件對象時,我們能更好的理解synchronized關(guān)鍵字。ReentrantLock是JAVA SE 5.0引入的, 用ReentrantLock保護(hù)代碼塊的結(jié)構(gòu)如下:

  mLock.lock();try{...}finally{mLock.unlock();}

  這一結(jié)構(gòu)確保任何時刻只有一個線程進(jìn)入臨界區(qū),一旦一個線程封鎖了鎖對象,其他任何線程都無法通過lock語句。當(dāng)其他線程調(diào)用lock時,它們則被阻塞直到第一個線程釋放鎖對象。把解鎖的操作放在finally中是十分必要的,如果在臨界區(qū)發(fā)生了異常,鎖是必須要釋放的,否則其他線程將會永遠(yuǎn)阻塞。

  2. 條件對象

  進(jìn)入臨界區(qū)時,卻發(fā)現(xiàn)在某一個條件滿足之后,它才能執(zhí)行。要使用一個條件對象來管理那些已經(jīng)獲得了一個鎖但是卻不能做有用工作的線程,條件對象又稱作條件變量。

  我們來看看下面的例子來看看為何需要條件對象

  假設(shè)一個場景我們需要用銀行轉(zhuǎn)賬,我們首先寫了銀行的類,它的構(gòu)造函數(shù)需要傳入賬戶數(shù)量和賬戶金額

  public class Bank {private double[] accounts; private Lock bankLock; public Bank(int n,double initialBalance){ accounts=new double[n]; bankLock=new ReentrantLock(); for (int i=0;i<accounts.length;i++){ accounts[i]=initialBalance; } } }

  接下來我們要提款,寫一個提款的方法,from是轉(zhuǎn)賬方,to是接收方,amount轉(zhuǎn)賬金額,結(jié)果我們發(fā)現(xiàn)轉(zhuǎn)賬方余額不足,如果有其他線程給這個轉(zhuǎn)賬方再存足夠的錢就可以轉(zhuǎn)賬成功了,但是這個線程已經(jīng)獲取了鎖,它具有排他性,別的線程也無法獲取鎖來進(jìn)行存款操作,這就是我們需要引入條件對象的原因。

  public void transfer(int from,int to,int amount){ bankLock.lock(); try{ while (accounts[from]<amount){ //wait } }finally { bankLock.unlock(); } }

  一個鎖對象擁有多個相關(guān)的條件對象,可以用newCondition方法獲得一個條件對象,我們得到條件對象后調(diào)用await方法,當(dāng)前線程就被阻塞了并放棄了鎖

  public class Bank {private double[] accounts; private Lock bankLock; private Condition condition; public Bank(int n,double initialBalance){ accounts=new double[n]; bankLock=new ReentrantLock(); //得到條件對象 condition=bankLock.newCondition(); for (int i=0;i<accounts.length;i++){ accounts[i]=initialBalance; } } public void transfer(int from,int to,int amount) throws InterruptedException { bankLock.lock(); try{ while (accounts[from]<amount){ //阻塞當(dāng)前線程,并放棄鎖 condition.await(); } }finally { bankLock.unlock(); } }}

  等待獲得鎖的線程和調(diào)用await方法的線程本質(zhì)上是不同的,一旦一個線程調(diào)用的await方法,他就會進(jìn)入該條件的等待集。當(dāng)鎖可用時,該線程不能馬上解鎖,相反他處于阻塞狀態(tài),直到另一個線程調(diào)用了同一個條件上的signalAll方法時為止。當(dāng)另一個線程準(zhǔn)備轉(zhuǎn)賬給我們此前的轉(zhuǎn)賬方時,只要調(diào)用condition.signalAll();該調(diào)用會重新激活因?yàn)檫@一條件而等待的所有線程。

  當(dāng)一個線程調(diào)用了await方法他沒法重新激活自身,并寄希望于其他線程來調(diào)用signalAll方法來激活自身,如果沒有其他線程來激活等待的線程,那么就會產(chǎn)生死鎖現(xiàn)象,如果所有的其他線程都被阻塞,最后一個活動線程在解除其他線程阻塞狀態(tài)前調(diào)用await,那么它也被阻塞,就沒有任何線程可以解除其他線程的阻塞,程序就被掛起了。

  那何時調(diào)用signalAll呢?正常來說應(yīng)該是有利于等待線程的方向改變時來調(diào)用signalAll。在這個例子里就是,當(dāng)一個賬戶余額發(fā)生變化時,等待的線程應(yīng)該有機(jī)會檢查余額。

  public void transfer(int from,int to,int amount) throws InterruptedException { bankLock.lock(); try{ while (accounts[from]<amount){ //阻塞當(dāng)前線程,并放棄鎖 condition.await(); } //轉(zhuǎn)賬的操作 ... condition.signalAll(); }finally { bankLock.unlock(); } }

  當(dāng)調(diào)用signalAll方法時并不是立即激活一個等待線程,它僅僅解除了等待線程的阻塞,以便這些線程能夠在當(dāng)前線程退出同步方法后,通過競爭實(shí)現(xiàn)對對象的訪問。還有一個方法是signal,它則是隨機(jī)解除某個線程的阻塞,如果該線程仍然不能運(yùn)行,那么則再次被阻塞,如果沒有其他線程再次調(diào)用signal,那么系統(tǒng)就死鎖了。

  3. Synchronized關(guān)鍵字

  Lock和Condition接口為程序設(shè)計(jì)人員提供了高度的鎖定控制,然而大多數(shù)情況下,并不需要那樣的控制,并且可以使用一種嵌入到j(luò)ava語言內(nèi)部的機(jī)制。從Java1.0版開始,Java中的每一個對象都有一個內(nèi)部鎖。如果一個方法用synchronized關(guān)鍵字聲明,那么對象的鎖將保護(hù)整個方法。也就是說,要調(diào)用該方法,線程必須獲得內(nèi)部的對象鎖。

  換句話說,public synchronized void method(){}等價于public void method(){this.lock.lock();try{}finally{this.lock.unlock();}

  上面銀行的例子,我們可以將Bank類的transfer方法聲明為synchronized,而不是使用一個顯示的鎖。

  內(nèi)部對象鎖只有一個相關(guān)條件,wait放大添加到一個線程到等待集中,notifyAll或者notify方法解除等待線程的阻塞狀態(tài)。也就是說wait相當(dāng)于調(diào)用condition.await(),notifyAll等價于condition.signalAll();

  我們上面的例子transfer方法也可以這樣寫:

  public synchronized void transfer(int from,int to,int amount)throws InterruptedException{ while (accounts[from]<amount) { wait(); } //轉(zhuǎn)賬的操作 ... notifyAll(); }

  可以看到使用synchronized關(guān)鍵字來編寫代碼要簡潔很多,當(dāng)然要理解這一代碼,你必須要了解每一個對象有一個內(nèi)部鎖,并且該鎖有一個內(nèi)部條件。由鎖來管理那些試圖進(jìn)入synchronized方法的線程,由條件來管理那些調(diào)用wait的線程。

  4. 同步阻塞

  上面我們說過,每一個Java對象都有一個鎖,線程可以調(diào)用同步方法來獲得鎖,還有另一種機(jī)制可以獲得鎖,通過進(jìn)入一個同步阻塞,當(dāng)線程進(jìn)入如下形式的阻塞:

  synchronized(obj){}

  于是他獲得了obj的鎖。再來看看Bank類

  public class Bank {private double[] accounts;private Object lock=new Object(); public Bank(int n,double initialBalance){ accounts=new double[n]; for (int i=0;i<accounts.length;i++){ accounts[i]=initialBalance; } } public void transfer(int from,int to,int amount){ synchronized(lock){ //轉(zhuǎn)賬的操作 ... } }}

  在此,lock對象創(chuàng)建僅僅是用來使用每個Java對象持有的鎖。有時開發(fā)人員使用一個對象的鎖來實(shí)現(xiàn)額外的原子操作,稱為客戶端鎖定。例如Vector類,它的方法是同步的,F(xiàn)在假設(shè)在Vector中存儲銀行余額

  public void transfer(Vectoraccounts,int from,int to,int amount){ accounts.set(from,accounts.get(from)-amount); accounts.set(to,accounts.get(to)+amount;}

  Vecror類的get和set方法是同步的,但是這并未對我們有所幫助。在第一次對get調(diào)用完成以后,一個線程完全可能在transfer方法中被被剝奪運(yùn)行權(quán),于是另一個線程可能在相同的存儲位置存入了不同的值,但是,我們可以截獲這個鎖

  public void transfer(Vectoraccounts,int from,int to,int amount){ synchronized(accounts){ accounts.set(from,accounts.get(from)-amount); accounts.set(to,accounts.get(to)+amount; }}

  客戶端鎖定(同步代碼塊)是非常脆弱的,通常不推薦使用,一般實(shí)現(xiàn)同步最好用java.util.concurrent包下提供的類,比如阻塞隊(duì)列。如果同步方法適合你的程序,那么請盡量的使用同步方法,他可以減少編寫代碼的數(shù)量,減少出錯的幾率,如果特別需要使用Lock/Condition結(jié)構(gòu)提供的獨(dú)有特性時,才使用Lock/Condition。

【Java多線程的定義狀態(tài)和屬性】相關(guān)文章:

java多線程的同步和異步09-05

java的多線程09-09

java多線程08-31

Java數(shù)組的定義和使用09-04

java語言的多線程08-29

java多線程介紹08-23

java多線程教程11-03

如何使用java多線程08-23

Java多線程問題總結(jié)10-24