synchronized關鍵字有以下幾種用法:javascript
下面對上述兩種狀況進行區分。java
多線程的線程同步機制其實是靠鎖的概念來控制的。編程
在Java程序運行時環境中,JVM須要對兩類線程共享的數據進行協調:安全
這兩類數據是被全部線程共享的。 (程序不須要協調保存在Java 棧當中的數據。由於這些數據是屬於擁有該棧的線程所私有的) 下面說說在虛擬機中內存的分配:多線程
Java的普通對象存活在堆中。與棧不一樣,堆的空間不會隨着方法調用結束而清空。所以,在某個方法中建立的對象,能夠在方法調用結束以後,繼續存在於堆中。這帶來的一個問題是,若是咱們不斷的建立新的對象,內存空間將最終消耗殆盡。異步
在java虛擬機中,每一個對象和類在邏輯上都是和一個監視器相關聯的。ide
(若是一個對象沒有實例變量,或者一個類沒有變量,相關聯的監視器就什麼也不監視。)性能
爲了實現監視器的排他性監視能力,java虛擬機爲每個對象和類都關聯一個鎖。表明任什麼時候候只容許一個線程擁有的特權。線程訪問實例變量或者類變量不需鎖。this
可是若是線程獲取了鎖,那麼在它釋放這個鎖以前,就沒有其餘線程能夠獲取一樣數據的鎖了。(鎖住一個對象就是獲取對象相關聯的監視器)spa
類鎖實際上用對象鎖來實現。當虛擬機裝載一個class文件的時候,它就會建立一個java.lang.Class類的實例。當鎖住一個對象的時候,實際上鎖住的是那個類的Class對象。
一個線程能夠屢次對同一個對象上鎖。對於每個對象,java虛擬機維護一個加鎖計數器,線程每得到一次該對象,計數器就加1,每釋放一次,計數器就減 1,當計數器值爲0時,鎖就被徹底釋放了。
java編程人員不須要本身動手加鎖,對象鎖是java虛擬機內部使用的。
在java程序中,只須要使用synchronized塊或者synchronized方法就能夠標誌一個監視區域。當每次進入一個監視區域時,java 虛擬機都會自動鎖上對象或者類。
synchronized修飾非靜態方法、同步代碼塊的synchronized (this)用法和synchronized (非this對象)的用法鎖的是對象,線程想要執行對應同步代碼,須要得到對象鎖。
synchronized修飾靜態方法以及同步代碼塊的synchronized (類.class)用法鎖的是類,線程想要執行對應同步代碼,須要得到類鎖。
先來看一個非線程安全實例:
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=100
修改HasSelfPrivateNum以下,方法用synchronized修飾以下:
class HasSelfPrivateNum { private int num = 0; public synchronized 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的對象鎖,他們並無在獲取鎖上有競爭關係,所以,出現非同步的結果 。
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 (){}的{}先後的代碼依舊是異步的。
public class Run { public static void main(String[] args) { Service service = new Service("ss"); 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 { public synchronized 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(); } } public synchronized 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默認是單例的。
~~~~~~~~~~~~~~~end~~~~~~~~~~~~~~~~~
留到做業題,下面的同步代碼是否會爭鎖?
public class TestThreadLock { public static void main(String[] args) { Service service1 = new Service("service1"); Service service2 = new Service("service2"); Thread a = new ThreadA(service1); a.start(); Thread b = new ThreadB(service2); b.start(); } } class Service{ String name = null; public Service(String name){ this.name = name; } public synchronized void add1(String num){ try { System.out.println(num+"(1):"+System.currentTimeMillis()); Thread.sleep(3000); System.out.println(num+"(1):"+System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } public synchronized void add2(String num){ try { System.out.println(num+"(2):"+System.currentTimeMillis()); Thread.sleep(3000); System.out.println(num+"(2):"+System.currentTimeMillis()); } catch (InterruptedException e) { e.printStackTrace(); } } } class ThreadA extends Thread{ public Service service; public ThreadA(Service service){ this.service = service; } @Override public void run(){ service.add1("A"); } } class ThreadB extends Thread{ public Service service; public ThreadB(Service service){ this.service = service; } @Override public void run(){ service.add2("B"); } }