系列文章傳送門:java
Java多線程學習(二)synchronized關鍵字(1)程序員
java多線程學習(二)synchronized關鍵字(2) github
Java多線程學習(四)等待/通知(wait/notify)機制編程
系列文章將被優先更新於微信公衆號<font color="red">「Java面試通關手冊」</font>,歡迎廣大Java程序員和愛好技術的人員關注。併發
本節思惟導圖:
思惟導圖源文件+思惟導圖軟件關注微信公衆號:「Java面試通關手冊」回覆關鍵字:「Java多線程」免費領取。
使用<font color="red">synchronized關鍵字</font>聲明方法有些時候是有很大的弊端的,好比咱們有兩個線程一個線程A調用同步方法後得到鎖,那麼另外一個線程B就須要等待A執行完,可是若是說A執行的是一個很費時間的任務的話這樣就會很耗時。
先來看一個<font color="red">暴露synchronized方法的缺點實例</font>,而後在看看如何經過synchronized同步語句塊解決這個問題。
<font size="2">Task.java</font>
public class Task { private String getData1; private String getData2; public synchronized void doLongTimeTask() { try { System.out.println("begin task"); Thread.sleep(3000); getData1 = "長時間處理任務後從遠程返回的值1 threadName=" + Thread.currentThread().getName(); getData2 = "長時間處理任務後從遠程返回的值2 threadName=" + Thread.currentThread().getName(); System.out.println(getData1); System.out.println(getData2); System.out.println("end task"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
<font size="2">CommonUtils.java</font>
public class CommonUtils { public static long beginTime1; public static long endTime1; public static long beginTime2; public static long endTime2; }
<font size="2">MyThread1.java</font>
public class MyThread1 extends Thread { private Task task; public MyThread1(Task task) { super(); this.task = task; } @Override public void run() { super.run(); CommonUtils.beginTime1 = System.currentTimeMillis(); task.doLongTimeTask(); CommonUtils.endTime1 = System.currentTimeMillis(); } }
<font size="2">MyThread2.java</font>
public class MyThread2 extends Thread { private Task task; public MyThread2(Task task) { super(); this.task = task; } @Override public void run() { super.run(); CommonUtils.beginTime2 = System.currentTimeMillis(); task.doLongTimeTask(); CommonUtils.endTime2 = System.currentTimeMillis(); } }
<font size="2">Run.java</font>
public class Run { public static void main(String[] args) { Task task = new Task(); MyThread1 thread1 = new MyThread1(task); thread1.start(); MyThread2 thread2 = new MyThread2(task); thread2.start(); try { Thread.sleep(10000); } catch (InterruptedException e) { e.printStackTrace(); } long beginTime = CommonUtils.beginTime1; if (CommonUtils.beginTime2 < CommonUtils.beginTime1) { beginTime = CommonUtils.beginTime2; } long endTime = CommonUtils.endTime1; if (CommonUtils.endTime2 > CommonUtils.endTime1) { endTime = CommonUtils.endTime2; } System.out.println("耗時:" + ((endTime - beginTime) / 1000)); } }
<font size="2">運行結果:</font>
從運行時間上來看,synchronized方法的問題很明顯。能夠<font color="red">使用synchronized同步塊來解決這個問題</font>。可是要注意synchronized同步塊的使用方式,若是synchronized同步塊使用很差的話並不會帶來效率的提高。
修改上例中的Task.java以下:
public class Task { private String getData1; private String getData2; public void doLongTimeTask() { try { System.out.println("begin task"); Thread.sleep(3000); String privateGetData1 = "長時間處理任務後從遠程返回的值1 threadName=" + Thread.currentThread().getName(); String privateGetData2 = "長時間處理任務後從遠程返回的值2 threadName=" + Thread.currentThread().getName(); synchronized (this) { getData1 = privateGetData1; getData2 = privateGetData2; } System.out.println(getData1); System.out.println(getData2); System.out.println("end task"); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } }
<font size="2">運行結果:</font>
從上面代碼能夠看出<font color="red">當一個線程訪問一個對象的synchronized同步代碼塊時,另外一個線程任然能夠訪問該對象非synchronized同步代碼塊</font>。
時間雖然縮短了,可是你們考慮一下synchronized代碼塊真的是同步的嗎?它真的持有當前調用對象的鎖嗎?
<font color="red">是的。不在synchronized代碼塊中就異步執行,在synchronized代碼塊中就是同步執行。</font>
驗證代碼:synchronizedDemo1包下
<font size="2">MyObject.java</font>
public class MyObject { }
<font size="2">Service.java</font>
public class Service { public void testMethod1(MyObject object) { synchronized (object) { try { System.out.println("testMethod1 ____getLock time=" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName()); Thread.sleep(2000); System.out.println("testMethod1 releaseLock time=" + System.currentTimeMillis() + " run ThreadName=" + Thread.currentThread().getName()); } catch (InterruptedException e) { e.printStackTrace(); } } } }
<font size="2">ThreadA.java</font>
public class ThreadA extends Thread { private Service service; private MyObject object; public ThreadA(Service service, MyObject object) { super(); this.service = service; this.object = object; } @Override public void run() { super.run(); service.testMethod1(object); } }
<font size="2">ThreadB.java</font>
public class ThreadB extends Thread { private Service service; private MyObject object; public ThreadB(Service service, MyObject object) { super(); this.service = service; this.object = object; } @Override public void run() { super.run(); service.testMethod1(object); } }
<font size="2"> Run1_1.java</font>
public class Run1_1 { public static void main(String[] args) { Service service = new Service(); MyObject object = new MyObject(); ThreadA a = new ThreadA(service, object); a.setName("a"); a.start(); ThreadB b = new ThreadB(service, object); b.setName("b"); b.start(); } }
<font size="2">運行結果:</font>
能夠看出以下圖所示,<font color="red">兩個線程使用了同一個「對象監視器」,因此運行結果是同步的。</font>
<font color="red">那麼,若是使用不一樣的對象監視器會出現什麼效果呢?</font>
修改Run1_1.java以下:
public class Run1_2 { public static void main(String[] args) { Service service = new Service(); MyObject object1 = new MyObject(); MyObject object2 = new MyObject(); ThreadA a = new ThreadA(service, object1); a.setName("a"); a.start(); ThreadB b = new ThreadB(service, object2); b.setName("b"); b.start(); } }
<font size="2">運行結果:</font>
能夠看出以下圖所示,<font color="red">兩個線程使用了不一樣的「對象監視器」,因此運行結果不是同步的了。</font>
當一個對象訪問synchronized(this)代碼塊時,其餘線程對同一個對象中全部其餘synchronized(this)代碼塊代碼塊的訪問將被阻塞,這說明<font color="red">synchronized(this)代碼塊使用的「對象監視器」是一個。</font>
也就是說<font color="red">和synchronized方法同樣,synchronized(this)代碼塊也是鎖定當前對象的。</font>
另外經過上面的學習咱們能夠得出<font color="red">兩個結論</font>。
<font color="red">synchronized關鍵字加到static靜態方法和synchronized(class)代碼塊上都是是給Class類上鎖,而synchronized關鍵字加到非static靜態方法上是給對象上鎖。</font>
<font size="2">Service.java</font>
package ceshi; public 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(); } } } synchronized public static void printB() { System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printB"); System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printB"); } synchronized public void printC() { System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "進入printC"); System.out.println("線程名稱爲:" + Thread.currentThread().getName() + "在" + System.currentTimeMillis() + "離開printC"); } }
<font size="2">ThreadA.java</font>
public class ThreadA extends Thread { private Service service; public ThreadA(Service service) { super(); this.service = service; } @Override public void run() { service.printA(); } }
<font size="2">ThreadB.java</font>
public class ThreadB extends Thread { private Service service; public ThreadB(Service service) { super(); this.service = service; } @Override public void run() { service.printB(); } }
<font size="2">ThreadC.java</font>
public class ThreadC extends Thread { private Service service; public ThreadC(Service service) { super(); this.service = service; } @Override public void run() { service.printC(); } }
<font size="2">Run.java</font>
public class Run { public static void main(String[] args) { Service service = new Service(); ThreadA a = new ThreadA(service); a.setName("A"); a.start(); ThreadB b = new ThreadB(service); b.setName("B"); b.start(); ThreadC c = new ThreadC(service); c.setName("C"); c.start(); } }
<font size="2">運行結果:</font>
從運行結果能夠看出:靜態同步synchronized方法與synchronized(class)代碼塊持有的鎖同樣,都是Class鎖,Class鎖對對象的全部實例起做用。synchronized關鍵字加到非static靜態方法上持有的是對象鎖。
線程A,B和線程C持有的鎖不同,因此A和B運行同步,可是和C運行不一樣步。
<font color="red">在Jvm中具備String常量池緩存的功能</font>
String s1 = "a"; String s2="a"; System.out.println(s1==s2);//true
上面代碼輸出爲true.<font color="red">這是爲何呢?</font>
字符串常量池中的字符串只存在一份! 即執行完第一行代碼後,常量池中已存在 「a」,那麼s2不會在常量池中申請新的空間,而是直接把已存在的字符串內存地址返回給s2。
由於數據類型String的常量池屬性,因此synchronized(string)在使用時某些狀況下會出現一些問題,好比兩個線程運行
synchronized("abc"){
}和
synchronized("abc"){
}修飾的方法時,這兩個線程就會持有相同的鎖,致使某一時刻只有一個線程能運行。因此儘可能不要使用synchronized(string)而使用synchronized(object)
參考:
《Java多線程編程核心技術》
《Java併發編程的藝術》
若是你以爲博主的文章不錯,歡迎轉發點贊。你能從中學到知識就是我最大的幸運。
歡迎關注個人微信公衆號:「Java面試通關手冊」(分享各類Java學習資源,面試題,以及企業級Java實戰項目回覆關鍵字免費領取)。另外我建立了一個Java學習交流羣(羣號:174594747),歡迎你們加入一塊兒學習,這裏更有面試,學習視頻等資源的分享。