Java实现线程同步的方式
1. synchronized關鍵字
synchronized關鍵字保證在同一時刻,只有一個線程可以執行某個對象內某一個方法或某一段代碼塊。
重量級鎖。包含兩個特征:互斥性和可見性。
synchronized可以解決一個線程看到對象處于不一致的狀態,可以保證進入同步方法或者同步代碼塊的每個線程都可以看到由同一個鎖保護之前所有的修改效果。
實現同步的基礎:Java中每個對象都可作為鎖。
| 方法 | static方法 | 鎖的是當前類的class對象,該類的所有對象訪問這個static方法都被阻塞。 |
| 非static方法 | 鎖的是當前實例對象,必須持有該對象鎖才能訪問對象內的同步方法。 | |
| 代碼塊 | this對象 | 與鎖非static方法一樣,都是給對象上鎖。 |
| 非this對象x | 給對象x上鎖,想訪問這個代碼段必須持有這個對象x的鎖才可以。 | |
| class對象 | 與給static方法加同步關鍵字一樣,鎖的都是class對象。 |
注意:class鎖和對象鎖不是同一種鎖。
- 持有class鎖則可以訪問同步的static方法和鎖定class對象的代碼段;
- 持有對象鎖則可以訪問同步的非static方法和鎖定this對象的代碼段。
- 只有多個線程持有同一個對象的鎖時,訪問該對象內的同步方法才會被阻塞,如果持有的不是同一個對象的鎖則異步執行。
2. wait()和notify()
wait():使一個線程處于等待狀態,并且釋放所持有的對象的lock。
sleep():使一個正在運行的線程處于睡眠狀態,是一個靜態方法,調用此方法要捕捉InterruptedException異常。
notify():喚醒一個處于等待狀態的線程,注意的是在調用此方法的時候,并不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且不是按優先級。
notityAll():喚醒所有處入等待狀態的線程,注意并不是給所有喚醒線程一個對象的鎖,而是讓它們競爭。
3. 使用重入鎖
在JavaSE5.0中新增了一個java.util.concurrent包來支持同步。
ReentrantLock類是可重入、互斥、實現了Lock接口的鎖,它與使用synchronized方法和快具有相同的基本行為和語義,并且擴展了其能力。
ReenreantLock類的常用方法有:
- ReentrantLock() : 創建一個ReentrantLock實例?
- lock() : 獲得鎖?
- unlock() : 釋放鎖?
?注:關于Lock對象和synchronized關鍵字的選擇:
- 最好兩個都不用,使用一種java.util.concurrent包提供的機制,能夠幫助用戶處理所有與鎖相關的代碼。
- 如果synchronized關鍵字能滿足用戶的需求,就用synchronized,因為它能簡化代碼
- 如果需要更高級的功能,就用ReentrantLock類,此時要注意及時釋放鎖,否則會出現死鎖,通常在finally代碼釋放鎖
4. await() signal() signalAll()
java.util.concurrent 類庫中提供了 Condition 類來實現線程之間的協調,可以在Condition上調用 await() 方法使線程等待,其它線程調用 signal() 或 signalAll() 方法喚醒等待的線程。
相比于 wait() 這種等待方式,await() 可以指定等待的條件,因此更加靈活。
使用 Lock 來獲取一個 Condition 對象
5. 使用局部變量
如果使用ThreadLocal管理變量,則每一個使用該變量的線程都獲得該變量的副本,副本之間相互獨立,這樣每一個線程都可以隨意修改自己的變量副本,而不會對其他線程產生影響。
ThreadLocal 類的常用方法:
- ThreadLocal() : 創建一個線程本地變量?
- get() : 返回此線程局部變量的當前線程副本中的值?
- initialValue() : 返回此線程局部變量的當前線程的"初始值"?
- set(T value) : 將此線程局部變量的當前線程副本中的值設置為value
前面5種同步方式都是在底層實現的線程同步,但是我們在實際開發當中,應當盡量遠離底層結構。 使用javaSE5.0版本中新增的java.util.concurrent包將有助于簡化開發。
6. 使用阻塞隊列
本小節主要是使用LinkedBlockingQueue<E>來實現線程的同步。
LinkedBlockingQueue<E>是一個基于已連接節點的,范圍任意的blocking queue。
隊列是先進先出的順序(FIFO),
LinkedBlockingQueue 類常用方法:
- LinkedBlockingQueue() : 創建一個容量為Integer.MAX_VALUE的LinkedBlockingQueue
- put(E e) : 在隊尾添加一個元素,如果隊列滿則阻塞
- size() : 返回隊列中的元素個數
- take() : 移除并返回隊頭元素,如果隊列空則阻塞
7. 使用原子變量
需要使用線程同步的根本原因在于對普通變量的操作不是原子的
原子操作就是指將讀取變量值、修改變量值、保存變量值看成一個整體來操作即-這幾種行為要么同時完成,要么都不完成。
在java的util.concurrent.atomic包中提供了創建了原子類型變量的工具類,使用該類可以簡化線程同步。
其中AtomicInteger 表可以用原子方式更新int的值,可用在應用程序中(如以原子方式增加的計數器),但不能用于替換Integer;
可擴展Number,允許那些處理機遇數字類的工具和實用工具進行統一訪問。
AtomicInteger類常用方法:
- AtomicInteger(int initialValue) : 創建具有給定初始值的新的
- AtomicIntegeraddAddGet(int dalta) : 以原子方式將給定值與當前值相加
- get() : 獲取當前值
注:
--volatile關鍵字?
輕量級synchronized,使用成本低,不會引起上下文切換和調度。
volatile變量在線程內存中被修改之后要立即同步回主內存中,以保證其他線程使用該volatile關鍵字修飾的變量時獲取到的是最新的變量值。
volatile關鍵字保證的是變量在不同線程之間的可見性,但是無法保證原子性,對于多個線程訪問同一個實例變量還是需要加鎖同步。
- 比如自增操作!!!
---
synchronized與Lock的區別
兩者區別:
1.首先synchronized是java內置關鍵字,在jvm層面,Lock是個java類;
2.synchronized無法判斷是否獲取鎖的狀態,;
3.synchronized會自動釋放鎖(a?線程執行完同步代碼會釋放鎖 ;b 線程執行過程中發生異常會釋放鎖),Lock需在finally中手工釋放鎖(unlock()方法釋放鎖),否則容易造成線程死鎖;
4.用synchronized關鍵字的兩個線程1和線程2,如果當前線程1獲得鎖,線程2線程等待。如果線程1阻塞,線程2則會一直等待下去,而Lock鎖就不一定會等待下去,如果嘗試獲取不到鎖,線程可以不用一直等待就結束了;
5.synchronized的鎖可重入、不可中斷、非公平,而Lock鎖可重入、可判斷、可公平(兩者皆可)
6.Lock鎖適合大量同步的代碼的同步問題,synchronized鎖適合代碼少量的同步問題。
參考:https://blog.csdn.net/scgyus/article/details/79499650?
與50位技術專家面對面20年技術見證,附贈技術全景圖總結
以上是生活随笔為你收集整理的Java实现线程同步的方式的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 面向对象回顾(静态变量、类加载机制/双亲
- 下一篇: 集合对比