激情久久久_欧美视频区_成人av免费_不卡视频一二三区_欧美精品在欧美一区二区少妇_欧美一区二区三区的

服務(wù)器之家:專(zhuān)注于服務(wù)器技術(shù)及軟件下載分享
分類(lèi)導(dǎo)航

PHP教程|ASP.NET教程|Java教程|ASP教程|編程技術(shù)|正則表達(dá)式|C/C++|IOS|C#|Swift|Android|VB|R語(yǔ)言|JavaScript|易語(yǔ)言|vb.net|

服務(wù)器之家 - 編程語(yǔ)言 - Java教程 - 淺談對(duì)java中鎖的理解

淺談對(duì)java中鎖的理解

2020-08-31 14:30落葉的博客 Java教程

本文主要講述java中鎖的相關(guān)知識(shí)。具有很好的參考價(jià)值,下面跟著小編一起來(lái)看下吧

在并發(fā)編程中,經(jīng)常遇到多個(gè)線程訪問(wèn)同一個(gè) 共享資源 ,這時(shí)候作為開(kāi)發(fā)者必須考慮如何維護(hù)數(shù)據(jù)一致性,在java中synchronized關(guān)鍵字被常用于維護(hù)數(shù)據(jù)一致性。synchronized機(jī)制是給共享資源上,只有拿到鎖的線程才可以訪問(wèn)共享資源,這樣就可以強(qiáng)制使得對(duì)共享資源的訪問(wèn)都是順序的,因?yàn)閷?duì)于共享資源屬性訪問(wèn)是必要也是必須的,下文會(huì)有具體示例演示。

一.java中的鎖

一般在java中所說(shuō)的鎖就是指的內(nèi)置鎖,每個(gè)java對(duì)象都可以作為一個(gè)實(shí)現(xiàn)同步的鎖,雖然說(shuō)在java中一切皆對(duì)象, 但是鎖必須是引用類(lèi)型的,基本數(shù)據(jù)類(lèi)型則不可以 。每一個(gè)引用類(lèi)型的對(duì)象都可以隱式的扮演一個(gè)用于同步的鎖的角色,執(zhí)行線程進(jìn)入synchronized塊之前會(huì)自動(dòng)獲得鎖,無(wú)論是通過(guò)正常語(yǔ)句退出還是執(zhí)行過(guò)程中拋出了異常,線程都會(huì)在放棄對(duì)synchronized塊的控制時(shí)自動(dòng)釋放鎖。 獲得鎖的唯一途徑就是進(jìn)入這個(gè)內(nèi)部鎖保護(hù)的同步塊或方法 。

正如引言中所說(shuō),對(duì)共享資源的訪問(wèn)必須是順序的,也就是說(shuō)當(dāng)多個(gè)線程對(duì)共享資源訪問(wèn)的時(shí)候,只能有一個(gè)線程可以獲得該共享資源的鎖,當(dāng)線程A嘗試獲取線程B的鎖時(shí),線程A必須等待或者阻塞,直到線程B釋放該鎖為止,否則線程A將一直等待下去,因此java內(nèi)置鎖也稱(chēng)作互斥鎖,也即是說(shuō)鎖實(shí)際上是一種互斥機(jī)制。

根據(jù)使用方式的不同一般我們會(huì)將鎖分為對(duì)象鎖和類(lèi)鎖,兩個(gè)鎖是有很大差別的,對(duì)象鎖是作用在實(shí)例方法或者一個(gè)對(duì)象實(shí)例上面的,而類(lèi)鎖是作用在靜態(tài)方法或者Class對(duì)象上面的。一個(gè)類(lèi)可以有多個(gè)實(shí)例對(duì)象,因此一個(gè)類(lèi)的對(duì)象鎖可能會(huì)有多個(gè),但是每個(gè)類(lèi)只有一個(gè)Class對(duì)象,所以類(lèi)鎖只有一個(gè)。 類(lèi)鎖只是一個(gè)概念上的東西,并不是真實(shí)存在的,它只是用來(lái)幫助我們理解鎖定的是實(shí)例方法還是靜態(tài)方法區(qū)別的 。

在java中實(shí)現(xiàn)鎖機(jī)制不僅僅限于使用synchronized關(guān)鍵字,還有JDK1.5之后提供的Lock,Lock不在本文討論范圍之內(nèi)。一個(gè)synchronized塊包含兩個(gè)部分:鎖對(duì)象的引用,以及這個(gè)鎖保護(hù)的代碼塊。如果作用在實(shí)例方法上面,鎖就是該方法所在的當(dāng)前對(duì)象,靜態(tài)synchronized方法會(huì)從Class對(duì)象上獲得鎖。

二.synchronized使用示例

1.多窗口售票

假設(shè)一個(gè)火車(chē)票售票系統(tǒng),有若干個(gè)窗口同時(shí)售票,很顯然在這里票是作為多個(gè)窗口的共享資源存在的,由于座位號(hào)是確定的,因此票上面的號(hào)碼也是確定的,我們用多個(gè)線程來(lái)模擬多個(gè)窗口同時(shí)售票,首先在不使用synchronized關(guān)鍵字的情況下測(cè)試一下售票情況。

先將票本身作為一個(gè)共享資源放在單獨(dú)的線程中,這種作為共享資源存在的線程很顯然應(yīng)該是實(shí)現(xiàn)Runnable接口,我們將票的總數(shù)num作為一個(gè)入?yún)魅耄看紊梢粋€(gè)票之后將num做減法運(yùn)算,直至num為0即停止,說(shuō)明票已經(jīng)售完了,然后開(kāi)啟多個(gè)線程將票資源傳入。

?
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
26
27
28
29
30
31
32
33
34
35
36
37
public class Ticket implements Runnable{
   private int num;//票數(shù)量
   private boolean flag=true;//若為false則售票停止
   public Ticket(int num){
   this.num=num;
   }
   @Override
   public void run() {
   while(flag){
   ticket();
   }
   }
   private void ticket(){
   if(num<=0){
   flag=false;
   return;
   }
   try {
   Thread.sleep(20);//模擬延時(shí)操作
   } catch (InterruptedException e) {
   e.printStackTrace();
   }
   //輸出當(dāng)前窗口號(hào)以及出票序列號(hào)
   System.out.println(Thread.currentThread().getName()+"售出票序列號(hào):"+num--);
   }
  }
  public class MainTest {
   public static void main(String[] args) {
   Ticketticket = new Ticket(5);
   Threadwindow01 = new Thread(ticket, "窗口01");
   Threadwindow02 = new Thread(ticket, "窗口02");
   Threadwindow03 = new Thread(ticket, "窗口03");
   window01.start();
   window02.start();
   window03.start();
   }
  }

 程序的輸出結(jié)果如下:

?
1
2
3
4
5
6
7
8
9
窗口02售出票序列號(hào):5
窗口03售出票序列號(hào):4
窗口01售出票序列號(hào):5
窗口02售出票序列號(hào):3
窗口01售出票序列號(hào):2
窗口03售出票序列號(hào):2
窗口02售出票序列號(hào):1
窗口03售出票序列號(hào):0
窗口01售出票序列號(hào):-1

從上面程序運(yùn)行結(jié)果可以看出不但票的序號(hào)有重號(hào)而且出票數(shù)量也不對(duì),這種售票系統(tǒng)比12306可要爛多了,人家在繁忙的時(shí)候只是刷不到票而已,而這里的售票系統(tǒng)倒好了,出票比預(yù)計(jì)的多了而且會(huì)出現(xiàn)多個(gè)人爭(zhēng)搶做同一個(gè)座位的風(fēng)險(xiǎn)。如果是單個(gè)售票窗口是不會(huì)出現(xiàn)這種問(wèn)題,多窗口同時(shí)售票就會(huì)出現(xiàn)爭(zhēng)搶共享資源因此紊亂的現(xiàn)象,解決該現(xiàn)象也很簡(jiǎn)單,就是在ticket()方法前面加上synchronized關(guān)鍵字或者將ticket()方法的方法體完全用synchronized塊包括起來(lái)。

?
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
26
27
28
//方式一
  private synchronized void ticket(){
   if(num<=0){
   flag=false;
   return;
   }
   try {
   Thread.sleep(20);//模擬延時(shí)操作
   } catch (InterruptedException e) {
   e.printStackTrace();
   }
   System.out.println(Thread.currentThread().getName()+"售出票序列號(hào):"+num--);
  }
  //方式二
  private void ticket(){
   synchronized (this) {
   if (num <= 0) {
   flag = false;
   return;
   }
   try {
   Thread.sleep(20);//模擬延時(shí)操作
   } catch (InterruptedException e) {
   e.printStackTrace();
   }
   System.out.println(Thread.currentThread().getName() + "售出票序列號(hào):" + num--);
   }
  }

再看一下加入synchronized關(guān)鍵字的程序運(yùn)行結(jié)果:

?
1
2
3
4
5
窗口01售出票序列號(hào):5
窗口03售出票序列號(hào):4
窗口03售出票序列號(hào):3
窗口02售出票序列號(hào):2
窗口02售出票序列號(hào):1

從這里可以看出在實(shí)例方法上面加上synchronized關(guān)鍵字的實(shí)現(xiàn)效果跟對(duì)整個(gè)方法體加上synchronized效果是一樣的。 另外一點(diǎn)需要注意加鎖的時(shí)機(jī)也非常重要 ,本示例中ticket()方法中有兩處操作容易出現(xiàn)紊亂,一個(gè)是在if語(yǔ)句模塊,一處是在num–,這兩處操作本身都不是原子類(lèi)型的操作,但是在使用運(yùn)行的時(shí)候需要這兩處當(dāng)成一個(gè)整體操作,所以synchronized將整個(gè)方法體都包裹在了一起。如若不然,假設(shè)num當(dāng)前值是1,但是窗口01執(zhí)行到了num–,整個(gè)操作還沒(méi)執(zhí)行完成,只進(jìn)行了賦值運(yùn)算還沒(méi)進(jìn)行自減運(yùn)算,但是窗口02已經(jīng)進(jìn)入到了if語(yǔ)句模塊,此時(shí)num還是等于1,等到窗口02執(zhí)行到了輸出語(yǔ)句的時(shí)候,窗口01的num–也已經(jīng)將自減運(yùn)算執(zhí)行完成,這時(shí)候窗口02就會(huì)輸出序列號(hào)0的票。再者如果將synchronized關(guān)鍵字加在了run方法上面,這時(shí)候的操作不會(huì)出現(xiàn)紊亂或者錯(cuò)誤,但是這種加鎖方式無(wú)異于單窗口操作,當(dāng)窗口01拿到鎖進(jìn)入run()方法之后,必須等到flag為false才會(huì)將語(yǔ)句執(zhí)行完成跳出循環(huán),這時(shí)候的num就已經(jīng)為0了,也就是說(shuō)票已經(jīng)被售賣(mài)完了,這種方式摒棄了多線程操作,違背了最初的設(shè)計(jì)原則-多窗口售票。

2.懶漢式單例模式

創(chuàng)建單例模式有很多中實(shí)現(xiàn)方式,本文只討論懶漢式創(chuàng)建。在Android開(kāi)發(fā)過(guò)程中單例模式可以說(shuō)是最常使用的一種設(shè)計(jì)模式,因?yàn)樗僮骱?jiǎn)單還可以有效減少內(nèi)存溢出。下面是懶漢式創(chuàng)建單例模式一個(gè)示例:

?
1
2
3
4
5
6
7
8
9
10
11
public class Singleton {
   private static Singletoninstance;
   private Singleton() {
   }
   public static SingletongetInstance() {
   if (instance == null) {
   instance = new Singleton();
   }
   return instance;
   }
  }

如果對(duì)于多窗口售票邏輯已經(jīng)完全明白了的話就可以看出這里的實(shí)現(xiàn)方式是有問(wèn)題的,我們可以簡(jiǎn)單的創(chuàng)建幾個(gè)線程來(lái)獲取單例輸出對(duì)象的hascode值。

?
1
2
3
com.sunny.singleton.Singleton@15c330aa
com.sunny.singleton.Singleton@15c330aa
com.sunny.singleton.Singleton@41aff40f

在多線程模式下發(fā)現(xiàn)會(huì)出現(xiàn)不同的對(duì)象,這種單例模式很顯然不是我們想要的,那么根據(jù)上面多窗口售票的邏輯我們?cè)趃etInstance()方法上面加上一個(gè)synchronized關(guān)鍵字,給該方法加上鎖,加上鎖之后可以避免多線程模式下生成多個(gè)不同對(duì)象,但是同樣會(huì)帶來(lái)一個(gè)效率問(wèn)題,因?yàn)椴还苣膫€(gè)線性進(jìn)入getInstance()方法都會(huì)先獲得鎖,然后再次釋放鎖,這是一個(gè)方面,另一個(gè)方面就是只有在第一次調(diào)用getInstance()方法的時(shí)候,也就是在if語(yǔ)句塊內(nèi)才會(huì)出現(xiàn)多線程并發(fā)問(wèn)題,而我們卻索性將整個(gè)方法都上鎖了。討論到這里就引出了另外一個(gè)問(wèn)題,究竟是synchronized方法好還是synchronized代碼塊好呢? 有一個(gè)原則就是鎖的范圍越小越好 ,加鎖的目的就是將鎖進(jìn)去的代碼作為原子性操作,因?yàn)榉窃硬僮鞫疾皇蔷€程安全的,因此synchronized代碼塊應(yīng)該是在開(kāi)發(fā)過(guò)程中優(yōu)先考慮使用的加鎖方式。

?
1
2
3
4
5
6
7
8
public static SingletongetInstance() {
   if (instance == null) {
   synchronized (Singleton.class) {
   instance = new Singleton();
   }
   }
   return instance;
  }

這里也會(huì)遇到類(lèi)似上面的問(wèn)題,多線程并發(fā)下回生成多個(gè)實(shí)例,如線程A和線程B都進(jìn)入if語(yǔ)句塊,假設(shè)線程A先獲得鎖,線程B則等待,當(dāng)new一個(gè)實(shí)例后,線程A釋放鎖,線程B獲得鎖后會(huì)再次執(zhí)行new語(yǔ)句,同樣不能保證單例要求,那么下面代碼再來(lái)一個(gè)null判斷,進(jìn)行雙重檢查上鎖呢?

?
1
2
3
4
5
6
7
8
9
10
public static SingletongetInstance() {
   if (instance == null) {
   synchronized (Singleton.class) {
   if(instance==null){
   instance = new Singleton();
   }
   }
   }
   return instance;
  }

該模式就是雙重檢查上鎖實(shí)現(xiàn)的單例模式,這里在代碼層面我們已經(jīng) 基本 保證了線程安全了,但是還是有問(wèn)題的, 雙重檢查鎖定的問(wèn)題是:并不能保證它會(huì)在單處理器或多處理器計(jì)算機(jī)上順利運(yùn)行。雙重檢查鎖定失敗的問(wèn)題并不歸咎于 JVM 中的實(shí)現(xiàn)bug,而是歸咎于java平臺(tái)內(nèi)存模型。內(nèi)存模型允許所謂的“無(wú)序?qū)懭?rdquo;,這也是這些習(xí)語(yǔ)失敗的一個(gè)主要原因。 更為詳細(xì)的介紹可以參考 Java單例模式中雙重檢查鎖的問(wèn)題 。所以單例模式創(chuàng)建比較建議使用惡漢式創(chuàng)建或者靜態(tài)內(nèi)部類(lèi)方式創(chuàng)建。

3.synchronized不具有繼承性

我們可以通過(guò)一個(gè)簡(jiǎn)單的demo驗(yàn)證這個(gè)問(wèn)題,在一個(gè)方法中順序的輸出一系列數(shù)字,并且輸出該數(shù)字所在的線程名稱(chēng),在父類(lèi)中加上synchronized關(guān)鍵字,子類(lèi)重寫(xiě)父類(lèi)方法測(cè)試一下加上synchronized關(guān)鍵字和不加關(guān)鍵字的區(qū)別即可。

?
1
2
3
4
5
6
7
8
9
10
11
12
public class Parent {
   public synchronized void test() {
   for (int i = 0; i < 5; i++) {
   System.out.println("Parent " + Thread.currentThread().getName() + ":" + i);
   try {
   Thread.sleep(500);
   } catch (InterruptedException e) {
   e.printStackTrace();
   }
   }
   }
  }

子類(lèi)繼承父類(lèi)Parent,重寫(xiě)test()方法.

?
1
2
3
4
5
6
7
8
9
10
11
12
13
public class Child extends Parent {
   @Override
   public void test() {
   for (int i = 0; i < 5; i++) {
   System.out.println("Child " + Thread.currentThread().getName() + ":" + i);
   try {
   Thread.sleep(500);
   } catch (InterruptedException e) {
   e.printStackTrace();
   }
   }
   }
  }

測(cè)試代碼如下:

?
1
2
3
4
5
6
7
8
9
10
11
final Child c = new Child();
  new Thread() {
   public void run() {
   c.test();
   };
  }.start();
  new Thread() {
   public void run() {
   c.test();
   };
  }.start();

 輸出結(jié)果如下:

?
1
2
3
4
5
6
7
8
9
10
Parent Thread-0:0 Child Thread-0:0
Parent Thread-0:1 Child Thread-1:0
Parent Thread-0:2 Child Thread-0:1
Parent Thread-0:3 Child Thread-1:1
Parent Thread-0:4 Child Thread-0:2
Parent Thread-1:0 Child Thread-1:2
Parent Thread-1:1 Child Thread-0:3
Parent Thread-1:2 Child Thread-1:3
Parent Thread-1:3 Child Thread-0:4
Parent Thread-1:4 Child Thread-1:4

通過(guò)輸出信息可以知道,父類(lèi)Parent中會(huì)將單個(gè)線程中序列號(hào)輸出完成才會(huì)執(zhí)行另一個(gè)線程中代碼,但是子類(lèi)Child中確是兩個(gè)線程交替輸出數(shù)字,所以synchronized不具有繼承性。

4.死鎖示例

死鎖是多線程開(kāi)發(fā)中比較常見(jiàn)的一個(gè)問(wèn)題。若有多個(gè)線程訪問(wèn)多個(gè)資源時(shí),相互之間存在競(jìng)爭(zhēng),就容易出現(xiàn)死鎖。下面就是一個(gè)死鎖的示例,當(dāng)一個(gè)線程等待另一個(gè)線程持有的鎖時(shí),而另一個(gè)線程也在等待該線程鎖持有的鎖,這時(shí)候兩個(gè)線程都會(huì)處于阻塞狀態(tài),程序便出現(xiàn)死鎖。

?
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
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
public class Thread01 extends Thread{
   private Object resource01;
   private Object resource02;
   public Thread01(Object resource01, Object resource02) {
   this.resource01 = resource01;
   this.resource02 = resource02;
   }
   @Override
   public void run() {
   synchronized(resource01){
   System.out.println("Thread01 locked resource01");
   try {
   Thread.sleep(500);
   } catch (InterruptedException e) {
   e.printStackTrace();
   }
   synchronized (resource02) {
   System.out.println("Thread01 locked resource02");
   }
   }
   }
  }
  public class Thread02 extends Thread{
   private Object resource01;
   private Object resource02;
   public Thread02(Object resource01, Object resource02) {
   this.resource01 = resource01;
   this.resource02 = resource02;
   }
   @Override
   public void run() {
   synchronized(resource02){
   System.out.println("Thread02 locked resource02");
   try {
   Thread.sleep(500);
   } catch (InterruptedException e) {
   e.printStackTrace();
   }
   synchronized (resource01) {
   System.out.println("Thread02 locked resource01");
   }
   }
   }
  }
  public class MainTest {
   public static void main(String[] args) {
   final Object resource01="resource01";
   final Object resource02="resource02";
   Thread01thread01=new Thread01(resource01, resource02);
   Thread02thread02=new Thread02(resource01, resource02);
   thread01.start();
   thread02.start();
   }
  }

執(zhí)行上面的程序就會(huì)一直等待下去,出現(xiàn)死鎖。當(dāng)線程Thread01獲得resource01的鎖后,等待500ms,然后嘗試獲取resource02的鎖,但是此時(shí)resouce02鎖已經(jīng)被Thread02持有,同樣Thread02也等待了500ms嘗試獲取resouce01鎖,但是該所已經(jīng)被Thread01持有,這樣兩個(gè)線程都在等待對(duì)方所有的資源,造成了死鎖。

三.其它

關(guān)鍵字synchronized具有鎖重入功能,當(dāng)一個(gè)線程已經(jīng)持有一個(gè)對(duì)象鎖后,再次請(qǐng)求該對(duì)象鎖時(shí)是可以得到該對(duì)象的鎖的,這種方式是必須的,否則在一個(gè)synchronized方法內(nèi)部就沒(méi)有辦法調(diào)用該對(duì)象的另外一個(gè)synchronized方法了。鎖重入是通過(guò)為每個(gè)所關(guān)聯(lián)一個(gè)計(jì)數(shù)器和一個(gè)占有它的線程,當(dāng)計(jì)數(shù)器為0時(shí),認(rèn)為鎖是未被占有的。線程請(qǐng)求一個(gè)未被占有的鎖時(shí),JVM會(huì)記錄鎖的占有者,并將計(jì)數(shù)器設(shè)置為1。如果同一個(gè)線程再次請(qǐng)求該鎖,計(jì)數(shù)器會(huì)遞增,每次占有的線程退出同步代碼塊時(shí)計(jì)數(shù)器會(huì)遞減,直至減為0時(shí)鎖才會(huì)被釋放。

在聲明一個(gè)對(duì)象作為鎖的時(shí)候要注意字符串類(lèi)型鎖對(duì)象,因?yàn)樽址幸粋€(gè)常量池,如果不同的線程持有的鎖是具有相同字符的字符串鎖時(shí),兩個(gè)鎖實(shí)際上同一個(gè)鎖。

以上就是本文的全部?jī)?nèi)容,希望本文的內(nèi)容對(duì)大家的學(xué)習(xí)或者工作能帶來(lái)一定的幫助,同時(shí)也希望多多支持服務(wù)器之家!

原文鏈接:http://www.cnblogs.com/wangyayun/p/6593446.html

延伸 · 閱讀

精彩推薦
主站蜘蛛池模板: www日韩在线 | 黑人一区二区三区四区五区 | 一区二区三区日韩在线观看 | 欧美日韩精品一区二区三区在线观看 | 亚洲精品久久久久久久久久久 | 国产一级不卡毛片 | 把娇妻调教成暴露狂 | 久久久久久久爱 | 精品一区二区免费 | 毛片大全 | 92看片淫黄大片欧美看国产片 | 午夜电影视频 | 免费日韩片 | 操操电影 | 成年人免费高清视频 | 姑娘第5集高清在线观看 | 久久国产精品影视 | 国产精品一品二区三区四区18 | 西川av在线一区二区三区 | 欧产日产国产精品乱噜噜 | 精品999www | www.成人免费视频 | 天天看夜夜爽 | 国产69久久精品成人看 | 欧美一区二区三区免费电影 | 久久精品中文字幕一区二区三区 | 亚洲成人网一区 | 国产一区二区三区影视 | 中文字幕在线观看成人 | 九九视屏 | 久夜草| 草逼一区| 在线看一区二区三区 | 日韩在线毛片 | 久色乳综合思思在线视频 | 免费a观看 | 久久精品亚洲精品国产欧美kt∨ | www中文在线| 欧美精品一区自拍a毛片在线视频 | 精品三级内地国产在线观看 | 久久精品欧美一区二区三区不卡 |