系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)synchronized關鍵字(2) Java多線程學習(三)volatile關鍵字 Java多線程學習(四)等待/通知(wait/notify)機制 Java多線程學習(五)線程間通信知識點補充 Java多線程學習(六)Lock鎖的使用 Java多線程學習(七)并發程式設計中一些問題 系列文章将被優先更新于微信公衆号“Java面試通關手冊”,歡迎廣大Java程式員和愛好技術的人員關注。
(2) synchronized同步語句塊
本節思維導圖:
思維導圖源檔案+思維導圖軟體關注微信公衆号:“Java面試通關手冊”回複關鍵字:“Java多線程”免費領取。
一 synchronized方法的缺點
使用synchronized關鍵字聲明方法有些時候是有很大的弊端的,比如我們有兩個線程一個線程A調用同步方法後獲得鎖,那麼另一個線程B就需要等待A執行完,但是如果說A執行的是一個很費時間的任務的話這樣就會很耗時。
先來看一個暴露synchronized方法的缺點執行個體,然後在看看如何通過synchronized同步語句塊解決這個問題。
Task.java
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(); } } }
CommonUtils.java
public class CommonUtils { public static long beginTime1; public static long endTime1; public static long beginTime2; public static long endTime2; }
MyThread1.java
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(); } }
MyThread2.java
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(); } }
Run.java
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)); } }
運作結果:
從運作時間上來看,synchronized方法的問題很明顯。可以使用synchronized同步塊來解決這個問題。但是要注意synchronized同步塊的使用方式,如果synchronized同步塊使用不好的話并不會帶來效率的提升。
二 synchronized(this)同步代碼塊的使用
修改上例中的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(); } } }
從上面代碼可以看出當一個線程通路一個對象的synchronized同步代碼塊時,另一個線程任然可以通路該對象非synchronized同步代碼塊。
時間雖然縮短了,但是大家考慮一下synchronized代碼塊真的是同步的嗎?它真的持有目前調用對象的鎖嗎?
是的。不在synchronized代碼塊中就異步執行,在synchronized代碼塊中就是同步執行。
驗證代碼:
synchronizedDemo1包下三 synchronized(object)代碼塊間使用
MyObject.java
public class MyObject { }
Service.java
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(); } } } }
ThreadA.java
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); } }
ThreadB.java
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); } }
Run1_1.java
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(); } }
可以看出如下圖所示,兩個線程使用了同一個“對象螢幕”,是以運作結果是同步的。
那麼,如果使用不同的對象螢幕會出現什麼效果呢?
修改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(); } }
可以看出如下圖所示,兩個線程使用了不同的“對象螢幕”,是以運作結果不是同步的了。
四 synchronized代碼塊間的同步性
當一個對象通路synchronized(this)代碼塊時,其他線程對同一個對象中所有其他synchronized(this)代碼塊代碼塊的通路将被阻塞,這說明synchronized(this)代碼塊使用的“對象螢幕”是一個。
也就是說和synchronized方法一樣,synchronized(this)代碼塊也是鎖定目前對象的。
另外通過上面的學習我們可以得出兩個結論。
- 其他線程執行對象中synchronized同步方法(上一節我們介紹過,需要回顧的可以看上一節的文章)和synchronized(this)代碼塊時呈現同步效果;
- 如果兩個線程使用了同一個“對象螢幕”,運作結果同步,否則不同步.
五 靜态同步synchronized方法與synchronized(class)代碼塊
synchronized關鍵字加到static靜态方法和synchronized(class)代碼塊上都是是給Class類上鎖,而synchronized關鍵字加到非static靜态方法上是給對象上鎖。
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"); } }
public class ThreadA extends Thread { private Service service; public ThreadA(Service service) { super(); this.service = service; } @Override public void run() { service.printA(); } }
public class ThreadB extends Thread { private Service service; public ThreadB(Service service) { super(); this.service = service; } @Override public void run() { service.printB(); } }
ThreadC.java
public class ThreadC extends Thread { private Service service; public ThreadC(Service service) { super(); this.service = service; } @Override public void run() { service.printC(); } }
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(); } }
從運作結果可以看出:靜态同步synchronized方法與synchronized(class)代碼塊持有的鎖一樣,都是Class鎖,Class鎖對對象的所有執行個體起作用。synchronized關鍵字加到非static靜态方法上持有的是對象鎖。
線程A,B和線程C持有的鎖不一樣,是以A和B運作同步,但是和C運作不同步。
六 資料類型String的常量池屬性
在Jvm中具有String常量池緩存的功能
String s1 = "a"; String s2="a"; System.out.println(s1==s2);//true
上面代碼輸出為true.這是為什麼呢?
字元串常量池中的字元串隻存在一份! 即執行完第一行代碼後,常量池中已存在 “a”,那麼s2不會在常量池中申請新的空間,而是直接把已存在的字元串記憶體位址傳回給s2。
因為資料類型String的常量池屬性,是以synchronized(string)在使用時某些情況下會出現一些問題,比如兩個線程運作
synchronized("abc"){
}和
}修飾的方法時,這兩個線程就會持有相同的鎖,導緻某一時刻隻有一個線程能運作。是以盡量不要使用synchronized(string)而使用synchronized(object)
參考:
《Java多線程程式設計核心技術》
《Java并發程式設計的藝術》
如果你覺得部落客的文章不錯,歡迎轉發點贊。你能從中學到知識就是我最大的幸運。
歡迎關注我的微信公衆号:“Java面試通關手冊”(分享各種Java學習資源,面試題,以及企業級Java實戰項目回複關鍵字免費領取)。另外我建立了一個Java學習交流群(群号:174594747),歡迎大家加入一起學習,這裡更有面試,學習視訊等資源的分享。