在咱們的實際應用當中可能常常會遇到這樣一個場景:多個線程讀或者、寫相同的數據,訪問相同的文件等等。對於這種狀況若是咱們不加以控制,是很是容易致使錯誤的。在java中,爲了解決這個問題,引入臨界區概念。所謂臨界區是指一個訪問共用資源的程序片斷,而這些共用資源又沒法同時被多個線程訪問。html
在java中爲了實現臨界區提供了同步機制。當一個線程試圖訪問一個臨界區時,他將使用一種同步機制來查看是否是已經有其餘線程進入臨界區。若是沒有則他就能夠進入臨界區,不然他就會被同步機制掛起,指定進入的線程離開這個臨界區。java
臨界區規定:每次只准許一個進程進入臨界區,進入後不容許其餘進程進入。調度法則爲(百度百科):編程
一、若是有若干進程要求進入空閒的臨界區,一次僅容許一個進程進入。 多線程
二、任什麼時候候,處於臨界區內的進程不可多於一個。如已有進程進入本身的臨界區,則其它全部試圖進入臨界區的進程必須等待。 併發
三、進入臨界區的進程要在有限時間內退出,以便其它進程能及時進入本身的臨界區。 ide
四、若是進程不能進入本身的臨界區,則應讓出CPU,避免進程出現「忙等」現象。 函數
下面介紹使用synchronized關鍵字來實現同步機制。性能
synchronized,咱們謂之鎖,主要用來給方法、代碼塊加鎖。當某個方法或者代碼塊使用synchronized時,那麼在同一時刻至多僅有有一個線程在執行該段代碼。當有多個線程訪問同一對象的加鎖方法/代碼塊時,同一時間只有一個線程在執行,其他線程必需要等待當前線程執行完以後才能執行該代碼段。可是,其他線程是能夠訪問該對象中的非加鎖代碼塊的。優化
synchronized主要包括兩種方法:synchronized 方法、synchronized 塊。this
經過在方法聲明中加入 synchronized關鍵字來聲明 synchronized 方法。如:
public synchronized void getResult();
synchronized方法控制對類成員變量的訪問。它是如何來避免類成員變量的訪問控制呢?咱們知道方法使用了synchronized關鍵字代表該方法已加鎖,在任一線程在訪問改方法時都必需要判斷該方法是否有其餘線程在「獨佔」。每一個類實例對應一個把鎖,每一個synchronized方法都必須調用該方法的類實例的鎖方能執行,不然所屬線程阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時纔將鎖釋放,被阻塞的線程方能得到該鎖。
其實synchronized方法是存在缺陷的,若是咱們將一個很大的方法聲明爲synchronized將會大大影響效率的。若是多個線程在訪問一個synchronized方法,那麼同一時刻只有一個線程在執行該方法,而其餘線程都必須等待,可是若是該方法沒有使用synchronized,則全部線程能夠在同一時刻執行它,減小了執行的總時間。因此若是咱們知道一個方法不會被多個線程執行到或者說不存在資源共享的問題,則不須要使用synchronized關鍵字。可是若是必定要使用synchronized關鍵字,那麼咱們能夠synchronized代碼塊來替換synchronized方法。
synchronized代碼塊所起到的做用和synchronized方法同樣,只不過它使臨界區變的儘量短了,換句話說:它只把須要的共享數據保護起來,其他的長代碼塊留出此操做。語法以下:
synchronized(object) { //容許訪問控制的代碼 }
若是咱們須要以這種方式來使用synchronized關鍵字,那麼必需要經過一個對象引用來做爲參數,一般這個參數咱們常使用爲this.
synchronized (this) { //容許訪問控制的代碼 }
對於synchronized(this)有以下理解:
一、當兩個併發線程訪問同一個對象object中的這個synchronized(this)同步代碼塊時,一個時間內只能有一個線程獲得執行。另外一個線程必須等待當前線程執行完這個代碼塊之後才能執行該代碼塊。
二、然而,當一個線程訪問object的一個synchronized(this)同步代碼塊時,另外一個線程仍然能夠訪問object中的非synchronized(this)同步代碼塊。
三、尤爲關鍵的是,當一個線程訪問object的一個synchronized(this)同步代碼塊時,其餘線程對object中全部其餘synchronized(this)同步代碼塊得訪問將被阻塞。
四、第三個例子一樣適用其餘同步代碼塊。也就是說,當一個線程訪問object的一個synchronized(this)同步代碼塊時,它就得到了這個object的對象鎖。結果,其餘線程對該object對象全部同步代碼部分的訪問都將被暫時阻塞。
五、以上規則對其餘對象鎖一樣適用
http://freewxy.iteye.com/blog/978159,這篇博客使用實例對上面四點進行了較爲詳細的說明,這裏就很少闡述了。
http://www.cnblogs.com/GnagWang/archive/2011/02/27/1966606.html這篇博客對synchronized的使用舉了一個很不錯的例子(拿鑰匙進房間)。這裏因爲篇幅問題LZ就很少闡述了,下面咱們來刨刨synchronized稍微高級點的東西。
在java多線程中存在一個「先來後到」的原則,也就是說誰先搶到鑰匙,誰先用。咱們知道爲避免資源競爭產生問題,java使用同步機制來避免,而同步機制是使用鎖概念來控制的。那麼在Java程序當中,鎖是如何體現的呢?這裏咱們須要弄清楚兩個概念:
什麼是鎖?在平常生活中,它就是一個加在門、箱子、抽屜等物體上的封緘器,防止別人偷窺或者偷盜,起到一個保護的做用。在java中一樣如此,鎖對對象起到一個保護的做用,一個線程若是獨佔了某個資源,那麼其餘的線程別想用,想用?等我用完再說吧!
在java程序運行環境中,JVM須要對兩類線程共享的數據進行協調:
一、保存在堆中的實例變量
二、保存在方法區中的類變量。
在java虛擬機中,每一個對象和類在邏輯上都是和一個監視器相關聯的。對於對象來講,相關聯的監視器保護對象的實例變量。 對於類來講,監視器保護類的類變量。若是一個對象沒有實例變量,或者說一個類沒有變量,相關聯的監視器就什麼也不監視。
爲了實現監視器的排他性監視能力,java虛擬機爲每個對象和類都關聯一個鎖。表明任什麼時候候只容許一個線程擁有的特權。線程訪問實例變量或者類變量不需鎖。 若是某個線程獲取了鎖,那麼在它釋放該鎖以前其餘線程是不可能獲取一樣鎖的。一個線程能夠屢次對同一個對象上鎖。對於每個對象,java虛擬機維護一個加鎖計數器,線程每得到一次該對象,計數器就加1,每釋放一次,計數器就減 1,當計數器值爲0時,鎖就被徹底釋放了。
java編程人員不須要本身動手加鎖,對象鎖是java虛擬機內部使用的。在java程序中,只須要使用synchronized塊或者synchronized方法就能夠標誌一個監視區域。當每次進入一個監視區域時,java 虛擬機都會自動鎖上對象或者類。(摘自java的鎖機制)。
在這個問題以前咱們必需要明確一點:不管synchronized關鍵字加在方法上仍是對象上,它取得的鎖都是對象。在java中每個對象均可以做爲鎖,它主要體如今下面三個方面:
首先咱們先看下面例子:
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操做,所以在有競爭的狀況下,輕量級鎖會比傳統的重量級鎖更慢。
一、《Java 7 併發編程實戰手冊》
三、聊聊併發(二)Java SE1.6中的Synchronized
四、java的鎖機制