這篇文章給大家介紹JAVA中如何應用synchronized關鍵字,內容非常詳細,感興趣的小伙伴們可以參考借鑒,希望對大家能有所幫助。
10年的新樂網站建設經驗,針對設計、前端、開發(fā)、售后、文案、推廣等六對一服務,響應快,48小時及時工作處理。成都全網營銷推廣的優(yōu)勢是能夠根據用戶設備顯示端的尺寸不同,自動調整新樂建站的顯示方式,使網站能夠適用不同顯示終端,在瀏覽器中調整網站的寬度,無論在任何一種瀏覽器上瀏覽網站,都能展現(xiàn)優(yōu)雅布局與設計,從而大程度地提升瀏覽體驗。創(chuàng)新互聯(lián)建站從事“新樂網站設計”,“新樂網站推廣”以來,每個客戶項目都認真落實執(zhí)行。
sychronized關鍵字有哪些特性?
可以用來修飾方法; 可以用來修飾代碼塊; 可以用來修飾靜態(tài)方法; 可以保證線程安全; 支持鎖的重入; sychronized使用不當導致死鎖;
了解sychronized之前,我們先來看一下幾個常見的概念:內置鎖、互斥鎖、對象鎖和類鎖。
內置鎖
在Java中每一個對象都可以作為同步的鎖,那么這些鎖就被稱為內置鎖。線程進入同步代碼塊或方法的時候會自動獲得該鎖,在退出同步代碼塊或方法時會釋放該鎖。獲得內置鎖的唯一途徑就是進入這個鎖的保護的同步代碼塊或方法。
互斥鎖
內置鎖同時也是一個互斥鎖,這就是意味著最多只有一個線程能夠獲得該鎖,當線程A嘗試去獲得線程B持有的內置鎖時,線程A必須等待或者阻塞,直到線程B拋出異常或者正常執(zhí)行完畢釋放這個鎖;如果B線程不釋放這個鎖,那么A線程將永遠等待下去。
對象鎖和類鎖
對象鎖和類鎖在鎖的概念上基本上和內置鎖是一致的,但是,兩個鎖實際是有很大的區(qū)別的。
對象鎖是用于對象實例方法; 類鎖是用于類的靜態(tài)方法或者一個類的class對象上的
一個對象無論有多少個同步方法區(qū),它們共用一把鎖,某一時刻某個線程已經進入到某個synchronzed方法,那么在該方法沒有執(zhí)行完畢前,其他線程無法訪問該對象的任何synchronzied 方法的,但可以訪問非synchronzied方法。
如果synchronized方法是static的,那么當線程訪問該方法時,它鎖的并不是synchronized方法所在的對象,而是synchronized方法所在對象的對應的Class對象,
因為java中無論一個類有多少個對象,這些對象會對應唯一一個Class對象,因此當線程分別訪問同一個類的兩個對象的static,synchronized方法時,他們的執(zhí)行也是按順序來的,也就是說一個線程先執(zhí)行,一個線程后執(zhí)行。
synchronized的用法:修飾方法和修飾代碼塊,下面分別分析這兩種用法在對象鎖和類鎖上的效果。
對象鎖的synchronized修飾方法和代碼塊
public class TestSynchronized { public void test1() { synchronized (this) { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } } public synchronized void test2() { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static void main(String[] args) { final TestSynchronized myt2 = new TestSynchronized(); Thread test1 = new Thread(new Runnable() { public void run() { myt2.test1(); } }, "test1"); Thread test2 = new Thread(new Runnable() { public void run() { myt2.test2(); } }, "test2"); test1.start(); test2.start(); }}
打印結果如下:
test2 : 4test2 : 3test2 : 2test2 : 1test2 : 0test1 : 4test1 : 3test1 : 2test1 : 1test1 : 0
上述的代碼,第一個方法用了同步代碼塊的方式進行同步,傳入的對象實例是this,表明是當前對象;第二個方法是修飾方法的方式進行同步
。因為第一個同步代碼塊傳入的this,所以兩個同步代碼所需要獲得的對象鎖都是同一個對象鎖,下面main方法時分別開啟兩個線程,分別調用test1和test2方法,那么兩個線程都需要獲得該對象鎖,另一個線程必須等待。
上面也給出了運行的結果可以看到:直到test2線程執(zhí)行完畢,釋放掉鎖,test1線程才開始執(zhí)行。這里test2方法先搶到CPU資源,故它先執(zhí)行,它獲得了鎖,它執(zhí)行完畢后,test1才開始執(zhí)行。
如果我們把test2方法的synchronized關鍵字去掉,執(zhí)行結果會如何呢?
test1 : 4test2 : 4test2 : 3test2 : 2test2 : 1test2 : 0test1 : 3test1 : 2test1 : 1test1 : 0
我們可以看到,結果輸出是交替著進行輸出的,這是因為,某個線程得到了對象鎖,但是另一個線程還是可以訪問沒有進行同步的方法或者代碼。進行了同步的方法(加鎖方法)和沒有進行同步的方法(普通方法)是互不影響的,一個線程進入了同步方法,得到了對象鎖,其他線程還是可以訪問那些沒有同步的方法(普通方法)。
類鎖的修飾(靜態(tài))方法和代碼塊
public class TestSynchronized { public void test1() { synchronized (TestSynchronized.class) { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } } public static synchronized void test2() { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static void main(String[] args) { final TestSynchronized myt2 = new TestSynchronized(); Thread test1 = new Thread(new Runnable() { public void run() { myt2.test1(); } }, "test1"); Thread test2 = new Thread(new Runnable() { public void run() { TestSynchronized.test2(); } }, "test2"); test1.start(); test2.start(); }}
輸出結果如下:
test1 : 4test1 : 3test1 : 2test1 : 1test1 : 0test2 : 4test2 : 3test2 : 2test2 : 1test2 : 0
類鎖修飾方法和代碼塊的效果和對象鎖是一樣的,因為類鎖只是一個抽象出來的概念,只是為了區(qū)別靜態(tài)方法的特點,因為靜態(tài)方法是所有對象實例共用的,所以對應著synchronized修飾的靜態(tài)方法的鎖也是唯一的,所以抽象出來個類鎖。其實這里的重點在下面這塊代碼,synchronized同時修飾靜態(tài)和非靜態(tài)方法
public class TestSynchronized { public synchronized void test1() { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static synchronized void test2() { int i = 5; while (i-- > 0) { System.out.println(Thread.currentThread().getName() + " : " + i); try { Thread.sleep(500); } catch (InterruptedException ie) { } } } public static void main(String[] args) { final TestSynchronized myt2 = new TestSynchronized(); Thread test1 = new Thread(new Runnable() { public void run() { myt2.test1(); } }, "test1"); Thread test2 = new Thread(new Runnable() { public void run() { TestSynchronized.test2(); } }, "test2"); test1.start(); test2.start(); }}
輸出結果如下:
test1 : 4test2 : 4test1 : 3test2 : 3test2 : 2test1 : 2test2 : 1test1 : 1test1 : 0test2 : 0
上面代碼synchronized同時修飾靜態(tài)方法和實例方法,但是運行結果是交替進行的,這證明了類鎖和對象鎖是兩個不一樣的鎖,控制著不同的區(qū)域,它們是互不干擾的。同樣,線程獲得對象鎖的同時,也可以獲得該類鎖,即同時獲得兩個鎖,這是允許的。
synchronized是如何保證線程安全的
如果有多個線程在同時運行,而這些線程可能會同時運行這段代碼。程序每次運行結果和單線程運行的結果是一樣的,而且其他的變量的值也和預期的是一樣的,就是線程安全的。
我們通過一個案例,演示線程的安全問題:
我們來模擬一下火車站賣票過程,總共有100張票,總共有三個窗口賣票。
public class SellTicket { public static void main(String[] args) { // 創(chuàng)建票對象 Ticket ticket = new Ticket(); // 創(chuàng)建3個窗口 Thread t1 = new Thread(ticket, "窗口1"); Thread t2 = new Thread(ticket, "窗口2"); Thread t3 = new Thread(ticket, "窗口3"); t1.start(); t2.start(); t3.start(); }} // 模擬票class Ticket implements Runnable { // 共100票 int ticket = 100; @Override public void run() { // 模擬賣票 while (true) { if (ticket > 0) { // 模擬選坐的操作 try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--); } } }}
運行結果發(fā)現(xiàn):上面程序出現(xiàn)了問題
票出現(xiàn)了重復的票 錯誤的票 0、-1
其實,線程安全問題都是由全局變量及靜態(tài)變量引起的。若每個線程中對全局變量、靜態(tài)變量只有讀操作,而無寫操作,這個全局變量是線程安全的;若有多個線程同時執(zhí)行寫操作,一般都需要考慮線程同步,否則的話就可能影響線程安全。
那么出現(xiàn)了上述問題,我們應該如何解決呢?
線程同步(線程安全處理Synchronized)
java中提供了線程同步機制,它能夠解決上述的線程安全問題。
線程同步的方式有兩種:
方式1:同步代碼塊 方式2:同步方法
同步代碼塊
同步代碼塊: 在代碼塊聲明上 加上synchronized
synchronized (鎖對象) { 可能會產生線程安全問題的代碼}
同步代碼塊中的鎖對象可以是任意的對象;但多個線程時,要使用同一個鎖對象才能夠保證線程安全。
使用同步代碼塊,對火車站賣票案例中Ticket類進行如下代碼修改:
public class SellTicket { public static void main(String[] args) { // 創(chuàng)建票對象 Ticket ticket = new Ticket(); // 創(chuàng)建3個窗口 Thread t1 = new Thread(ticket, "窗口1"); Thread t2 = new Thread(ticket, "窗口2"); Thread t3 = new Thread(ticket, "窗口3"); t1.start(); t2.start(); t3.start(); }} // 模擬票class Ticket implements Runnable { // 共100票 int ticket = 100; Object lock = new Object(); @Override public void run() { // 模擬賣票 while (true) { // 同步代碼塊 synchronized (lock) { if (ticket > 0) { // 模擬選坐的操作 try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--); } } } }}
當使用了同步代碼塊后,上述的線程的安全問題,解決了。
同步方法
同步方法:在方法聲明上加上synchronized
public synchronized void method(){ 可能會產生線程安全問題的代碼}
同步方法中的鎖對象是 this
使用同步方法,對火車站賣票案例中Ticket類進行如下代碼修改:
public class SellTicket { public static void main(String[] args) { // 創(chuàng)建票對象 Ticket ticket = new Ticket(); // 創(chuàng)建3個窗口 Thread t1 = new Thread(ticket, "窗口1"); Thread t2 = new Thread(ticket, "窗口2"); Thread t3 = new Thread(ticket, "窗口3"); t1.start(); t2.start(); t3.start(); }} // 模擬票class Ticket implements Runnable { // 共100票 int ticket = 100; Object lock = new Object(); @Override public void run() { // 模擬賣票 while (true) { // 同步方法 method(); } } // 同步方法,鎖對象this public synchronized void method() { if (ticket > 0) { // 模擬選坐的操作 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在賣票:" + ticket--); } }}
synchronized支持鎖的重入嗎?
我們先來看下面一段代碼:
public class ReentrantLockDemo { public synchronized void a() { System.out.println("a"); b(); } private synchronized void b() { System.out.println("b"); } public static void main(String[] args) { new Thread(new Runnable() { @Override public void run() { ReentrantLockDemo d = new ReentrantLockDemo(); d.a(); } }).start(); }}
上述的代碼,我們分析一下,兩個方法,方法a和方法b都被synchronized關鍵字修飾,鎖對象是當前對象實例,按照上文我們對synchronized的了解,如果調用方法a,在方法a還沒有執(zhí)行完之前,我們是不能執(zhí)行方法b的,方法a必須先釋放鎖,方法b才能執(zhí)行,方法b處于等待狀態(tài),那樣不就形成死鎖了嗎?那么事實真的如分析一致嗎?
運行結果發(fā)現(xiàn):
ab
代碼很快就執(zhí)行完了,實驗結果與分析不一致,這就引入了另外一個概念:重入鎖。在 java 內部,同一線程在調用自己類中其他 synchronized 方法/塊或調用父類的 synchronized 方法/塊都不會阻礙該線程的執(zhí)行。就是說同一線程對同一個對象鎖是可重入的,而且同一個線程可以獲取同一把鎖多次,也就是可以多次重入。在JDK1.5后對synchronized關鍵字做了相關優(yōu)化。
synchronized死鎖問題
同步鎖使用的弊端:當線程任務中出現(xiàn)了多個同步(多個鎖)時,如果同步中嵌套了其他的同步。這時容易引發(fā)一種現(xiàn)象:程序出現(xiàn)無限等待,這種現(xiàn)象我們稱為死鎖。這種情況能避免就避免掉。
synchronzied(A鎖){ synchronized(B鎖){ }}
我們進行下死鎖情況的代碼演示:
public class DeadLock { Object obj1 = new Object(); Object obj2 = new Object(); public void a() { synchronized (obj1) { synchronized (obj2) { System.out.println("a"); } } } public void b() { synchronized (obj2) { synchronized (obj1) { System.out.println("b"); } } } public static void main(String[] args) { DeadLock d = new DeadLock(); new Thread(new Runnable() { @Override public void run() { d.a(); } }).start(); new Thread(new Runnable() { @Override public void run() { d.b(); } }).start(); }}
上述的代碼,我們分析一下,兩個方法,我們假設兩個線程T1,T2,T1運行到方法a了,拿到了obj1這把鎖,此時T2運行到方法b了,拿到了obj2這把鎖,T1要往下執(zhí)行,就必須等待T2釋放了obj2這把鎖,線程T2要往下面執(zhí)行,就必須等待T1釋放了持有的obj1這把鎖,他們兩個互相等待,就形成了死鎖。
為了演示的更明白,需要讓兩個方法執(zhí)行過程中睡眠10ms,要不然很難看到現(xiàn)象,因為計算機執(zhí)行速度賊快
public class DeadLock { Object obj1 = new Object(); Object obj2 = new Object(); public void a() { synchronized (obj1) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj2) { System.out.println("a"); } } } public void b() { synchronized (obj2) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } synchronized (obj1) { System.out.println("b"); } } } public static void main(String[] args) { DeadLock d = new DeadLock(); new Thread(new Runnable() { @Override public void run() { d.a(); } }).start(); new Thread(new Runnable() { @Override public void run() { d.b(); } }).start(); } }
關于JAVA中如何應用synchronized關鍵字就分享到這里了,希望以上內容可以對大家有一定的幫助,可以學到更多知識。如果覺得文章不錯,可以把它分享出去讓更多的人看到。
當前標題:JAVA中如何應用synchronized關鍵字
文章起源:http://muchs.cn/article40/jsojeo.html
成都網站建設公司_創(chuàng)新互聯(lián),為您提供靜態(tài)網站、響應式網站、軟件開發(fā)、網站設計公司、網頁設計公司、建站公司
聲明:本網站發(fā)布的內容(圖片、視頻和文字)以用戶投稿、用戶轉載內容為主,如果涉及侵權請盡快告知,我們將會在第一時間刪除。文章觀點不代表本網站立場,如需處理請聯(lián)系客服。電話:028-86922220;郵箱:631063699@qq.com。內容未經允許不得轉載,或轉載時需注明來源: 創(chuàng)新互聯(lián)