天天看點

Java多線程學習(二)synchronized關鍵字(2)

系列文章傳送門: 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)代碼塊也是鎖定目前對象的。

另外通過上面的學習我們可以得出兩個結論。

  1. 其他線程執行對象中synchronized同步方法(上一節我們介紹過,需要回顧的可以看上一節的文章)和synchronized(this)代碼塊時呈現同步效果;
  2. 如果兩個線程使用了同一個“對象螢幕”,運作結果同步,否則不同步.

五 靜态同步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),歡迎大家加入一起學習,這裡更有面試,學習視訊等資源的分享。