synchronized 與 Lock 的那點事
最近在做一個監控系統,該系統主要包括對資料實時分析和儲存兩個部分,由於併發量比較高,所以不可避免的使用到了一些併發的知識。為了實現這些要求,後臺使用一個佇列作為快取,對於請求只管往快取裡寫資料。同時啟動一個執行緒監聽該佇列,檢測到資料,立即請求排程執行緒,對資料進行處理。 具體的使用方案就是使用同步保證資料的正常,使用執行緒池提高效率。
同步的實現當然是採用鎖了,java中使用鎖的兩個基本工具是 synchronized 和 Lock。
一直很喜歡synchronized,因為使用它很方便。比如,需要對一個方法進行同步,那麼只需在方法的簽名新增一個synchronized關鍵字。
// 未同步的方法
public void test() {}
// 同步的方法
pubilc synchronized void test() {}
synchronized 也可以用在一個程式碼塊上,看
public void test() {
synchronized(obj) {
System.out.println("===");
}
}
synchronized 用在方法和程式碼塊上有什麼區別呢?
synchronized 用在方法簽名上(以test為例),當某個執行緒呼叫此方法時,會獲取該例項的物件鎖,方法未結束之前,其他執行緒只能去等待。當這個方法執行完時,才會釋放物件鎖。其他執行緒才有機會去搶佔這把鎖,去執行方法test,但是發生這一切的基礎應當是所有執行緒使用的同一個物件例項,才能實現互斥的現象。否則synchronized關鍵字將失去意義。
(但是如果該方法為類方法,即其修飾符為static,那麼synchronized 意味著某個呼叫此方法的執行緒當前會擁有該類的鎖,只要該執行緒持續在當前方法內執行,其他執行緒依然無法獲得方法的使用權!)
synchronized 用在程式碼塊的使用方式:synchronized(obj){//todo code here}
當執行緒執行到該程式碼塊內,就會擁有obj物件的物件鎖,如果多個執行緒共享同一個Object物件,那麼此時就會形成互斥!特別的,當obj == this時,表示當前呼叫該方法的例項物件。即
public void test() {
...
synchronized(this) {
// todo your code
}
...
}
此時,其效果等同於
public synchronized void test() {
// todo your code
}
使用synchronized程式碼塊,可以只對需要同步的程式碼進行同步,這樣可以大大的提高效率。
小結:
使用synchronized 程式碼塊相比方法有兩點優勢:
1、可以只對需要同步的使用
2、與wait()/notify()/nitifyAll()一起使用時,比較方便
----------------------------------------------------------------------------------------------------------------------------------------------------------
wait() 與notify()/notifyAll()
這三個方法都是Object的方法,並不是執行緒的方法!
wait():釋放佔有的物件鎖,執行緒進入等待池,釋放cpu,而其他正在等待的執行緒即可搶佔此鎖,獲得鎖的執行緒即可執行程式。而sleep()不同的是,執行緒呼叫此方法後,會休眠一段時間,休眠期間,會暫時釋放cpu,但並不釋放物件鎖。也就是說,在休眠期間,其他執行緒依然無法進入此程式碼內部。休眠結束,執行緒重新獲得cpu,執行程式碼。wait()和sleep()最大的不同在於wait()會釋放物件鎖,而sleep()不會!
notify(): 該方法會喚醒因為呼叫物件的wait()而等待的執行緒,其實就是對物件鎖的喚醒,從而使得wait()的執行緒可以有機會獲取物件鎖。呼叫notify()後,並不會立即釋放鎖,而是繼續執行當前程式碼,直到synchronized中的程式碼全部執行完畢,才會釋放物件鎖。JVM則會在等待的執行緒中排程一個執行緒去獲得物件鎖,執行程式碼。需要注意的是,wait()和notify()必須在synchronized程式碼塊中呼叫。
notifyAll()則是喚醒所有等待的執行緒。
為了說明這一點,舉例如下:
兩個執行緒依次列印"A""B",總共列印10次。
public class Consumer implements Runnable
{
@Override
public synchronized void run()
{
// TODO Auto-generated
method stub
int count
= 10;
while(count
> 0) {
synchronized (Test. obj)
{
System. out.print( "B");
count --;
Test. obj.notify(); //
主動釋放物件鎖
try {
Test. obj.wait();
} catch (InterruptedException
e) {
// TODO Auto-generated
catch block
e.printStackTrace();
}
}
}
}
}
public class Produce implements Runnable
{
@Override
public void run()
{
// TODO Auto-generated
method stub
int count
= 10;
while(count
> 0) {
synchronized (Test. obj)
{
//System.out.print("count = " + count);
System. out.print( "A");
count --;
Test. obj.notify();
try {
Test. obj.wait();
} catch (InterruptedException
e) {
// TODO Auto-generated
catch block
e.printStackTrace();
}
}
}
}
}
測試類如下:
public class Test
{
public static final Object obj = new Object();
public static void main(String[]
args) {
new Thread( new Produce()).start();
new Thread( new Consumer()).start();
}
}
這裡使用static obj作為鎖的物件,當執行緒Produce啟動時(假如Produce首先獲得鎖,則Consumer會等待),列印“A”後,會先主動釋放鎖,然後阻塞自己。Consumer獲得物件鎖,列印“B”,然後釋放鎖,阻塞自己,那麼Produce又會獲得鎖,然後...一直迴圈下去,直到count = 0.這樣,使用Synchronized和wait()以及notify()就可以達到執行緒同步的目的。
----------------------------------------------------------------------------------------------------------------------------------------------------------
除了wait()和notify()協作完成執行緒同步之外,使用Lock也可以完成同樣的目的。
ReentrantLock 與synchronized有相同的併發性和記憶體語義,還包含了中斷鎖等候和定時鎖等候,意味著執行緒A如果先獲得了物件obj的鎖,那麼執行緒B可以在等待指定時間內依然無法獲取鎖,那麼就會自動放棄該鎖。
但是由於synchronized是在JVM層面實現的,因此係統可以監控鎖的釋放與否,而ReentrantLock使用程式碼實現的,系統無法自動釋放鎖,需要在程式碼中finally子句中顯式釋放鎖lock.unlock();
同樣的例子,使用lock 如何實現呢?
public class Consumer implements Runnable
{
private Lock lock;
public Consumer(Lock
lock) {
this. lock =
lock;
}
@Override
public void run()
{
// TODO Auto-generated
method stub
int count
= 10;
while(
count > 0 ) {
try {
lock.lock();
count --;
System. out.print( "B");
} finally {
lock.unlock(); //主動釋放鎖
try {
Thread. sleep(91L);
} catch (InterruptedException
e) {
// TODO Auto-generated
catch block
e.printStackTrace();
}
}
}
}
}
public class Producer implements Runnable{
private Lock lock;
public Producer(Lock
lock) {
this. lock =
lock;
}
@Override
public void run()
{
// TODO Auto-generated
method stub
int count
= 10;
while (count
> 0) {
try {
lock.lock();
count --;
System. out.print( "A");
} finally {
lock.unlock();
try {
Thread. sleep(90L);
} catch (InterruptedException
e) {
// TODO Auto-generated
catch block
e.printStackTrace();
}
}
}
}
}
呼叫程式碼:
public class Test
{
public static void main(String[]
args) {
Lock lock = new ReentrantLock();
Consumer consumer = new Consumer(lock);
Producer producer = new Producer(lock);
new Thread(consumer).start();
new Thread( producer).start();
}
}
使用建議:
在併發量比較小的情況下,使用synchronized是個不錯的選擇,但是在併發量比較高的情況下,其效能下降很嚴重,此時ReentrantLock是個不錯的方案。
-------------------------------<全文完>-------------------------------------------------------------------------------------------------------
相關文章
- Java synchronized那點事Javasynchronized
- synchronized與Lock的區別synchronized
- synchronized 與 Lock 的對比synchronized
- Toast與Snackbar的那點事AST
- 四、Synchronized與Lock原理synchronized
- synchronized與Lock的區別與使用詳解synchronized
- PHP與MySQL通訊那點事PHPMySql
- PostgreSQL的那點事兒SQL
- vue元件的那點事Vue元件
- 跨域的那點事跨域
- oracle hints的那點事Oracle
- Lock 和 synchronized的區別synchronized
- 前端加密那點事前端加密
- 佈局那點事
- 面試那點事兒面試
- MySQL的timeout那點事MySql
- Netty的那點事兒Netty
- 郵件的那點事薦
- 【GoLang 那點事】gRPC 攔截器那點事,希望幫到你(六)GolangRPC
- 淺談HTTP快取與CDN快取的那點事HTTP快取
- 資料埋點測試的那點事
- 關於 Flex 的那點事兒Flex
- SpringCloud微服務的那點事SpringGCCloud微服務
- 淺談 Java HashMap 的那點事JavaHashMap
- 開源專案的那點事
- 推薦系統的那點事
- 研發遊戲的那點事兒遊戲
- Webpack4那點事Web
- Java 物件頭那點事Java物件
- 求職那點破事兒求職
- Android Activity那點事Android
- 談談JavaScript this那點事JavaScript
- 談談CORS那點事CORS
- Android GC 那點事AndroidGC
- 分散式鎖那點事分散式
- ThreadLocal 那點事兒thread
- UITableView優化那點事UIView優化
- HTTPS 加密那點事HTTP加密