Java中synchronized關鍵字和對象的內置鎖結合使用,用來保護代碼塊在併發環境下的線程安全,可以使被保護的代碼塊操作原子性。
synchronized關鍵字可以用於修飾方法來保護方法內的全部代碼塊,可以用synchronized(對象1) 的方式保護指定代碼塊。(這裏說一下:很多書中都說synchronized可以給對象加鎖,我實在不願意這麼說,這樣讓我概念混淆。。。因爲,對象內置鎖是本來就存在的,不是誰加給它的,“synchronized(對象1)”我更願意解釋成:執行到此的線程需要去獲取到(持有)“對象1”的對象鎖才能執行synchronized塊中的代碼)。
而多個線程在執行某一被synchronized保護的代碼或者方法時,能夠進行互斥的關鍵在於synchronized的是不是同一個鎖。下面是synchronized使用的幾種情況並分析他分別持有的是哪一個對象鎖(假設這些方法都屬於類 Salary):
1、 synchronized修飾非靜態方法:持有Salary類實例的對象鎖
2、 synchronized修飾靜態方法:持有Salary.class的對象鎖
3、 synchronized(對象1):持有對象1 的對象鎖
4、 synchronized(this):持有Salary類當前對象實例的對象鎖
5、 synchronized(Salary.class): 持有Salary.class的對象鎖
下面用幾個程序實驗一下。
一、synchronized修飾非靜態方法:持有Salary類實例的對象鎖
併發對象類Salary:
public class Salary { public synchronized void method1(){ try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " has entered method1 at:"+System.currentTimeMillis()/1000); } public void method2(){ System.out.println(Thread.currentThread().getName() + " has entered method2 at:"+System.currentTimeMillis()/1000); } }
併發程序SynchronizeTest:
public class SynchronizeTest { public static void main(String[] args) { final Salary salary = new Salary(); Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+" start at:"+System.currentTimeMillis()/1000); salary.method1(); System.out.println(Thread.currentThread().getName()+" end at:"+System.currentTimeMillis()/1000); } }); t1.start(); //爲了保證線程t1首先獲得對象鎖,在此處主線程掛起1秒鐘,再執行後面的啓動線程t2的代碼。 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //如果線程t2在啓動4秒之後才進入salary.method2(),則說明請求的是同一個對象鎖。否則,就不是同一個對象鎖 Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+" start at:"+System.currentTimeMillis()/1000); synchronized (salary) { //TODO1 salary.method2(); } System.out.println(Thread.currentThread().getName()+" end at:"+System.currentTimeMillis()/1000); } }); t2.start(); } }
運行結果:
Thread-0 start at:1452076058
Thread-1 start at:1452076059
Thread-0 has entered method1 at:1452076063
Thread-0 end at:1452076063
Thread-1 has entered method2 at:1452076063
Thread-1 end at:1452076063
從運行結果可以看出,當線程t1進入方法method1 5秒之後(釋放了對象鎖),線程t2才進入方法method2,說明兩個線程持有的是相同的對象鎖。
二、synchronized修飾靜態方法:持有Salary.class的對象鎖
併發對象類Salary
public class Salary { public synchronized static void method1(){ try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " has entered method1 at:"+System.currentTimeMillis()/1000); } public void method2(){ System.out.println(Thread.currentThread().getName() + " has entered method2 at:"+System.currentTimeMillis()/1000); } }
併發程序SynchronizeTest
public class SynchronizeTest { public static void main(String[] args) { final Salary salary = new Salary(); Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+" start at:"+System.currentTimeMillis()/1000); Salary.method1(); System.out.println(Thread.currentThread().getName()+" end at:"+System.currentTimeMillis()/1000); } }); t1.start(); //爲了保證線程t1首先獲得對象鎖,在此處主線程掛起1秒鐘,再執行後面的啓動線程t2的代碼。 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //如果線程t2在啓動4秒之後才進入salary.method2(),則說明請求的是同一個對象鎖。否則,就不是同一個對象鎖 Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+" start at:"+System.currentTimeMillis()/1000); synchronized (Salary.class) { salary.method2(); } System.out.println(Thread.currentThread().getName()+" end at:"+System.currentTimeMillis()/1000); } }); t2.start(); } }
運行結果:
Thread-0 start at:1452076058
Thread-1 start at:1452076059
Thread-0 has entered method1 at:1452076063
Thread-0 end at:1452076063
Thread-1 has entered method2 at:1452076063
Thread-1 end at:1452076063
三、synchronized(對象1):持有對象1 的對象鎖
併發對象類Salary:
public class Salary { private Object lock = new Object(); public Salary(Object lock){ this.lock = lock; } public void method1(){ synchronized (lock) { try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " has entered method1 at:"+System.currentTimeMillis()/1000); } } public void method2(){ System.out.println(Thread.currentThread().getName() + " has entered method2 at:"+System.currentTimeMillis()/1000); } }
併發程序SynchronizeTest:
public class SynchronizeTest { public static void main(String[] args) { final Object 對象1 = new Object(); final Salary salary = new Salary(對象1); Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+" start at:"+System.currentTimeMillis()/1000); salary.method1(); System.out.println(Thread.currentThread().getName()+" end at:"+System.currentTimeMillis()/1000); } }); t1.start(); //爲了保證線程t1首先獲得對象鎖,在此處主線程掛起1秒鐘,再執行後面的啓動線程t2的代碼。 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //如果線程t2在啓動4秒之後才進入salary.method2(),則說明請求的是同一個對象鎖。否則,就不是同一個對象鎖 Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+" start at:"+System.currentTimeMillis()/1000); synchronized (對象1) { salary.method2(); } System.out.println(Thread.currentThread().getName()+" end at:"+System.currentTimeMillis()/1000); } }); t2.start(); } }
運行結果:
Thread-0 start at:1452077315
Thread-1 start at:1452077316
Thread-0 has entered method1 at:1452077320
Thread-1 has entered method2 at:1452077320
Thread-1 end at:1452077320
Thread-0 end at:1452077320
四、 synchronized(this):持有Salary類當前對象實例的對象鎖
併發對象類Salary:
public class Salary { private Object lock = new Object(); public Salary(Object lock){ this.lock = lock; } public void method1(){ synchronized (this) { try { Thread.sleep(5000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + " has entered method1 at:"+System.currentTimeMillis()/1000); } } public void method2(){ System.out.println(Thread.currentThread().getName() + " has entered method2 at:"+System.currentTimeMillis()/1000); } }
併發程序SynchronizeTest:
public class SynchronizeTest { public static void main(String[] args) { final Object 對象1 = new Object(); final Salary salary = new Salary(對象1); Thread t1 = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+" start at:"+System.currentTimeMillis()/1000); salary.method1(); System.out.println(Thread.currentThread().getName()+" end at:"+System.currentTimeMillis()/1000); } }); t1.start(); //爲了保證線程t1首先獲得對象鎖,在此處主線程掛起1秒鐘,再執行後面的啓動線程t2的代碼。 try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } //如果線程t2在啓動4秒之後才進入salary.method2(),則說明請求的是同一個對象鎖。否則,就不是同一個對象鎖 Thread t2 = new Thread(new Runnable() { @Override public void run() { System.out.println(Thread.currentThread().getName()+" start at:"+System.currentTimeMillis()/1000); synchronized (salary) { salary.method2(); } System.out.println(Thread.currentThread().getName()+" end at:"+System.currentTimeMillis()/1000); } }); t2.start(); } }
運行結果:
Thread-0 start at:1452077439
Thread-1 start at:1452077440
Thread-0 has entered method1 at:1452077444
Thread-0 end at:1452077444
Thread-1 has entered method2 at:1452077444
Thread-1 end at:1452077444
五、 synchronized(Salary.class): 持有Salary.class的對象鎖
與測試程序二同理。
總結一下,synchronized的使用變種可能還有很多,但是萬變不離其宗,分析多線程是否在synchronized處產生了互斥,其根本就是要分析synchronized所持有的對象鎖是否爲同一個對象鎖。這很關鍵!