一区二区三区在线-一区二区三区亚洲视频-一区二区三区亚洲-一区二区三区午夜-一区二区三区四区在线视频-一区二区三区四区在线免费观看

服務器之家:專注于服務器技術及軟件下載分享
分類導航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術|正則表達式|C/C++|IOS|C#|Swift|Android|VB|R語言|JavaScript|易語言|vb.net|

服務器之家 - 編程語言 - Java教程 - 詳解Java同步—線程鎖和條件對象

詳解Java同步—線程鎖和條件對象

2021-05-21 10:55Kepler Java教程

在這篇文章中給大家詳細講述了Java同步—線程鎖和條件對象的相關知識點,有需要的讀者們可以參考下。

線程鎖和條件對象

在大多數多線程應用中,都是兩個及以上線程需要共享對同一數據的存取,所以有可能出現兩個線程同時訪問同一個資源的情況,這種情況叫做:競爭條件。

在java中為了解決并發的數據訪問問題,一般使用鎖這個概念來解決。

有幾種機制防止代碼收到并發訪問的干擾:

1.synchronized關鍵字(自動創建一個鎖及相關的條件)

2.reentrantlock類+java.util.concurrent包中的lock接口(在java5.0的時候引入)

reentrantlock的使用

?
1
2
3
4
5
6
7
8
9
10
11
12
public void method() {
    boolean flag = false;//標識條件
    reentrantlock locker = new reentrantlock();
    locker.lock();//開啟線程鎖
    try {
      //do some work...
    } catch (exception ex) {
 
    } finally {
      locker.unlock();//解鎖線程
    }
  }

locker.lock();確保只有一個線程進入臨界區,一旦一個線程進入之后,會獲得鎖對象,其他線程無法通過lock語句。當其他線程調用lock時,它們會被阻塞,知道第一個線程釋放鎖對象。

locker.unlock();解鎖操作,一定要放到finally里,因為如果try語句里出了問題,鎖必須被釋放,否則其他線程將永遠被阻塞

因為系統會隨機為線程分配資源,所以在線程獲得鎖對象之后,可能被系統剝奪運行權,這時候其他線程來訪問,但是發現有鎖,進不去,只能等拿到鎖對象的線程把里面的代碼執行完畢后,釋放鎖,第二個線程才能運行。

假設說做一個銀行轉賬的功能,線程鎖操作應該定義在銀行類的轉賬方法里,因為這樣每個銀行對象都有一個鎖對象,兩個線程訪問一個銀行對象的時候,那么鎖以串行方式提供服務。但是,如果每個線程訪問不同的銀行對象,每個線程都會得到不同的鎖對象,彼此之間不會沖突,所以就不會造成不必要的線程阻塞。

鎖是可重入的,線程可以重復獲得已經持有的鎖,鎖通過一個持有數量計數來跟蹤對lock方法的嵌套使用。

假設說,一個線程獲得鎖之后,要執行a方法,但是a方法里面又調用了b方法,這時候這個線程獲得了兩個鎖對象,當線程執行b方法的時候,也會被鎖死,防止其他線程亂入,當b方法執行完畢后,鎖對象變成了一個,當a方法也執行完畢的時候,鎖對象變成了0個,線程釋放鎖。

synchronized關鍵字

前面我們講了reentrantlock鎖對象的使用,但是在系統里面我們不一定要使用reentrantlock鎖,java中還提供了一個內部的隱式鎖,關鍵字是synchronized.

舉個例子:

?
1
2
3
public synchronized void method() {
  //do some work...
}

只需要在返回值前面加上synchronized鎖,就會實現上面reentrantlock鎖同樣的效果.

conditional條件對象

通常,線程拿到鎖對象之后,卻發現需要滿足某一條件才能繼續向下執行。

拿銀行程序來舉例子,我們需要轉賬方賬戶有足夠的資金才能轉出到目標賬戶,這時候需要用到reentrantlock對象,因為如果我們已經完成轉賬方賬戶有足夠的資金的判斷之后,線程被其他線程中斷,等其他線程執行完之后,轉賬方的錢又沒有了足夠的資金,這時候因為系統已經完成了判斷,所以會繼續向下執行,然后銀行系統就會出現問題。

舉例:

?
1
2
3
4
public void transfer(int from, int to, double amount) {
  if (accounts[from] > amount)//系統在結束判斷之后被剝奪運行權,然后賬戶通過網銀轉出所有錢,銀行涼涼
    dotransfer(from, to, amount);
}

這時候我們就需要使用reentrantlock對象了,我們修改一下代碼:

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
public void transfer(int from, int to, double amount) {
  reentrantlock locker = new reentrantlock();
  locker.lock();
  try {
    while (accounts[from] < amount) {
      //等待有足夠的錢
    }
    dotransfer(from, to, amount);
  } catch (exception ex) {
    ex.printstacktrace();
  } finally {
    locker.unlock();
  }
}

但是這樣又有了問題,當前線程獲取了鎖對象之后,開始執行代碼,發現錢不夠,進入等待狀態,然后其他線程又因為鎖的原因無法給該賬戶轉賬,就會一直進入等待狀態。

這個問題如何解決呢?

條件對象登場!

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public void transfer(int from, int to, double amount) {
  reentrantlock locker = new reentrantlock();
  condition sufficientfunds = locker.newcondition();//條件對象,
  lock.lock();
  try {
    while (accounts[from] < amount) {
      sufficientfunds.await();
      //等待有足夠的錢
    }
    dotransfer(from, to, amount);
    sufficientfunds.signalall();
  } catch (exception ex) {
    ex.printstacktrace();
  } finally {
    locker.unlock();
  }
}

條件對象的關鍵字是:condition,一個鎖對象可以有一個或多個相關的條件對象。可以通過鎖對象.newcondition方法獲得一個條件對象.

一般關于條件對象的命名需要能夠反映它表達的條件的名字,所以在這里我們叫他sufficientfund,表示余額充足的意思。

在進入鎖之前,我們創建一個條件,然后如果金額不足,在這里調用條件對象的await方法,通知系統當前線程進入掛起狀態,讓其他線程執行。這樣你這次調用會被鎖定,然后系統可以再次調用該方法給其他賬戶轉賬,當每一次轉賬完成后,執行轉賬操作的線程在底部調用signalall通知所有線程可以繼續運行了,因為我們有可能是轉足夠的錢給當前賬戶,這時候有可能該線程會繼續執行(不一定是你,是通知所有線程,如果通知的線程還是不符合條件,會繼續調用await方法,并完成轉賬操作,然后通知其他掛起的線程。

你說為啥不直接通知當前線程?不行,可以調用signal方法只通知一個線程,但是如果這個線程操作的賬戶還是沒錢(不是轉賬給這個賬戶的情況),那這個線程又進入等待了,這時候已經沒有線程能通知其他線程了,程序死鎖,所以還是用signal比較保險。

以上是使用reentrantlock+condition對象,那你說我要是使用synchronized隱式鎖怎么辦?

也可以,而且不需要

?
1
2
3
4
5
6
7
8
public void transfer(int from, int to, double amount) {
   while (accounts[from] < amount) {
      wait();//這個wait方法是定義在object類里面的,可以直接用,和條件對象的await一樣,掛起線程
      //等待有足夠的錢
    }
    dotransfer(from, to, amount);
    notifyall();//通知其他掛起的線程
}

object類里面定義了wait、notifyall、notify方法,對應await、signalall和signal方法,用來操作隱式鎖,synchronized只能有一個條件,而reentrantlock顯式聲明的鎖可以用綁定多個condition條件.

同步塊

除了我們上面講的兩種獲取線程鎖的方式,還有另外一種機制獲得鎖,這種方式比較特殊,叫做同步塊:

?
1
2
3
4
5
6
7
8
9
object locker = new object();
synchronized (locker) {
  //do some work
}
 
//也可以直接鎖當前類的對象
sychronized(this){
  //do some work
}

以上代碼會獲得object類型locker對象的鎖,這種鎖是一個特殊的鎖,在上面的代碼中,創建這個object類對象只是單純用來使用其持有的鎖.

這種機制叫做同步塊,應用場景也很廣:有的時候,我們并不是整個一個方法都需要同步,只是方法里的部分代碼塊需要同步,這種情況下,我們如果將這個方法聲明為synchronized,尤其是方法很大的時候,會造成很大的資源浪費。所以在這種情況下我們可以使用synchronized關鍵字來聲明同步塊:

?
1
2
3
4
5
6
public void method() {
  //do some work without synchronized
  synchronized (this) {
    //do some synchronized operation
  }
}

監視器的概念

鎖和條件是同步中一個很重要的工具,但是它們并不是面向對象的。多年來,java的研究人員努力尋找一種方法,可以在不需要考慮如何加鎖的情況下,就能保證多線程的安全性。最成功的的一個解決方案叫做monitor監視器,這個對象內置于每一個object變量中,相當于一個許可證。拿到許可證就可以進行操作,沒有拿到則需要阻塞等待。

監視器具有以下特性:

1.監視器是只包含私有域的類

2.每個監視器對象都有一個相關的鎖

3.使用監視器對象的鎖對所有的方法進行加鎖(舉個例子:如果調用obj.method方法,obj對象的鎖會在方法調用的時候自動獲得,當方法結束或返回之后會自動釋放該鎖。因為所有的域都是私有的,這樣可以確保一個線程在操作類對象的時候,沒有其他線程可以訪問里面的域)

4.該鎖對象可以有任意多個相關條件

你也可以自己創建一個監視器類,只要符合以上的要求即可。

其實我們使用的synchronized關鍵字就是使用了monitor來實現加鎖解鎖,所以又被稱為內部鎖。因為object類實現了監視器,所以對象又被內置于任何一個對象之中。這就是我們為什么可以使用synchronized(locker)的方式鎖定一個代碼塊了,其實只是用到了locker對象中內置的monitor而已。每一個對象的monitor類又是唯一的,所以就是唯一的許可證,拿到許可證的線程才可以執行,執行完后釋放對象的monitor才可以被其他線程獲取。

舉個例子:

?
1
2
3
synchronized (this) {
  //do some synchronized operation
}

它在字節碼文件中會被編譯為:

?
1
2
3
monitorenter;//get monitor,enter the synchronized block
      //do some synchronized operation
monitorexit;//leavel the synchronized block,release the monitor

死鎖

雖然有了線程可以保證原子性,但是鎖和條件不能解決多線程中的所有問題,舉個例子:

賬戶1余額:200

賬戶2余額:300

線程1:賬戶1→賬戶2(300)

線程2:賬戶2→賬戶1(400)

因為線程1和線程2的金額都不足以進行轉賬,所以兩個線程都阻塞了,這種狀態就叫死鎖(deadlock),如果所有線程死鎖,程序就卡死了。

為什么傾向于使用signalall和notifyall方式,如果假設使用signal和notify,

鎖測試和超時

線程在調用lock方法獲得另一個線程持有的鎖的時候,很可能發生阻塞。應該更加謹慎的申請鎖,trylock方法試圖申請一個鎖,如果申請成功,返回true,否則,立刻返回false,線程就會離開去做別的事,而不是被阻塞等待鎖對象。

語法:

?
1
2
3
4
5
6
7
8
9
10
11
12
reentrantlock locker = new reentrantlock();
if (locker.trylock()) {
  try {
    //do some work
  } catch (exception ex) {
    ex.printstacktrace();
  } finally {
    locker.unlock();
  }
} else {
  //do other work
}

也可以給其指定超時參數,單位有seconds、milliseconds、microseonds和manoseconds.

?
1
2
3
4
5
6
7
8
9
10
11
12
reentrantlock locker = new reentrantlock();
if (locker.trylock(1000, timeunit.milliseconds)) {
  try {
    //do some work
  } catch (exception ex) {
    ex.printstacktrace();
  } finally {
    locker.unlock();
  }
} else {
  //do other work
}

lock方法不能被中斷,如果一個線程在調用了lock方法后等待鎖的時候被中斷,中斷線程在獲得鎖之前一直處于阻塞狀態。

如果帶有超時參數的trylock方法,那么如果等待期間線程被中斷,會拋出interruptedexception異常,這是一個很好的特性,允許程序打破死鎖。

讀/寫鎖

reentrantlock類屬于java.util.concurrent.locks包,這個包底下還有一個reentrantreaderwriterlock類,如果使用多線程對數據讀的操作很多,但是寫的操作很少的話,可以使用這個類。

?
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
private reentrantreadwritelock rwl = new reentrantreadwritelock():
 
public void read() {
  lock readlocker = rwl.readlock();//創建讀取鎖對象
  readlocker.lock();//使用讀取鎖對象加鎖
  try {
    //do some work
  } catch (exception ex) {
    ex.printstacktrace();
  } finally {
    readlocker.unlock();
  }
}
 
public void write() {
  lock writelocker = rwl.writelock();//創建寫入鎖對象
  writelocker.lock();//使用寫入鎖對象加鎖
  try {
    //do some work
  } catch (exception ex) {
    ex.printstacktrace();
  } finally {
    writelocker.unlock();
  }
}

原文鏈接:https://www.cnblogs.com/Fill/p/9379776.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: 欧美肥胖老妇做爰变态 | 性欧美videosex18嫩 | 色老妈| 欧美成人免费草草影院视频 | 翁熄性放纵交换300章 | 精品国产一区二区 | 欧美成人tv在线观看免费 | 国产伦精品一区二区三区女 | 四虎免费永久观看 | 咪咪爱网友自拍 | 亚洲精品久久7777777 | 小SAO货叫大声点妓女 | 激情五月开心 | 日韩欧美高清视频 | 日本成熟bbxxxxxxxx| 久久久伊人影院 | 欧美a级在线 | 美女扒开胸罩露出奶了无遮挡免费 | 亚洲精品久久碰 | 91日本在线观看亚洲精品 | 操操小说 | 奇米影视7777 | 无码射肉在线播放视频 | 日本老妇和子乱视频 | 日韩一级片在线播放 | 翁熄性放纵交换300章 | 91制片厂果冻传媒首页 | 男女一级特黄a大片 | 香蕉久久一区二区三区 | meyd–456佐山爱在线播放 | 蜜桃影像传媒破解版 | 日本大片免aaa费观看视频 | 网友偷自拍原创区 | 爱福利视频一区二区 | 扒开胸流出吃奶 | 久99久热只有精品国产99 | 午夜性爽视频男人的天堂在线 | 国产精品国产三级国产专区不 | 精品国产欧美一区二区三区成人 | 美女脱了内裤让男生尿囗 | 好奇害死猫在线观看 |