摘要:關鍵字加到非靜態方法上持有的是對象鎖。線程和線程持有的鎖不一樣,所以和運行同步,但是和運行不同步。所以盡量不要使用而使用參考多線程編程核心技術并發編程的藝術如果你覺得博主的文章不錯,歡迎轉發點贊。
系列文章傳送門:
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同步塊使用不好的話并不會帶來效率的提升。
修改上例中的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(this)代碼塊時,其他線程對同一個對象中所有其他synchronized(this)代碼塊代碼塊的訪問將被阻塞,這說明synchronized(this)代碼塊使用的“對象監視器”是一個。
也就是說和synchronized方法一樣,synchronized(this)代碼塊也是鎖定當前對象的。
另外通過上面的學習我們可以得出兩個結論。
其他線程執行對象中synchronized同步方法(上一節我們介紹過,需要回顧的可以看上一節的文章)和synchronized(this)代碼塊時呈現同步效果;
如果兩個線程使用了同一個“對象監視器”,運行結果同步,否則不同步.
五 靜態同步synchronized方法與synchronized(class)代碼塊synchronized關鍵字加到static靜態方法和synchronized(class)代碼塊上都是是給Class類上鎖,而synchronized關鍵字加到非static靜態方法上是給對象上鎖。
Service.java
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"); } }
ThreadA.java
public class ThreadA extends Thread { private Service service; public ThreadA(Service service) { super(); this.service = service; } @Override public void run() { service.printA(); } }
ThreadB.java
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(); } }
Run.java
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運行不同步。
在Jvm中具有String常量池緩存的功能
String s1 = "a"; String s2="a"; System.out.println(s1==s2);//true
上面代碼輸出為true.這是為什么呢?
字符串常量池中的字符串只存在一份! 即執行完第一行代碼后,常量池中已存在 “a”,那么s2不會在常量池中申請新的空間,而是直接把已存在的字符串內存地址返回給s2。
因為數據類型String的常量池屬性,所以synchronized(string)在使用時某些情況下會出現一些問題,比如兩個線程運行
synchronized("abc"){
}和
synchronized("abc"){
}修飾的方法時,這兩個線程就會持有相同的鎖,導致某一時刻只有一個線程能運行。所以盡量不要使用synchronized(string)而使用synchronized(object)
參考:
《Java多線程編程核心技術》
《Java并發編程的藝術》
如果你覺得博主的文章不錯,歡迎轉發點贊。你能從中學到知識就是我最大的幸運。
歡迎關注我的微信公眾號:“Java面試通關手冊”(分享各種Java學習資源,面試題,以及企業級Java實戰項目回復關鍵字免費領取)。另外我創建了一個Java學習交流群(群號:174594747),歡迎大家加入一起學習,這里更有面試,學習視頻等資源的分享。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/69102.html
摘要:轉載請備注地址多線程學習二將分為兩篇文章介紹同步方法另一篇介紹同步語句塊。如果兩個線程同時操作對象中的實例變量,則會出現非線程安全,解決辦法就是在方法前加上關鍵字即可。 轉載請備注地址: https://blog.csdn.net/qq_3433... Java多線程學習(二)將分為兩篇文章介紹synchronized同步方法另一篇介紹synchronized同步語句塊。系列文章傳送門...
摘要:運行可運行狀態的線程獲得了時間片,執行程序代碼。阻塞的情況分三種一等待阻塞運行的線程執行方法,會把該線程放入等待隊列中。死亡線程方法執行結束,或者因異常退出了方法,則該線程結束生命周期。死亡的線程不可再次復生。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)synchronized關鍵...
摘要:三關鍵字能保證原子性嗎并發編程藝術這本書上說保證但是在自增操作非原子操作上不保證,多線程編程核心藝術這本書說不保證。多線程訪問關鍵字不會發生阻塞,而關鍵字可能會發生阻塞關鍵字能保證數據的可見性,但不能保證數據的原子性。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)synchroniz...
摘要:今天開始整理學習多線程的知識,談談最重要的兩個關鍵字和。但是這樣一個過程比較慢,在使用多線程的時候就會出現問題。有序性有序性是指多線程執行結果的正確性。這種機制在多線程中會出現問題,因此可以通過來禁止重排。 今天開始整理學習多線程的知識,談談最重要的兩個關鍵字:volatile和synchronized。 一、三個特性 1、原子性 所謂原子性操作就是指這些操作是不可中斷的,要么執行過程...
摘要:返回與此鎖相關聯的給定條件等待的線程數的估計。查詢是否有線程正在等待獲取此鎖。為公平鎖,為非公平鎖線程運行了獲得鎖定運行結果公平鎖的運行結果是有序的。 系列文章傳送門: Java多線程學習(一)Java多線程入門 Java多線程學習(二)synchronized關鍵字(1) java多線程學習(二)synchronized關鍵字(2) Java多線程學習(三)volatile關鍵字 ...
閱讀 535·2019-08-30 15:55
閱讀 944·2019-08-29 15:35
閱讀 1198·2019-08-29 13:48
閱讀 1910·2019-08-26 13:29
閱讀 2933·2019-08-23 18:26
閱讀 1237·2019-08-23 18:20
閱讀 2834·2019-08-23 16:43
閱讀 2709·2019-08-23 15:58