国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

深入解析Java并發程序中線程的同步與線程鎖的使用

2019-11-26 14:31:43
字體:
來源:轉載
供稿:網友

synchronized關鍵字

synchronized,我們謂之鎖,主要用來給方法、代碼塊加鎖。當某個方法或者代碼塊使用synchronized時,那么在同一時刻至多僅有有一個線程在執行該段代碼。當有多個線程訪問同一對象的加鎖方法/代碼塊時,同一時間只有一個線程在執行,其余線程必須要等待當前線程執行完之后才能執行該代碼段。但是,其余線程是可以訪問該對象中的非加鎖代碼塊的。

synchronized主要包括兩種方法:synchronized 方法、synchronized 塊。

synchronized 方法

通過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。如:

public synchronized void getResult(); 
synchronized方法控制對類成員變量的訪問。它是如何來避免類成員變量的訪問控制呢?我們知道方法使用了synchronized關鍵字表明該方法已加鎖,在任一線程在訪問改方法時都必須要判斷該方法是否有其他線程在“獨占”。每個類實例對應一個把鎖,每個synchronized方法都必須調用該方法的類實例的鎖方能執行,否則所屬線程阻塞,方法一旦執行,就獨占該鎖,直到從該方法返回時才將鎖釋放,被阻塞的線程方能獲得該鎖。

其實synchronized方法是存在缺陷的,如果我們將一個很大的方法聲明為synchronized將會大大影響效率的。如果多個線程在訪問一個synchronized方法,那么同一時刻只有一個線程在執行該方法,而其他線程都必須等待,但是如果該方法沒有使用synchronized,則所有線程可以在同一時刻執行它,減少了執行的總時間。所以如果我們知道一個方法不會被多個線程執行到或者說不存在資源共享的問題,則不需要使用synchronized關鍵字。但是如果一定要使用synchronized關鍵字,那么我們可以synchronized代碼塊來替換synchronized方法。

synchronized 塊

synchronized代碼塊所起到的作用和synchronized方法一樣,只不過它使臨界區變的盡可能短了,換句話說:它只把需要的共享數據保護起來,其余的長代碼塊留出此操作。語法如下:

synchronized(object) {  //允許訪問控制的代碼 } 如果我們需要以這種方式來使用synchronized關鍵字,那么必須要通過一個對象引用來作為參數,通常這個參數我們常使用為this.synchronized (this) {  //允許訪問控制的代碼 } 

對于synchronized(this)有如下理解:

1、當兩個并發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程得到執行。另一個線程必須等待當前線程執行完這個代碼塊以后才能執行該代碼塊。

2、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另一個線程仍然可以訪問object中的非synchronized(this)同步代碼塊。

3、尤其關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其他線程對object中所有其他synchronized(this)同步代碼塊得訪問將被阻塞。

4、第三個例子同樣適用其他同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就獲得了這個object的對象鎖。結果,其他線程對該object對象所有同步代碼部分的訪問都將被暫時阻塞。


在java多線程中存在一個“先來后到”的原則,也就是說誰先搶到鑰匙,誰先用。我們知道為避免資源競爭產生問題,java使用同步機制來避免,而同步機制是使用鎖概念來控制的。那么在Java程序當中,鎖是如何體現的呢?這里我們需要弄清楚兩個概念:

什么是鎖?在日常生活中,它就是一個加在門、箱子、抽屜等物體上的封緘器,防止別人偷窺或者偷盜,起到一個保護的作用。在java中同樣如此,鎖對對象起到一個保護的作用,一個線程如果獨占了某個資源,那么其他的線程別想用,想用?等我用完再說吧!

在java程序運行環境中,JVM需要對兩類線程共享的數據進行協調:

1、保存在堆中的實例變量

2、保存在方法區中的類變量。

在java虛擬機中,每個對象和類在邏輯上都是和一個監視器相關聯的。對于對象來說,相關聯的監視器保護對象的實例變量。 對于類來說,監視器保護類的類變量。如果一個對象沒有實例變量,或者說一個類沒有變量,相關聯的監視器就什么也不監視。

為了實現監視器的排他性監視能力,java虛擬機為每一個對象和類都關聯一個鎖。代表任何時候只允許一個線程擁有的特權。線程訪問實例變量或者類變量不需鎖。 如果某個線程獲取了鎖,那么在它釋放該鎖之前其他線程是不可能獲取同樣鎖的。一個線程可以多次對同一個對象上鎖。對于每一個對象,java虛擬機維護一個加鎖計數器,線程每獲得一次該對象,計數器就加1,每釋放一次,計數器就減 1,當計數器值為0時,鎖就被完全釋放了。
java編程人員不需要自己動手加鎖,對象鎖是java虛擬機內部使用的。在java程序中,只需要使用synchronized塊或者synchronized方法就可以標志一個監視區域。當每次進入一個監視區域時,java 虛擬機都會自動鎖上對象或者類。

一個簡單的鎖

在使用synchronized時,我們是這樣使用鎖的:

public class ThreadTest {  public void test(){   synchronized(this){    //do something   }  } } 

synchronized可以確保在同一時間內只有一個線程在執行dosomething。下面是使用lock替代synchronized:

public class ThreadTest {  Lock lock = new Lock();  public void test(){   lock.lock();   //do something   lock.unlock();  } } 

lock()方法會對Lock實例對象進行加鎖,因此所有對該對象調用lock()方法的線程都會被阻塞,直到該Lock對象的unlock()方法被調用。


鎖的是什么?

在這個問題之前我們必須要明確一點:無論synchronized關鍵字加在方法上還是對象上,它取得的鎖都是對象。在java中每一個對象都可以作為鎖,它主要體現在下面三個方面:

對于同步方法,鎖是當前實例對象。
對于同步方法塊,鎖是Synchonized括號里配置的對象。


對于靜態同步方法,鎖是當前對象的Class對象。
首先我們先看下面例子:

public class ThreadTest_01 implements Runnable{   @Override  public synchronized void run() {   for(int i = 0 ; i < 3 ; i++){    System.out.println(Thread.currentThread().getName() + "run......");   }  }    public static void main(String[] args) {   for(int i = 0 ; i < 5 ; i++){    new Thread(new ThreadTest_01(),"Thread_" + i).start();   }  } } 

部分運行結果:

Thread_2run......Thread_2run......Thread_4run......Thread_4run......Thread_3run......Thread_3run......Thread_3run......Thread_2run......Thread_4run......

這個結果與我們預期的結果有點不同(這些線程在這里亂跑),照理來說,run方法加上synchronized關鍵字后,會產生同步效果,這些線程應該是一個接著一個執行run方法的。在上面LZ提到,一個成員方法加上synchronized關鍵字后,實際上就是給這個成員方法加上鎖,具體點就是以這個成員方法所在的對象本身作為對象鎖。但是在這個實例當中我們一共new了10個ThreadTest對象,那個每個線程都會持有自己線程對象的對象鎖,這必定不能產生同步的效果。所以:如果要對這些線程進行同步,那么這些線程所持有的對象鎖應當是共享且唯一的!

這個時候synchronized鎖住的是那個對象?它鎖住的就是調用這個同步方法對象。就是說threadTest這個對象在不同線程中執行同步方法,就會形成互斥。達到同步的效果。所以將上面的new Thread(new ThreadTest_01(),”Thread_” + i).start(); 修改為new Thread(threadTest,”Thread_” + i).start();就可以了。

對于同步方法,鎖是當前實例對象。

上面實例是使用synchronized方法,我們在看看synchronized代碼塊:

public class ThreadTest_02 extends Thread{   private String lock ;  private String name;    public ThreadTest_02(String name,String lock){   this.name = name;   this.lock = lock;  }    @Override  public void run() {   synchronized (lock) {    for(int i = 0 ; i < 3 ; i++){     System.out.println(name + " run......");    }   }  }    public static void main(String[] args) {   String lock = new String("test");   for(int i = 0 ; i < 5 ; i++){    new ThreadTest_02("ThreadTest_" + i,lock).start();   }  } } 

運行結果:

ThreadTest_0 run......ThreadTest_0 run......ThreadTest_0 run......ThreadTest_1 run......ThreadTest_1 run......ThreadTest_1 run......ThreadTest_4 run......ThreadTest_4 run......ThreadTest_4 run......ThreadTest_3 run......ThreadTest_3 run......ThreadTest_3 run......ThreadTest_2 run......ThreadTest_2 run......ThreadTest_2 run......

在main方法中我們創建了一個String對象lock,并將這個對象賦予每一個ThreadTest2線程對象的私有變量lock。我們知道java中存在一個字符串池,那么這些線程的lock私有變量實際上指向的是堆內存中的同一個區域,即存放main函數中的lock變量的區域,所以對象鎖是唯一且共享的。線程同步!!

在這里synchronized鎖住的就是lock這個String對象。

  對于同步方法塊,鎖是Synchonized括號里配置的對象。

public class ThreadTest_03 extends Thread{   public synchronized static void test(){   for(int i = 0 ; i < 3 ; i++){    System.out.println(Thread.currentThread().getName() + " run......");   }  }    @Override  public void run() {   test();  }   public static void main(String[] args) {   for(int i = 0 ; i < 5 ; i++){    new ThreadTest_03().start();   }  } } 

運行結果:

Thread-0 run......Thread-0 run......Thread-0 run......Thread-4 run......Thread-4 run......Thread-4 run......Thread-1 run......Thread-1 run......Thread-1 run......Thread-2 run......Thread-2 run......Thread-2 run......Thread-3 run......Thread-3 run......Thread-3 run......

在這個實例中,run方法使用的是一個同步方法,而且是static的同步方法,那么這里synchronized鎖的又是什么呢?我們知道static超脫于對象之外,它屬于類級別的。所以,對象鎖就是該靜態放發所在的類的Class實例。由于在JVM中,所有被加載的類都有唯一的類對象,在該實例當中就是唯一的 ThreadTest_03.class對象。不管我們創建了該類的多少實例,但是它的類實例仍然是一個!所以對象鎖是唯一且共享的。線程同步!!

對于靜態同步方法,鎖是當前對象的Class對象。

如果一個類中定義了一個synchronized的static函數A,也定義了一個synchronized的instance函數B,那么這個類的同一對象Obj,在多線程中分別訪問A和B兩個方法時,不會構成同步,因為它們的鎖都不一樣。A方法的鎖是Obj這個對象,而B的鎖是Obj所屬的那個Class。

鎖的升級

java中鎖一共有四種狀態,無鎖狀態,偏向鎖狀態,輕量級鎖狀態和重量級鎖狀態,它會隨著競爭情況逐漸升級。鎖可以升級但不能降級,意味著偏向鎖升級成輕量級鎖后不能降級成偏向鎖。這種鎖升級卻不能降級的策略,目的是為了提高獲得鎖和釋放鎖的效率。下面主要部分主要是對博客:聊聊并發(二)Java SE1.6中的Synchronized的總結。

鎖自旋

我們知道在當某個線程在進入同步方法/代碼塊時若發現該同步方法/代碼塊被其他現在所占,則它就要等待,進入阻塞狀態,這個過程性能是低下的。

在遇到鎖的爭用或許等待事,線程可以不那么著急進入阻塞狀態,而是等一等,看看鎖是不是馬上就釋放了,這就是鎖自旋。鎖自旋在一定程度上可以對線程進行優化處理。

偏向鎖

偏向鎖主要為了解決在沒有競爭情況下鎖的性能問題。在大多數情況下鎖鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,為了讓線程獲得鎖的代價更低而引入了偏向鎖。當某個線程獲得鎖的情況,該線程是可以多次鎖住該對象,但是每次執行這樣的操作都會因為CAS(CPU的Compare-And-Swap指令)操作而造成一些開銷消耗性能,為了減少這種開銷,這個鎖會偏向于第一個獲得它的線程,如果在接下來的執行過程中,該鎖沒有被其他的線程獲取,則持有偏向鎖的線程將永遠不需要再進行同步。

當有其他線程在嘗試著競爭偏向鎖時,持有偏向鎖的線程就會釋放鎖。

鎖膨脹

多個或多次調用粒度太小的鎖,進行加鎖解鎖的消耗,反而還不如一次大粒度的鎖調用來得高效。

輕量級鎖

輕量級鎖能提升程序同步性能的依據是“對于絕大部分的鎖,在整個同步周期內都是不存在競爭的”,這是一個經驗數據。輕量級鎖在當前線程的棧幀中建立一個名為鎖記錄的空間,用于存儲鎖對象目前的指向和狀態。如果沒有競爭,輕量級鎖使用CAS操作避免了使用互斥量的開銷,但如果存在鎖競爭,除了互斥量的開銷外,還額外發生了CAS操作,因此在有競爭的情況下,輕量級鎖會比傳統的重量級鎖更慢。

鎖的公平性

公平性的對立面是饑餓。那么什么是“饑餓”呢?如果一個線程因為其他線程在一直搶占著CPU而得不到CPU運行時間,那么我們就稱該線程被“饑餓致死”。而解決饑餓的方案則被稱之為“公平性”――所有線程均可以公平地獲得CPU運行機會。

導致線程饑餓主要有如下幾個原因:

高優先級線程吞噬所有的低優先級線程的CPU時間。我們可以為每個線程單獨設置其優先級,從1到10。優先級越高的線程獲得CPU的時間越多。對大多數應用來說,我們最好是不要改變其優先級值。

線程被永久堵塞在一個等待進入同步塊的狀態。java的同步代碼區是導致線程饑餓的重要因素。java的同步代碼塊并不會保證進入它的線程的先后順序。這就意味著理論上存在一個或者多個線程在試圖進入同步代碼區時永遠被堵塞著,因為其他線程總是不斷優于他獲得訪問權,導致它一直得到不到CPU運行機會被“饑餓致死”。

線程在等待一個本身也處于永久等待完成的對象。如果多個線程處在wait()方法執行上,而對其調用notify()不會保證哪一個線程會獲得喚醒,任何線程都有可能處于繼續等待的狀態。因此存在這樣一個風險:一個等待線程從來得不到喚醒,因為其他等待線程總是能被獲得喚醒。

為了解決線程“饑餓”的問題,我們可以使用鎖實現公平性。

鎖的可重入性

我們知道當線程請求一個由其它線程持有鎖的對象時,該線程會阻塞,但是當線程請求由自己持有鎖的對象時,是否可以成功呢?答案是可以成功的,成功的保障就是線程鎖的“可重入性”。

“可重入”意味著自己可以再次獲得自己的內部鎖,而不需要阻塞。如下:

public class Father {  public synchronized void method(){   //do something  } } public class Child extends Father{  public synchronized void method(){   //do something   super.method();  } } 

 
如果所是不可重入的,上面的代碼就會死鎖,因為調用child的method(),首先會獲取父類Father的內置鎖然后獲取Child的內置鎖,當調用父類的方法時,需要再次后去父類的內置鎖,如果不可重入,可能會陷入死鎖。

java多線程的可重入性的實現是通過每個鎖關聯一個請求計算和一個占有它的線程,當計數為0時,認為該鎖是沒有被占有的,那么任何線程都可以獲得該鎖的占有權。當某一個線程請求成功后,JVM會記錄該鎖的持有線程 并且將計數設置為1,如果這時其他線程請求該鎖時則必須等待。當該線程再次請求請求獲得鎖時,計數會+1;當占有線程退出同步代碼塊時,計數就會-1,直到為0時,釋放該鎖。這時其他線程才有機會獲得該鎖的占有權。

lock及其實現類

java.util.concurrent.locks提供了非常靈活鎖機制,為鎖定和等待條件提供一個框架的接口和類,它不同于內置同步和監視器,該框架允許更靈活地使用鎖定和條件。它的類結構圖如下:

20163285704787.jpg (783×548)

ReentrantLock:一個可重入的互斥鎖,為lock接口的主要實現。

ReentrantReadWriteLock:

ReadWriteLock:ReadWriteLock 維護了一對相關的鎖,一個用于只讀操作,另一個用于寫入操作。

Semaphore:一個計數信號量。

Condition:鎖的關聯條件,目的是允許線程獲取鎖并且查看等待的某一個條件是否滿足。

CyclicBarrier:一個同步輔助類,它允許一組線程互相等待,直到到達某個公共屏障點。

發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 铜山县| 隆德县| 永修县| 墨脱县| 峨边| 临夏市| 疏勒县| 高雄县| 措美县| 全州县| 万山特区| 游戏| 台南市| 昌图县| 河北省| 本溪| 沐川县| 永定县| 化隆| 宜都市| 万全县| 益阳市| 新和县| 宜兰市| 墨玉县| 桃园县| 无极县| 友谊县| 沭阳县| 衢州市| 连云港市| 博客| 肇庆市| 沂南县| 珲春市| 富川| 伊金霍洛旗| 崇信县| 牟定县| 尖扎县| 军事|