日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

synchronized使用和原理全解

發布時間:2023/12/13 编程问答 33 豆豆
生活随笔 收集整理的這篇文章主要介紹了 synchronized使用和原理全解 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

synchronizedJava中的關鍵字,是一種同步鎖。它修飾的對象有以下幾種:

修飾一個方法

被修飾的方法稱為同步方法,其作用的范圍是整個方法,作用的對象是調用這個方法的對象;

修飾一個靜態的方法

其作用的范圍是整個靜態方法,作用的對象是這個類的所有對象;

修飾一個代碼塊

被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象;

修飾一個類

其作用的范圍是synchronized后面括號括起來的部分,作用主的對象是這個類的所有對象。

?

修飾一個方法

被修飾的方法稱為同步方法,其作用的范圍是整個方法,作用的對象是調用這個方法的對象;

如果多個線程訪問同一個對象的實例變量,可能出現非線程安全問題。

例子:a線程set后sleep2000ms,看b線程是否可以趁機set,造成非線程安全

HasSelfPrivateNum.java:

package service;public class HasSelfPrivateNum {private int num = 0;public void addI(String username) {try {if (username.equals("a")) {num = 100;System.out.println("a set over!");Thread.sleep(2000);} else {num = 200;System.out.println("b set over!");}System.out.println(username + " num=" + num);} catch (InterruptedException e) {e.printStackTrace();}}}

A:

package extthread;import service.HasSelfPrivateNum;public class ThreadA extends Thread {private HasSelfPrivateNum numRef;public ThreadA(HasSelfPrivateNum numRef) {super();this.numRef = numRef;}@Overridepublic void run() {super.run();numRef.addI("a");}}

B:

package extthread;import service.HasSelfPrivateNum;public class ThreadB extends Thread {private HasSelfPrivateNum numRef;public ThreadB(HasSelfPrivateNum numRef) {super();this.numRef = numRef;}@Overridepublic void run() {super.run();numRef.addI("b");}}

run:

package test;import service.HasSelfPrivateNum; import extthread.ThreadA; import extthread.ThreadB;public class Run {public static void main(String[] args) {HasSelfPrivateNum numRef = new HasSelfPrivateNum();ThreadA athread = new ThreadA(numRef);athread.start();ThreadB bthread = new ThreadB(numRef);bthread.start();} }

結果:a線程set后sleep2000ms,b線程可以趁機set,造成非線程安全

這時我們使用synchronized修飾addI方法:

package service;public class HasSelfPrivateNum {private int num = 0;synchronized public void addI(String username) {try {if (username.equals("a")) {num = 100;System.out.println("a set over!");Thread.sleep(2000);} else {num = 200;System.out.println("b set over!");}System.out.println(username + " num=" + num);} catch (InterruptedException e) {e.printStackTrace();}}}

結果:B不能set了,說明線程安全。

注意:我們取得的是對象鎖,也就是說,一個對象一個鎖,而不是鎖住整個類或者代碼或者方法。

例子:兩個對象兩個鎖

myobject.java

打印名字后sleep,最后打印end

package extobject;public class MyObject {synchronized public void methodA() {try {System.out.println("begin methodA threadName="+ Thread.currentThread().getName());Thread.sleep(5000);System.out.println("end");} catch (InterruptedException e) {e.printStackTrace();}}}

A:

package extthread;import extobject.MyObject;public class ThreadA extends Thread {private MyObject object;public ThreadA(MyObject object) {super();this.object = object;}@Overridepublic void run() {super.run();object.methodA();}}

B:

package extthread;import extobject.MyObject;public class ThreadB extends Thread {private MyObject object;public ThreadB(MyObject object) {super();this.object = object;}@Overridepublic void run() {super.run();object.methodA();} }

RUN:

package test.run;import extobject.MyObject; import extthread.ThreadA; import extthread.ThreadB;public class Run {public static void main(String[] args) {MyObject object = new MyObject();ThreadA a = new ThreadA(object);a.setName("A");ThreadB b = new ThreadB(object);b.setName("B");a.start();b.start();}}

結果:兩個對象互不影響,各自運行各自上鎖

其它方法被調用是什么效果呢?

之前做實驗是因為怕大家不理解知識,現在大家已經有了一定的基礎,這個結論不再做實驗。

結論:

如果A線程持有x對象的鎖,B線程不可調用synchronized修飾的方法,但是可以異步調用沒有被synchronized修飾的方法

?

synchronized具有鎖重入功能,也就是說一個線程獲得鎖,再次請求是可以再次得到對象的鎖的

下面做實驗驗證這個結論:

service.java

package myservice;public class Service {synchronized public void service1() {System.out.println("service1");service2();}synchronized public void service2() {System.out.println("service2");service3();}synchronized public void service3() {System.out.println("service3");}}

thread:

package extthread;import myservice.Service;public class MyThread extends Thread {@Overridepublic void run() {Service service = new Service();service.service1();}}

run:

package test;import extthread.MyThread;public class Run {public static void main(String[] args) {MyThread t = new MyThread();t.start();} }

結果驗證了上面的結論:

注:在父子類之間同樣適用,不再做實驗

?

但是如果一個線程出現了異常,難道就永遠鎖住了資源嗎?其實不是的,出現異常自動釋放鎖。

實驗:讓a鎖住對象后出現異常,看b是否可以拿到鎖,代碼不再展示。

結果:b可以正常執行。

?

修飾一個靜態的方法

?

其作用的范圍是整個靜態方法,作用的對象是這個類的所有對象;

也就是說整個類就一個鎖,這也和靜態的概念相符(靜態方法和屬性是屬于類的而不是具體一個對象的)

讓我們來驗證這個結論:

service:

package service;public class Service {synchronized public static void printA() {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");}}

a:

package extthread;import service.Service;public class ThreadA extends Thread {private Service service;public ThreadA(Service service) {super();this.service = service;}@Overridepublic void run() {service.printA();} }

b:

package extthread;import service.Service;public class ThreadB extends Thread {private Service service;public ThreadB(Service service) {super();this.service = service;}@Overridepublic void run() {service.printB();} }

run:

package test;import service.Service; import extthread.ThreadA; import extthread.ThreadB;public class Run {public static void main(String[] args) {Service service1 = new Service();Service service2 = new Service();ThreadA a = new ThreadA(service1);a.setName("A");a.start();ThreadB b = new ThreadB(service2);b.setName("B");b.start();}}

結果:

但是,請注意一個顯而易見的結論:a線程訪問synchronized修飾的靜態方法時,b線程可以訪問synchronized修飾的非靜態方法,原因也很容易想到:靜態方法是屬于類的,普通方法是屬于對象本身的,所以一個是對象鎖,一個是class鎖,不會影響。

為了驗證這個結論,我們做實驗看看結果。

service:

AB為靜態的,C普通的。

package service;public class Service {synchronized public static void printA() {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");}}

a:?

package extthread;import service.Service;public class ThreadA extends Thread {private Service service;public ThreadA(Service service) {super();this.service = service;}@Overridepublic void run() {service.printA();}}

b:

package extthread;import service.Service;public class ThreadB extends Thread {private Service service;public ThreadB(Service service) {super();this.service = service;}@Overridepublic void run() {service.printB();} }

c:

package extthread;import service.Service;public class ThreadC extends Thread {private Service service;public ThreadC(Service service) {super();this.service = service;}@Overridepublic void run() {service.printC();} }

?run:

package test;import service.Service; import extthread.ThreadA; import extthread.ThreadB; import extthread.ThreadC;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();}}

結果:

說明:驗證了結論,因為AB同步執行,C異步執行。

?

修飾一個代碼塊

被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號{}括起來的代碼,作用的對象是調用這個代碼塊的對象;(而不一定是本對象了)

synchronized方法是對當前對象加鎖,synchronized代碼塊是對某個對象加鎖

用synchronized聲明方法是有弊端的,比如A調用同步方法執行一個很長時間的任務,這時B就必須等待,有時候這種長時間等待是低效率且沒有必要的,這時我們就要認識一下synchronized同步代碼塊了。

格式是這樣的:synchronized(類名){......}

我們先來認識第一種用法來體會修飾代碼塊的好處:

synchronized(this)同步代碼塊:a調用相關代碼后,b對其它synchronized方法和synchronized(this)同步代碼塊調用會阻塞。但是沒有被synchronized修飾的代碼就得以執行,不像之前修飾方法那么死板了。

我們來看一個例子:

第一個循環異步,第二個循環同步:

package mytask;public class Task {public void doLongTimeTask() {for (int i = 0; i < 100; i++) {System.out.println("nosynchronized threadName="+ Thread.currentThread().getName() + " i=" + (i + 1));}System.out.println("");synchronized (this) {for (int i = 0; i < 100; i++) {System.out.println("synchronized threadName="+ Thread.currentThread().getName() + " i=" + (i + 1));}}} }

thread1:

package mythread;import mytask.Task;public class MyThread1 extends Thread {private Task task;public MyThread1(Task task) {super();this.task = task;}@Overridepublic void run() {super.run();task.doLongTimeTask();}}

thread2:

package mythread;import mytask.Task;public class MyThread2 extends Thread {private Task task;public MyThread2(Task task) {super();this.task = task;}@Overridepublic void run() {super.run();task.doLongTimeTask();}}

run:

package test;import mytask.Task; import mythread.MyThread1; import mythread.MyThread2;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();} }

結果:

在非同步代碼塊時,是交叉打印的

同步代碼塊時排隊執行:

另一個用法:

synchronized(非this)同步代碼塊(也就是說將任意對象作為對象監視器):

格式:synchronized(非this對象x){......}

1、當多個線程持有的對象監聽器為同一個對象時,依舊是同步的,同一時間只有一個可以訪問,

2、但是對象不同,執行就是異步的。

這樣有什么好處呢?

(因為如果一個類有很多synchronized方法或synchronized(this)代碼塊,還是會影響效率,這時用synchronized(非this)同步代碼塊就不會和其它鎖this同步方法爭搶this鎖)

實驗

第一點我們就不驗證了,因為體現不出它的好處,我們驗證第二點:

service:

一個同步非this代碼塊,一個同步方法:

package service;public class Service {private String anyString = new String();public void a() {try {synchronized (anyString) {System.out.println("a begin");Thread.sleep(3000);System.out.println("a end");}} catch (InterruptedException e) {e.printStackTrace();}}synchronized public void b() {System.out.println("b begin");System.out.println("b end");}}

a:

package extthread;import service.Service;public class ThreadA extends Thread {private Service service;public ThreadA(Service service) {super();this.service = service;}@Overridepublic void run() {service.a();}}

b:

package extthread;import service.Service;public class ThreadB extends Thread {private Service service;public ThreadB(Service service) {super();this.service = service;}@Overridepublic void run() {service.b();}}

?run:

package test;import service.Service; import extthread.ThreadA; import extthread.ThreadB;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();}}

結果:

確實是異步的。

最后一個要注意的點:我們知道synchronized(非this對象x){......}是將對象x監視,這也就意味著當線程a調用這段代碼時,線程b調用類x中的同步方法和代碼塊也會是同步的效果(阻塞)。

為了讓大家更明白,做最后一個例子:

首先創建一個有靜態方法的類:

package test2.extobject;public class MyObject {synchronized public void speedPrintString() {System.out.println("speedPrintString ____getLock time="+ System.currentTimeMillis() + " run ThreadName="+ Thread.currentThread().getName());System.out.println("-----------------");System.out.println("speedPrintString releaseLock time="+ System.currentTimeMillis() + " run ThreadName="+ Thread.currentThread().getName());} }

然后用?synchronized(非this對象x){......}的形式引用它,并進入這個代碼塊,然后看看這時這個靜態方法是否可以被調用。

service:作用是synchronized(object)

package test2.service;import test2.extobject.MyObject;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(5000);System.out.println("testMethod1 releaseLock time="+ System.currentTimeMillis() + " run ThreadName="+ Thread.currentThread().getName());} catch (InterruptedException e) {e.printStackTrace();}}}}

threadA:

package test2.extthread;import test2.extobject.MyObject; import test2.service.Service;public class ThreadA extends Thread {private Service service;private MyObject object;public ThreadA(Service service, MyObject object) {super();this.service = service;this.object = object;}@Overridepublic void run() {super.run();service.testMethod1(object);}}

?threadB:

package test2.extthread;import test2.extobject.MyObject;public class ThreadB extends Thread {private MyObject object;public ThreadB(MyObject object) {super();this.object = object;}@Overridepublic void run() {super.run();object.speedPrintString();} }

run:

?

package test2.run;import test2.extobject.MyObject; import test2.extthread.ThreadA; import test2.extthread.ThreadB; import test2.service.Service;public class Run {public static void main(String[] args) throws InterruptedException {Service service = new Service();MyObject object = new MyObject();ThreadA a = new ThreadA(service, object);a.setName("a");a.start();Thread.sleep(100);ThreadB b = new ThreadB(object);b.setName("b");b.start();}}

結果:

?

是同步的。

當然,把修飾方法改為修飾代碼塊也是一樣不能被執行的。

Synchronized的作用主要有三個:(1)確保線程互斥的訪問同步代碼(2)保證共享變量的修改能夠及時可見(3)有效解決重排序問題。

synchronized底層原理

在 Java 早期版本中,synchronized屬于重量級鎖,效率低下,因為監視器鎖(monitor)是依賴于底層的操作系統來實現,Java 的線程是映射到操作系統的原生線程之上的。如果要掛起或者喚醒一個線程,都需要操作系統幫忙完成,而操作系統實現線程之間的切換時需要從用戶態轉換到內核態,這個狀態之間的轉換需要相對比較長的時間,時間成本相對較高。

在 Java 6 之后從 JVM 層面對synchronized 較大優化,鎖的實現引入了如自旋鎖、適應性自旋鎖、鎖消除、鎖粗化、偏向鎖、輕量級鎖等技術來減少鎖操作的開銷。

?

synchronized 同步語句塊的實現使用的是 monitorenter 和 monitorexit 指令。

其中 monitorenter 指令指向同步代碼塊的開始位置,monitorexit 指令則指明同步代碼塊的結束位置。

當執行 monitorenter 指令時,線程試圖獲取鎖也就是獲取 monitor(monitor對象存在于每個Java對象的對象頭中,synchronized 鎖便是通過這種方式獲取鎖的,也是為什么Java中任意對象可以作為鎖的原因) 的持有權.當計數器為0則可以成功獲取,獲取后將鎖計數器設為1也就是加1。相應的在執行 monitorexit 指令后,將鎖計數器設為0,表明鎖被釋放。如果獲取對象鎖失敗,那當前線程就要阻塞等待,直到鎖被另外一個線程釋放為止。

synchronized 修飾的方法并沒有 monitorenter 指令和 monitorexit 指令,取得代之的是ACC_SYNCHRONIZED標識,該標識指明了該方法是一個同步方法,JVM 通過該 ACC_SYNCHRONIZED 訪問標志來辨別一個方法是否聲明為同步方法,從而執行相應的同步調用。但是原理其實都是類似的。具體的實現是操作系統的知識可以去翻我操作系統的文章。

鎖詳解

鎖主要存在四種狀態,依次是:無鎖狀態、偏向鎖狀態、輕量級鎖狀態、重量級鎖狀態,他們會隨著競爭的激烈而逐漸升級。注意鎖可以升級不可降級,這種策略是為了提高獲得鎖和釋放鎖的效率。

自旋:當有個線程A去請求某個鎖的時候,這個鎖正在被其它線程占用,但是線程A并不會馬上進入阻塞狀態,而是循環請求鎖(自旋)。這樣做的目的是因為很多時候持有鎖的線程會很快釋放鎖的,線程A可以嘗試一直請求鎖,沒必要被掛起放棄CPU時間片,因為線程被掛起然后到喚醒這個過程開銷很大,當然如果線程A自旋指定的時間還沒有獲得鎖,仍然會被掛起。

自適應性自旋:自適應性自旋是自旋的升級、優化,自旋的時間不再固定,而是由前一次在同一個鎖上的自旋時間及鎖的擁有者的狀態決定。例如線程如果自旋成功了,那么下次自旋的次數會增多,因為JVM認為既然上次成功了,那么這次自旋也很有可能成功,那么它會允許自旋的次數更多。

鎖消除是指虛擬機即時編譯器在運行時,對一些代碼上要求同步,但是被檢測到不可能存在共享數據競爭的鎖進行消除。

偏向鎖的目的是消除數據在無競爭情況下的同步原語,進一步提高程序的運行性能。如果說輕量級鎖是在無競爭的情況下使用CAS操作去消除同步使用的互斥量,那么偏向鎖就是在無競爭的情況下把整個同步都消除掉,連CAS操作都不用做了。偏向鎖默認是開啟的,也可以關閉。

?

總結

以上是生活随笔為你收集整理的synchronized使用和原理全解的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。