最近工做有用到一些多線程的東西,以前吧,有用到synchronized同步塊,不過是別人怎麼用就跟着用,並無搞清楚鎖的概念。最近也是遇到一些問題,不搞清楚鎖的概念,很容易碰壁,甚至有些時候本身連用沒用對都不知道。java
今天把一些疑惑都解開了,寫篇文章分享給你們,文章還算比較全面。固然可能有小寶鴿理解得不夠深刻透徹的地方,若是說得不正確還望指出。編程
看以前有必要跟某些猿友說一下,若是看一遍沒有看明白呢,也不要緊,當是瞭解一下,等真正使用到了,再回頭看。安全
本文主要是將synchronized關鍵字用法做爲例子來去解釋Java中的對象鎖和類鎖。特別的是但願能幫你們理清一些概念。多線程
synchronized關鍵字有以下兩種用法:異步
synchronized public void getValue() { System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); }
上面的代碼修飾的synchronized是非靜態方法,若是修飾的是靜態方法(static)含義是徹底不同的。具體不同在哪裏,後面會詳細說清楚。ide
synchronized static public void getValue() { System.out.println("getValue method thread name=" + Thread.currentThread().getName() + " username=" + username + " password=" + password); }
public void serviceMethod() { try { synchronized (this) { System.out.println("begin time=" + System.currentTimeMillis()); Thread.sleep(2000); System.out.println("end end=" + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } }
上面的代碼塊是synchronized (this)用法,還有synchronized (非this對象)以及synchronized (類.class)這兩種用法,這些使用方式的含義也是有根本的區別的。咱們先帶着這些問題繼續往下看。性能
小寶鴿彷佛並無辦法用清晰簡短的語言來描述對象鎖和類鎖的概念。即使能用簡單的語句概況,也會顯得抽象。猿友們耐心看完天然會明白。this
以前網上有找一些相關資料,有篇博客是這樣描述的(看的是轉載的,原創鏈接我也不知道):線程
一段synchronized的代碼被一個線程執行以前,他要先拿到執行這段代碼的權限, 在Java裏邊就是拿到某個同步對象的鎖(一個對象只有一把鎖); 若是這個時候同步對象的鎖被其餘線程拿走了,他(這個線程)就只能等了(線程阻塞在鎖池等待隊列中)。 取到鎖後,他就開始執行同步代碼(被synchronized修飾的代碼); 線程執行完同步代碼後立刻就把鎖還給同步對象,其餘在鎖池中等待的某個線程就能夠拿到鎖執行同步代碼了。 這樣就保證了同步代碼在統一時刻只有一個線程在執行。code
上面提到鎖,這裏先引出鎖的概念。先來看看下面這些囉嗦而必不可少的文字。
多線程的線程同步機制其實是靠鎖的概念來控制的。
在Java程序運行時環境中,JVM須要對兩類線程共享的數據進行協調:
這兩類數據是被全部線程共享的。 (程序不須要協調保存在Java 棧當中的數據。由於這些數據是屬於擁有該棧的線程所私有的。)
Java的普通對象存活在堆中。與棧不一樣,堆的空間不會隨着方法調用結束而清空。所以,在某個方法中建立的對象,能夠在方法調用結束以後,繼續存在於堆中。這帶來的一個問題是,若是咱們不斷的建立新的對象,內存空間將最終消耗殆盡。
在java虛擬機中,每一個對象和類在邏輯上都是和一個監視器相關聯的。 對於對象來講,相關聯的監視器保護對象的實例變量。
對於類來講,監視器保護類的類變量。
(若是一個對象沒有實例變量,或者一個類沒有變量,相關聯的監視器就什麼也不監視。) 爲了實現監視器的排他性監視能力,java虛擬機爲每個對象和類都關聯一個鎖。表明任什麼時候候只容許一個線程擁有的特權。線程訪問實例變量或者類變量不需鎖。
可是若是線程獲取了鎖,那麼在它釋放這個鎖以前,就沒有其餘線程能夠獲取一樣數據的鎖了。(鎖住一個對象就是獲取對象相關聯的監視器)
類鎖實際上用對象鎖來實現。當虛擬機裝載一個class文件的時候,它就會建立一個java.lang.Class類的實例。當鎖住一個對象的時候,實際上鎖住的是那個類的Class對象。
一個線程能夠屢次對同一個對象上鎖。對於每個對象,java虛擬機維護一個加鎖計數器,線程每得到一次該對象,計數器就加1,每釋放一次,計數器就減 1,當計數器值爲0時,鎖就被徹底釋放了。
java編程人員不須要本身動手加鎖,對象鎖是java虛擬機內部使用的。
在java程序中,只須要使用synchronized塊或者synchronized方法就能夠標誌一個監視區域。當每次進入一個監視區域時,java 虛擬機都會自動鎖上對象或者類。
看完了」2、Java中的對象鎖和類鎖」,咱們再來結合」1、synchronized關鍵字」裏面提到的synchronized用法。
事實上,synchronized修飾非靜態方法、同步代碼塊的synchronized (this)用法和synchronized (非this對象)的用法鎖的是對象,線程想要執行對應同步代碼,須要得到對象鎖。
synchronized修飾靜態方法以及同步代碼塊的synchronized (類.class)用法鎖的是類,線程想要執行對應同步代碼,須要得到類鎖。
所以,事實上synchronized關鍵字能夠細分爲上面描述的五種用法。
本文的實例均來自於《Java多線程編程核心技術》這本書裏面的例子。
public class Run { public static void main(String[] args) { HasSelfPrivateNum numRef = new HasSelfPrivateNum(); ThreadA athread = new ThreadA(numRef); athread.start(); ThreadB bthread = new ThreadB(numRef); bthread.start(); } } class HasSelfPrivateNum { private int num = 0; public void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over!"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over!"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class ThreadA extends Thread { private HasSelfPrivateNum numRef; public ThreadA(HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } @Override public void run() { super.run(); numRef.addI("a"); } } class ThreadB extends Thread { private HasSelfPrivateNum numRef; public ThreadB(HasSelfPrivateNum numRef) { super(); this.numRef = numRef; } @Override public void run() { super.run(); numRef.addI("b"); } }
運行結果爲:
a set over! b set over! b num=200 a num=200
修改HasSelfPrivateNum以下,方法用synchronized修飾以下:
class HasSelfPrivateNum { private int num = 0; synchronized public void addI(String username) { try { if (username.equals("a")) { num = 100; System.out.println("a set over!"); Thread.sleep(2000); } else { num = 200; System.out.println("b set over!"); } System.out.println(username + " num=" + num); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
運行結果是線程安全的:
b set over! b num=200 a set over! a num=100
實驗結論:兩個線程訪問同一個對象中的同步方法是必定是線程安全的。本實現因爲是同步訪問,因此先打印出a,而後打印出b
這裏線程獲取的是HasSelfPrivateNum的對象實例的鎖——對象鎖。
就上面的實例,咱們將Run改爲以下:
public class Run { public static void main(String[] args) { HasSelfPrivateNum numRef1 = new HasSelfPrivateNum(); HasSelfPrivateNum numRef2 = new HasSelfPrivateNum(); ThreadA athread = new ThreadA(numRef1); athread.start(); ThreadB bthread = new ThreadB(numRef2); bthread.start(); } }
運行結果爲:
a set over! b set over! b num=200 a num=200
這裏是非同步的,由於線程athread得到是numRef1的對象鎖,而bthread線程獲取的是numRef2的對象鎖,他們並無在獲取鎖上有競爭關係,所以,出現非同步的結果
這裏插播一下:同步不具備繼承性
咱們先看看代碼實例(Run.java)
public class Run { public static void main(String[] args) { ObjectService service = new ObjectService(); ThreadA a = new ThreadA(service); a.setName("a"); a.start(); ThreadB b = new ThreadB(service); b.setName("b"); b.start(); } } class ObjectService { public void serviceMethod() { try { synchronized (this) { System.out.println("begin time=" + System.currentTimeMillis()); Thread.sleep(2000); System.out.println("end end=" + System.currentTimeMillis()); } } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadA extends Thread { private ObjectService service; public ThreadA(ObjectService service) { super(); this.service = service; } @Override public void run() { super.run(); service.serviceMethod(); } } class ThreadB extends Thread { private ObjectService service; public ThreadB(ObjectService service) { super(); this.service = service; } @Override public void run() { super.run(); service.serviceMethod(); } }
運行結果:
begin time=1466148260341 end end=1466148262342 begin time=1466148262342 end end=1466148264378
這樣也是同步的,線程獲取的是同步塊synchronized (this)括號()裏面的對象實例的對象鎖,這裏就是ObjectService實例對象的對象鎖了。
須要注意的是synchronized (){}的{}先後的代碼依舊是異步的。
咱們先看看代碼實例(Run.java)
public class Run { public static void main(String[] args) { Service service = new Service("xiaobaoge"); ThreadA a = new ThreadA(service); a.setName("A"); a.start(); ThreadB b = new ThreadB(service); b.setName("B"); b.start(); } } class Service { String anyString = new String(); public Service(String anyString){ this.anyString = anyString; } public void setUsernamePassword(String username, String password) { try { synchronized (anyString) { System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入同步塊"); Thread.sleep(3000); System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開同步塊"); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } class ThreadA extends Thread { private Service service; public ThreadA(Service service) { super(); this.service = service; } @Override public void run() { service.setUsernamePassword("a", "aa"); } } class ThreadB extends Thread { private Service service; public ThreadB(Service service) { super(); this.service = service; } @Override public void run() { service.setUsernamePassword("b", "bb"); } }
不難看出,這裏線程爭奪的是anyString的對象鎖,兩個線程有競爭同一對象鎖的關係,出現同步
如今有一個問題:一個類裏面有兩個非靜態同步方法,會有影響麼?
答案是:若是對象實例A,線程1得到了對象A的對象鎖,那麼其餘線程就不能進入須要得到對象實例A的對象鎖才能訪問的同步代碼(包括同步方法和同步塊)。不理解能夠細細品味一下!
咱們直接看代碼實例:
public class Run { public static void main(String[] args) { ThreadA a = new ThreadA(); a.setName("A"); a.start(); ThreadB b = new ThreadB(); b.setName("B"); b.start(); } } class Service { synchronized public static void printA() { try { System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printA"); Thread.sleep(3000); System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printA"); } catch (InterruptedException e) { e.printStackTrace(); } } synchronized public static void printB() { System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printB"); System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printB"); } } class ThreadA extends Thread { @Override public void run() { Service.printA(); } } class ThreadB extends Thread { @Override public void run() { Service.printB(); } }
運行結果:
線程名稱爲:A在1466149372909進入printA 線程名稱爲:A在1466149375920離開printA 線程名稱爲:B在1466149375920進入printB 線程名稱爲:B在1466149375920離開printB
兩個線程在爭奪同一個類鎖,所以同步
對上面Service類代碼修改爲以下:
class Service { public static void printA() { synchronized (Service.class) { try { System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printA"); Thread.sleep(3000); System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printA"); } catch (InterruptedException e) { e.printStackTrace(); } } } public static void printB() { synchronized (Service.class) { System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printB"); System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printB"); } } }
運行結果:
線程名稱爲:A在1466149372909進入printA 線程名稱爲:A在1466149375920離開printA 線程名稱爲:B在1466149375920進入printB 線程名稱爲:B在1466149375920離開printB
兩個線程依舊在爭奪同一個類鎖,所以同步
須要特別說明:對於同一個類A,線程1爭奪A對象實例的對象鎖,線程2爭奪類A的類鎖,這二者不存在競爭關係。也就說對象鎖和類鎖互補干預內政
靜態方法則必定會同步,非靜態方法需在單例模式才生效,可是也不能都用靜態同步方法,總之用得很差可能會給性能帶來極大的影響。另外,有必要說一下的是Spring的bean默認是單例的。