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