詳解Java synchronized關(guān)鍵字的用法
當(dāng)你在洗手間時(shí),門(mén)是被鎖定的,這意味著沒(méi)有其他人可以走進(jìn)來(lái)并干擾你。同樣,在多線(xiàn)程編程中也存在這樣的問(wèn)題,如果多個(gè)線(xiàn)程同時(shí)訪(fǎng)問(wèn)同一塊共享內(nèi)存,那么就會(huì)產(chǎn)生競(jìng)態(tài)條件,可能導(dǎo)致數(shù)據(jù)丟失或不一致的情況。為了避免這種情況,在多線(xiàn)程編程中使用鎖機(jī)制來(lái)確保同一時(shí)刻只有一個(gè)線(xiàn)程能夠修改共享內(nèi)存。Java 中使用 synchronized 作為鎖機(jī)制,讓我們來(lái)學(xué)習(xí)一下如何使用 synchronized 實(shí)現(xiàn)線(xiàn)程安全。
1. synchronized 的基本概念
在 Java 中,synchronized 可以用來(lái)鎖定一個(gè)對(duì)象,從而達(dá)到保護(hù)多個(gè)線(xiàn)程訪(fǎng)問(wèn)共享數(shù)據(jù)的目的。當(dāng)一個(gè)線(xiàn)程獲取了 synchronized 鎖后,在未釋放鎖之前,其他線(xiàn)程不能獲取該鎖。相應(yīng)地,這個(gè)線(xiàn)程也不能獲取其他線(xiàn)程已經(jīng)獲取的鎖。
2. synchronized 的兩種使用方式
synchronized 關(guān)鍵字可以用在方法級(jí)別和代碼塊級(jí)別,下面分別介紹兩種使用方式。
2.1 方法級(jí)別
我們可以將 synchronized 用在方法級(jí)別上,這種情況下鎖定的對(duì)象是當(dāng)前對(duì)象(this)。
public class MyClass { public synchronized void myMethod() { // synchronized 代碼 } }
2.2 代碼塊級(jí)別
我們也可以將 synchronized 關(guān)鍵字用在代碼塊級(jí)別上,這種情況下鎖定的對(duì)象可以是當(dāng)前對(duì)象(this),也可以是任意一個(gè)對(duì)象。
public class MyClass { private final Object lock = new Object(); // 定義一個(gè)對(duì)象作為鎖 public void myMethod() { synchronized (lock) { // synchronized 代碼 } } }
這種情況下,我們可以在任何時(shí)間使用 lock 對(duì)象來(lái)同步操作,當(dāng)然,也可以使用其他對(duì)象作為鎖。
3. synchronized 的示例
示例1
在下面的示例中,我們定義了一個(gè)計(jì)數(shù)器 Counter,在 Counter 的 run() 方法中使用 synchronized 關(guān)鍵字來(lái)保證多個(gè)線(xiàn)程對(duì) count 變量的訪(fǎng)問(wèn)安全。我們創(chuàng)建了 10 個(gè)線(xiàn)程,并且每個(gè)線(xiàn)程對(duì)計(jì)數(shù)器進(jìn)行 1000 次增操作,最后輸出計(jì)數(shù)器的值。
public class Test { public static void main(String[] args) { Counter counter = new Counter(); Thread[] threads = new Thread[10]; for (int i = 0; i < threads.length; i++) { threads[i] = new Thread(counter); threads[i].start(); } for (int i = 0; i < threads.length; i++) { try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println("Final count: " + counter.getCount()); } static class Counter implements Runnable { private int count = 0; public synchronized void increment() { count++; } public synchronized int getCount() { return count; } @Override public void run() { for (int i = 0; i < 1000; i++) { increment(); } } } }
在這個(gè)例子中,我們創(chuàng)建了 10 個(gè)線(xiàn)程,每個(gè)線(xiàn)程對(duì)計(jì)數(shù)器進(jìn)行了 1000 次增加操作,這種并發(fā)場(chǎng)景下如果沒(méi)有加鎖機(jī)制,將會(huì)導(dǎo)致數(shù)據(jù)不一致。但是通過(guò)使用 synchronized 關(guān)鍵字的加鎖機(jī)制,我們保證了計(jì)數(shù)器的安全訪(fǎng)問(wèn),并且最終輸出的計(jì)數(shù)器的值也是正確的。
示例2
模擬在取款過(guò)程中可能出現(xiàn)的問(wèn)題:
class BankAccount { private int balance; public BankAccount(int initialBalance) { this.balance = initialBalance; } public synchronized void withdraw(int amount) { System.out.println(Thread.currentThread().getName() + " 開(kāi)始取款"); try { // 模擬取款過(guò)程中的延遲 Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } if (amount <= balance) { balance -= amount; System.out.println(Thread.currentThread().getName() + " 成功取款: " + amount); } else { System.out.println(Thread.currentThread().getName() + " 余額不足"); } System.out.println(Thread.currentThread().getName() + " 取款后余額為: " + balance); } } public class BankWithdrawalExample { public static void main(String[] args) { BankAccount account = new BankAccount(1000); Thread thread1 = new Thread(() -> account.withdraw(500), "Thread 1"); Thread thread2 = new Thread(() -> account.withdraw(700), "Thread 2"); thread1.start(); thread2.start(); } }
在以上示例中,withdraw方法模擬了取款操作,并在其中加入了1秒的延遲。
withdraw方法加上和不加synchronized的對(duì)比結(jié)果:
不加synchronized的結(jié)果:
Thread 1 開(kāi)始取款
Thread 2 開(kāi)始取款
Thread 2 成功取款: 700
Thread 2 取款后余額為: 300
Thread 1 成功取款: 500
Thread 1 取款后余額為: -200
可以看到,由于沒(méi)有對(duì)銀行賬戶(hù)的取款方法進(jìn)行同步控制,兩個(gè)線(xiàn)程同時(shí)進(jìn)入了取款方法,導(dǎo)致賬戶(hù)余額計(jì)算錯(cuò)誤,出現(xiàn)了負(fù)數(shù)的情況。
加上synchronized的結(jié)果:
Thread 1 開(kāi)始取款
Thread 1 成功取款: 500
Thread 1 取款后余額為: 500
Thread 2 開(kāi)始取款
Thread 2 余額不足
Thread 2 取款后余額為: 500
可以看到,加上synchronized之后,兩個(gè)線(xiàn)程依次進(jìn)入了取款方法,避免了資源競(jìng)爭(zhēng)的問(wèn)題,從而保證了賬戶(hù)余額的正確性。
4. synchronized 的注意事項(xiàng)
使用 synchronized 時(shí),需要注意以下幾點(diǎn):
- synchronized 關(guān)鍵字只能用于方法和代碼塊內(nèi)部,不能用于類(lèi)和接口。
- synchronized 鎖定的對(duì)象是當(dāng)前對(duì)象(this)或指定的對(duì)象,要注意鎖對(duì)象不應(yīng)該是一個(gè)字符串或者數(shù)字等常量,因?yàn)檫@樣可能導(dǎo)致死鎖情況。
- synchronized 的開(kāi)銷(xiāo)很大,每次加鎖和釋放鎖都需要進(jìn)行系統(tǒng)調(diào)用,需要注意性能問(wèn)題。
- synchronized 僅能解決單 JVM 內(nèi)的線(xiàn)程同步問(wèn)題,對(duì)于多線(xiàn)程分布式環(huán)境,需要考慮分布式鎖的解決方案。
5. 總結(jié)
synchronized 關(guān)鍵字是 Java 中用來(lái)確保線(xiàn)程安全的基本機(jī)制。通過(guò)使用 synchronized,我們可以鎖定一個(gè)對(duì)象,從而確保同一時(shí)刻只有一個(gè)線(xiàn)程可以訪(fǎng)問(wèn)該對(duì)象??梢詫?synchronized 用于方法級(jí)別和代碼塊級(jí)別,要注意鎖定的對(duì)象應(yīng)該是一個(gè)合適的對(duì)象,不能是一個(gè)常量。同時(shí),需要注意性能問(wèn)題和分布式環(huán)境下的線(xiàn)程同步問(wèn)題。
到此這篇關(guān)于詳解Java synchronized關(guān)鍵字的用法的文章就介紹到這了,更多相關(guān)Java synchronized關(guān)鍵字內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Java類(lèi)成員訪(fǎng)問(wèn)權(quán)限控制知識(shí)總結(jié)
這篇文章主要介紹了Java類(lèi)成員訪(fǎng)問(wèn)權(quán)限控制知識(shí)總結(jié),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2020-04-04Java利用jenkins做項(xiàng)目的自動(dòng)化部署
這篇文章主要介紹了Java利用jenkins做項(xiàng)目的自動(dòng)化部署,小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2018-06-06VSCode+Gradle搭建Java開(kāi)發(fā)環(huán)境實(shí)現(xiàn)
這篇文章主要介紹了VSCode+Gradle搭建Java開(kāi)發(fā)環(huán)境實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2020-07-07使用@ConditionalOnProperty控制是否加載的操作
這篇文章主要介紹了使用@ConditionalOnProperty控制是否加載的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06SpringBoot整合SpringSession實(shí)現(xiàn)分布式登錄詳情
這篇文章主要介紹了SpringBoot整合SpringSession實(shí)現(xiàn)分布式登錄詳情,文章圍繞主題展開(kāi)詳細(xì)的內(nèi)容介紹,具有一定的參考價(jià)值,需要的朋友可以參考一下2022-08-08Sentinel熱門(mén)詞匯限流的實(shí)現(xiàn)詳解
這篇文章主要介紹了使用Sentinel對(duì)熱門(mén)詞匯進(jìn)行限流的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧2022-07-07java定義二維數(shù)組的幾種寫(xiě)法(小結(jié))
下面小編就為大家?guī)?lái)一篇java定義二維數(shù)組的幾種寫(xiě)法(小結(jié))。小編覺(jué)得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過(guò)來(lái)看看吧2016-10-10JAVA中五個(gè)重定向的方式盤(pán)點(diǎn)
頁(yè)面重定向即頁(yè)面從當(dāng)前請(qǐng)求的頁(yè)面,有條件或者定時(shí)跳轉(zhuǎn)到其他頁(yè)面,下面這篇文章主要給大家介紹了關(guān)于JAVA中五個(gè)重定向的方式,文中通過(guò)實(shí)例代碼介紹的非常詳細(xì),需要的朋友可以參考下2022-12-12