導(dǎo)讀:記錄下java中提供鎖的兩種方式:synchronized和Lock(juc里面提供的鎖)
一、問題的產(chǎn)生和解決
1、產(chǎn)生:Java允許多線程并發(fā)控制,所以當(dāng)多個線程同時操作一個共享的資源變量時,就對導(dǎo)致數(shù)據(jù)的不準(zhǔn)確或者數(shù)據(jù)間相互沖突。在多線程情況下,容易對數(shù)據(jù)造成臟讀,幻讀及不可重讀等多種并發(fā)問題。
2、解決:加入同步鎖避免當(dāng)前線程沒有完成操作時,被其他線程調(diào)用改用。從而保證了該變量的一致性和準(zhǔn)確性。下面說明下jdk提供的兩種鎖機(jī)制 synchronized和Lock(ReentrantLock(有代表性)等)
二、synchronized和lock的區(qū)別對比
一:
synchronized:
?????1、依賴JVM實(shí)現(xiàn)鎖,因此在這個關(guān)鍵字作用對象的作用范圍內(nèi),都是同一時刻只有一個線程可以進(jìn)行操作的
?????2、不可中斷鎖,適合競爭不激烈,可讀性好(競爭激烈時,性能下降的快)
二:
Lock:
?????1、依賴特殊的CPU指令,代碼實(shí)現(xiàn)。實(shí)現(xiàn)類中有代表的是ReentrantLock
?????2、可中斷鎖,多樣化同步,競爭激烈時能維持常態(tài)
三:
補(bǔ)充Atomic:競爭激烈時能維持常態(tài),比Lock性能好;只能同步一個值。
三、原子性--Synchronized
修飾代碼塊:大括號括起來的代碼,作用于調(diào)用的對象
修飾方法:整個方法,作用于調(diào)用的對象
修飾靜態(tài)方法:整個靜態(tài)方法,作用于所有對象
修飾類:括號括起來的部分,作用于所有對象
(對于synchronized具體鎖到什么程度,就需要根據(jù)實(shí)際場景分析。上面是4種形式,不同的方式的根本就是鎖的粒度不一樣。在滿足條件的情況下,盡可能的將鎖的范圍小一點(diǎn),可以加快處理速度。)
- 1、下面是相關(guān)示列代碼一
@Slf4j
public class SynchronizedExample{
// 修飾一個代碼塊
public void test1(int j) {
synchronized (this) {
for (int i = 0; i < 10; i++) {
log.info("test1 {} - {}", j, i);
}
}
}
// 修飾一個方法
public synchronized void test2(int j) {
for (int i = 0; i < 10; i++) {
log.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) {
//兩個不同對象同時操作,會不影響。example1和2的執(zhí)行是交叉執(zhí)行,不是example1執(zhí)行完之后,再執(zhí)行2
SynchronizedExample1 example1 = new SynchronizedExample1();
SynchronizedExample1 example2 = new SynchronizedExample1();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test1(1);
});
executorService.execute(() -> {
example2.test1(2);
});
}
}
@Slf4j
public class SynchronizedExample2 {
// 修飾一個類
public static void test1(int j) {
synchronized (SynchronizedExample2.class) {
for (int i = 0; i < 10; i++) {
log.info("test1 {} - {}", j, i);
}
}
}
// 修飾一個靜態(tài)方法
public static synchronized void test2(int j) {
for (int i = 0; i < 10; i++) {
log.info("test2 {} - {}", j, i);
}
}
public static void main(String[] args) {
//這里會按順序執(zhí)行,因?yàn)閟ynchronized是修飾這個類,所表現(xiàn)的和修飾靜態(tài)方法是一樣的。
SynchronizedExample2 example1 = new SynchronizedExample2();
SynchronizedExample2 example2 = new SynchronizedExample2();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
example1.test1(1);
});
executorService.execute(() -> {
example2.test1(2);
});
}
}
1、一個方法里面,整個一個都是一個同步代碼塊時,那跟修飾一個方法是一樣的。
2、一個方法里面,如果所有執(zhí)行代碼部分都是被synchronized修飾的一個類來包圍的時候,它和修飾一個靜態(tài)方法它的表現(xiàn)是一致的。
注意一點(diǎn):如果當(dāng)前的類是一個父類,子類繼承這個類之后,如果它想調(diào)用test2方法時是帶不上synchronized這個關(guān)鍵字的,原因是synchronized它不屬于方法聲明的一部分,需要注意的。
如果子類也想使用synchronized時,則需要在方法上顯示的聲明synchronized關(guān)鍵字才行。
四、ReentrantLock與鎖
ReentrantLock和Synchronized的區(qū)別
1、可重入性 (區(qū)別不大,因?yàn)樗鼈兌际峭粋€線程進(jìn)入一次鎖的計(jì)數(shù)器就自增1,所以要等到鎖的計(jì)數(shù)器下降為0時,才能釋放鎖)
2、 鎖的實(shí)現(xiàn)
??ReentrantLock 基于 JDK 實(shí)現(xiàn) (可通過源碼查看如何實(shí)現(xiàn))
??synchronized 基于 JVM 實(shí)現(xiàn)
3、性能的區(qū)別
????synchronized 比 ReentrantLock 差很多,但是后面synchronized 加入了 偏向鎖和輕量級鎖(自璇鎖)后性能和ReentrantLock 差不多了。在兩種方法都可用的情況下官方更建議使用synchronized,因?yàn)閷懛ǜ菀?。synchronized的優(yōu)化感覺就是借鑒了ReentrantLock中的CAS技術(shù),都是試圖在用戶態(tài)就把加鎖問題解決,避免進(jìn)入內(nèi)核態(tài)的線程阻塞。
4、功能區(qū)別
??synchronized 的使用比較方便簡潔并且是由編譯器去保證鎖的加鎖和釋放的
??ReentrantLock 需要手動聲明鎖的加鎖和釋放鎖,為了避免忘記手工釋放鎖,發(fā)生死鎖,所以最好在finally中加入釋放鎖操作
?? 在鎖的細(xì)粒度和靈活度比較,ReentrantLock 高于 synchronized
5、ReentrantLock 獨(dú)有的功能
??1、可指定是公平鎖還是非公平鎖 (公平鎖的含義:先等待的線程先獲取鎖)(synchronized只能是非公平鎖)
??2、提供了一個Condition類,可以分組喚醒需要喚醒的線程
??3、提供能夠中斷等待鎖的線程的機(jī)制,lock.lockInterruptibly()
?? 什么時候使用ReentrantLock?就是在你需要實(shí)現(xiàn)這三個獨(dú)有功能時就可以。其他情況下可以根據(jù)性能,業(yè)務(wù)場景時使用ReentrantLock還是synchronized。
?? ReentrantLock的實(shí)現(xiàn)是一種自璇鎖,通過循環(huán)調(diào)用CAS操作來實(shí)現(xiàn)加鎖,性能比較好是因?yàn)楸苊饩€程進(jìn)入內(nèi)核態(tài)的阻塞狀態(tài)

- 1、示列代碼
@Slf4j
public class LockExample1 {
// 請求總數(shù)
public static int clientTotal = 1000;
// 同時并發(fā)執(zhí)行的線程數(shù)
public static int threadTotal = 100;
public static int count = 0;
private final static Lock lock = new ReentrantLock();
public static void main(String[] args) throws Exception {
ExecutorService executorService = Executors.newCachedThreadPool();
final Semaphore semaphore = new Semaphore(threadTotal);
final CountDownLatch countDownLatch = new CountDownLatch(clientTotal);
for (int i = 0; i < clientTotal ; i++) {
executorService.execute(() -> {
try {
semaphore.acquire();
add();
semaphore.release();
} catch (Exception e) {
log.error("exception", e);
}
countDownLatch.countDown();
});
}
countDownLatch.await();
executorService.shutdown();
log.info("count:{}", count);
}
private static void add() {
lock.lock(); //加鎖
try {
count++;
} finally {
lock.unlock(); //釋放鎖
}
}
}
上面這個例子就是很簡單對add()操作進(jìn)行加鎖和釋放鎖,這個鎖的基本操作。但是ReentrantLock中還有很多方法可以使用,可以通過查看源碼了解,這也是ReentrantLock靈活性的一點(diǎn),可以通過源碼分析看是如何實(shí)現(xiàn)的。下面是有關(guān)ReentrantLock的一些方法。
@Slf4j
public class LockExample6 {
public static void main(String[] args) {
ReentrantLock reentrantLock = new ReentrantLock();
Condition condition = reentrantLock.newCondition();
// 線程一
new Thread(() -> {
try {
reentrantLock.lock();
log.info("wait signal"); // 1
condition.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("get signal"); // 4
reentrantLock.unlock();
}).start();
//線程二
new Thread(() -> {
reentrantLock.lock();
log.info("get lock"); // 2
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
condition.signalAll();
//condition.signal(); // 喚醒單個
log.info("send signal"); // 3
reentrantLock.unlock();
}).start();
}
}
上面標(biāo)記了程序的執(zhí)行順序。從reentrantLock中取出了Condition,線程一通過調(diào)用reentrantLock.lock()方法,這個時候線程一就加入到了aqs的等待隊(duì)列里面去,輸出”wait signal"。一旦調(diào)用了condition.await()之后,這個線程就從aqs隊(duì)列中移除了(對應(yīng)的操作是鎖的釋放),然后馬上加入到了Condition中的等待隊(duì)列里去了。線程二因?yàn)榫€程一釋放了鎖被喚醒,并判斷是否可以取到鎖所以線程二獲取到鎖也加入到了aqs的等待隊(duì)列中,然后輸出了"get lock",接下來之后調(diào)用了condition.signalAll();(發(fā)送信號的方法),然后輸出了"send signal"。這個時候Condition的等待隊(duì)列里面有線程一的一個節(jié)點(diǎn),于是被取出來加到了aqs的隊(duì)列中去,這個時候線程一并沒有被喚醒而是等到發(fā)送信號的方法結(jié)束后并調(diào)用了unlock()釋放鎖之后,重新喚醒繼續(xù)執(zhí)行。這個時候輸出了"get signal",之后線程一釋放鎖整個線程執(zhí)行完畢。
總結(jié):整個過程中靠節(jié)點(diǎn)在aqs的等待隊(duì)列和condition的等待隊(duì)列中來回移動實(shí)現(xiàn)的。condition作為一個條件類很好的維護(hù)了一個等待信號的隊(duì)列并在適合的時候?qū)⒐?jié)點(diǎn)加入到aqs的等待隊(duì)列中實(shí)現(xiàn)喚醒操作。