摘要:性能較好是因為避免了線程進入內核的阻塞狀態請求總數同時并發執行的線程數我們首先使用聲明一個所得實例,然后使用進行加鎖和解鎖操作。
ReentrantLock與鎖
可重入性:兩者都具有可重入性
鎖的實現:Synchronized是依賴jvm實現的,ReentrantLock是jdk實現的。(我們可以理解為一個是操作系統層面的實現另一個是用戶自己自己實現的)Synchronized的實現是jvm層面的很難看到其中的實現。而ReentrantLock是通過jvm實現的我們可以通過閱讀jvm源碼來查看實現。
性能區別:在Synchronized優化之前Synchronized的性能相比ReentrantLock差很多,在Synchronized引入了偏向鎖,輕量級鎖也就是自旋鎖之后了,兩者的性能相差不大了。在兩者都可用的情況下官方更推薦使用Synchronized,因為其寫法更簡單,Synchronized的優化就是借鑒了ReentrantLock中的cas技術。
功能區別:便利性,很明顯synchronized的使用更加便利,ReentrantLock在細粒度和靈活性中會優于Synchronized。
ReentrantLock可指定是公平鎖還是非公平鎖,Synchronized只能是非公平鎖。(公平鎖就是先等待的線程先獲得鎖)
ReentrantLock提供一個Condition類,可以分組喚醒需要喚醒的形成。synchronized是要嘛隨機喚醒一個線程要嘛喚醒所有的線程。
ReentrantLock提供了一種能夠中斷等待鎖的線程的機制lock.locInterruptibly(),ReentrantLock實現是一種自旋鎖通過循環調用,通過cas機制來實現加鎖。性能較好是因為避免了線程進入內核的阻塞狀態
@Slf4j public class LockExample2 { // 請求總數 public static int clientTotal = 5000; // 同時并發執行的線程數 public static int threadTotal = 200; 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(); } } }
我們首先使用 private final static Lock lock = new ReentrantLock()聲明一個所得實例,然后使用
lock.lock(); try { count++; } finally { lock.unlock(); }
進行加鎖和解鎖操作。
我們在通過一個例子來看看這個ReentrantReadWriteLock怎么用。
@Slf4j public class LockExample3 { private final Mapmap = new TreeMap<>(); private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); private final Lock readLock = lock.readLock(); private final Lock writeLock = lock.writeLock(); public Data get(String key) { readLock.lock(); try { return map.get(key); } finally { readLock.unlock(); } } public Set getAllKeys() { readLock.lock(); try { return map.keySet(); } finally { readLock.unlock(); } } public Data put(String key, Data value) { writeLock.lock(); try { return map.put(key, value); } finally { readLock.unlock(); } } class Data { } }
通過 private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock()聲明一個ReentrantReadWriteLock,然后再分別獲取 private final Lock readLock = lock.readLock() private final Lock writeLock = lock.writeLock()讀鎖和寫鎖。
我們在這個map讀的時候加上讀鎖在寫的時候加上寫鎖,但是這里有問題就是這個鎖是悲觀鎖,也就是說在執行寫鎖的時候一定不能有讀鎖,當讀操作特 特別多的時候很有可能會讓寫鎖一直無法執行。
我們看一下官方的例子學習一下,StampedLock
import java.util.concurrent.locks.StampedLock; public class LockExample4 { class Point { private double x, y; private final StampedLock sl = new StampedLock(); void move(double deltaX, double deltaY) { // an exclusively locked method long stamp = sl.writeLock(); try { x += deltaX; y += deltaY; } finally { sl.unlockWrite(stamp); } } //下面看看樂觀讀鎖案例 double distanceFromOrigin() { // A read-only method long stamp = sl.tryOptimisticRead(); //獲得一個樂觀讀鎖 double currentX = x, currentY = y; //將兩個字段讀入本地局部變量 if (!sl.validate(stamp)) { //檢查發出樂觀讀鎖后同時是否有其他寫鎖發生? stamp = sl.readLock(); //如果沒有,我們再次獲得一個讀悲觀鎖 try { currentX = x; // 將兩個字段讀入本地局部變量 currentY = y; // 將兩個字段讀入本地局部變量 } finally { sl.unlockRead(stamp); } } return Math.sqrt(currentX * currentX + currentY * currentY); } //下面是悲觀讀鎖案例 void moveIfAtOrigin(double newX, double newY) { // upgrade // Could instead start with optimistic, not read mode long stamp = sl.readLock(); try { while (x == 0.0 && y == 0.0) { //循環,檢查當前狀態是否符合 long ws = sl.tryConvertToWriteLock(stamp); //將讀鎖轉為寫鎖 if (ws != 0L) { //這是確認轉為寫鎖是否成功 stamp = ws; //如果成功 替換票據 x = newX; //進行狀態改變 y = newY; //進行狀態改變 break; } else { //如果不能成功轉換為寫鎖 sl.unlockRead(stamp); //我們顯式釋放讀鎖 stamp = sl.writeLock(); //顯式直接進行寫鎖 然后再通過循環再試 } } } finally { sl.unlock(stamp); //釋放讀鎖或寫鎖 } } } }
我們再將前面的里改成StampedLock
@Slf4j public class LockExample5 { // 請求總數 public static int clientTotal = 5000; // 同時并發執行的線程數 public static int threadTotal = 200; public static int count = 0; private final static StampedLock lock = new StampedLock(); 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() { long stamp = lock.writeLock(); try { count++; } finally { lock.unlock(stamp); } } }
這里和之前的不一樣的地方就是
long stamp = lock.writeLock(); try { count++; } finally { lock.unlock(stamp); }
在加鎖后會返回一個值,解鎖的時候需要傳入這個值。
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/77684.html
摘要:鎖與很好的隔離使用者與實現者所需要關注的領域。那么這個就是包裝線程并且放入到隊列的過程實現的方法。也證實了就是獲取鎖的線程的節點。如果發生異常取消請求,也就是將當前節點重隊列中移除。 前言 自從JDK1.5后,jdk新增一個并發工具包java.util.concurrent,提供了一系列的并發工具類。而今天我們需要學習的是java.util.concurrent.lock也就是它下面的...
摘要:接著線程過來通過方式獲取鎖,獲取鎖的過程就是通過操作變量將其值從變為。線程加鎖成功后還有一步重要的操作,就是將設置成為自己。線程屁顛屁顛的就去等待區小憩一會去了。 一、寫在前面 這篇文章,我們聊一聊Java并發中的核武器, AQS底層實現。 不管是工作三四年、還是五六年的在工作或者面試中涉及到并發的是時候總是繞不過AQS這個詞。 首先,確實還有很多人連AQS是什么都不知道,甚至有的竟...
摘要:在創建對象時,需要轉入一個值,用于初始化的成員變量,該成員變量表示屏障攔截的線程數。當到達屏障的線程數小于時,這些線程都會被阻塞住。當所有線程到達屏障后,將會被更新,表示進入新一輪的運行輪次中。 1.簡介 在分析完AbstractQueuedSynchronizer(以下簡稱 AQS)和ReentrantLock的原理后,本文將分析 java.util.concurrent 包下的兩個...
摘要:的主要功能和關鍵字一致,均是用于多線程的同步。而僅支持通過查詢當前線程是否持有鎖。由于和使用的是同一把可重入鎖,所以線程可以進入方法,并再次獲得鎖,而不會被阻塞住。公平與非公平公平與非公平指的是線程獲取鎖的方式。 1.簡介 可重入鎖ReentrantLock自 JDK 1.5 被引入,功能上與synchronized關鍵字類似。所謂的可重入是指,線程可對同一把鎖進行重復加鎖,而不會被阻...
閱讀 2390·2021-09-30 09:47
閱讀 1376·2021-09-28 09:35
閱讀 3254·2021-09-22 15:57
閱讀 2500·2021-09-22 14:59
閱讀 3644·2021-09-07 10:25
閱讀 3079·2021-09-03 10:48
閱讀 3043·2021-08-26 14:14
閱讀 945·2019-08-30 15:55