91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

java同步之如何寫一個鎖Lock

發布時間:2020-10-01 11:09:43 來源:腳本之家 閱讀:168 作者:彤哥讀源碼 欄目:編程語言

問題

(1)自己動手寫一個鎖需要哪些知識?

(2)自己動手寫一個鎖到底有多簡單?

(3)自己能不能寫出來一個完美的鎖?

簡介

本篇文章的目標一是自己動手寫一個鎖,這個鎖的功能很簡單,能進行正常的加鎖、解鎖操作。

本篇文章的目標二是通過自己動手寫一個鎖,能更好地理解后面章節將要學習的AQS及各種同步器實現的原理。

分析

自己動手寫一個鎖需要準備些什么呢?

首先,在上一章學習synchronized的時候我們說過它的實現原理是更改對象頭中的MarkWord,標記為已加鎖或未加鎖。

但是,我們自己是無法修改對象頭信息的,那么我們可不可以用一個變量來代替呢?

比如,這個變量的值為1的時候就說明已加鎖,變量值為0的時候就說明未加鎖,我覺得可行。

其次,我們要保證多個線程對上面我們定義的變量的爭用是可控的,所謂可控即同時只能有一個線程把它的值修改為1,且當它的值為1的時候其它線程不能再修改它的值,這種是不是就是典型的CAS操作,所以我們需要使用Unsafe這個類來做CAS操作。

然后,我們知道在多線程的環境下,多個線程對同一個鎖的爭用肯定只有一個能成功,那么,其它的線程就要排隊,所以我們還需要一個隊列。

最后,這些線程排隊的時候干嘛呢?它們不能再繼續執行自己的程序,那就只能阻塞了,阻塞完了當輪到這個線程的時候還要喚醒,所以我們還需要Unsfae這個類來阻塞(park)和喚醒(unpark)線程。

基于以上四點,我們需要的神器大致有:一個變量、一個隊列、執行CAS/park/unpark的Unsafe類。

大概的流程圖如下圖所示:

java同步之如何寫一個鎖Lock

關于Unsafe類的相關講解請參考之前發的文章:

java Unsafe詳細解析

解決

一個變量

這個變量只支持同時只有一個線程能把它修改為1,所以它修改完了一定要讓其它線程可見,因此,這個變量需要使用volatile來修飾。

private volatile int state;

CAS

這個變量的修改必須是原子操作,所以我們需要CAS更新它,我們這里使用Unsafe來直接CAS更新int類型的state。

當然,這個變量如果直接使用AtomicInteger也是可以的,不過,既然我們學習了更底層的Unsafe類那就應該用(浪)起來。

private boolean compareAndSetState(int expect, int update) {
 return unsafe.compareAndSwapInt(this, stateOffset, expect, update);
}

一個隊列

隊列的實現有很多,數組、鏈表都可以,我們這里采用鏈表,畢竟鏈表實現隊列相對簡單一些,不用考慮擴容等問題。

這個隊列的操作很有特點:

放元素的時候都是放到尾部,且可能是多個線程一起放,所以對尾部的操作要CAS更新;

喚醒一個元素的時候從頭部開始,但同時只有一個線程在操作,即獲得了鎖的那個線程,所以對頭部的操作不需要CAS去更新。

private static class Node {
 // 存儲的元素為線程
 Thread thread;
 // 前一個節點(可以沒有,但實現起來很困難)
 Node prev;
 // 后一個節點
 Node next;

 public Node() {
 }

 public Node(Thread thread, Node prev) {
 this.thread = thread;
 this.prev = prev;
 }
}
// 鏈表頭
private volatile Node head;
// 鏈表尾
private volatile Node tail;
// 原子更新tail字段
private boolean compareAndSetTail(Node expect, Node update) {
 return unsafe.compareAndSwapObject(this, tailOffset, expect, update);
}

這個隊列很簡單,存儲的元素是線程,需要有指向下一個待喚醒的節點,前一個節點可有可無,但是沒有實現起來很困難,不信學完這篇文章你試試。

加鎖

public void lock() {
 // 嘗試更新state字段,更新成功說明占有了鎖
 if (compareAndSetState(0, 1)) {
 return;
 }
 // 未更新成功則入隊
 Node node = enqueue();
 Node prev = node.prev;
 // 再次嘗試獲取鎖,需要檢測上一個節點是不是head,按入隊順序加鎖
 while (node.prev != head || !compareAndSetState(0, 1)) {
 // 未獲取到鎖,阻塞
 unsafe.park(false, 0L);
 }
 // 下面不需要原子更新,因為同時只有一個線程訪問到這里
 // 獲取到鎖了且上一個節點是head
 // head后移一位
 head = node;
 // 清空當前節點的內容,協助GC
 node.thread = null;
 // 將上一個節點從鏈表中剔除,協助GC
 node.prev = null;
 prev.next = null;
}
// 入隊
private Node enqueue() {
 while (true) {
 // 獲取尾節點
 Node t = tail;
 // 構造新節點
 Node node = new Node(Thread.currentThread(), t);
 // 不斷嘗試原子更新尾節點
 if (compareAndSetTail(t, node)) {
 // 更新尾節點成功了,讓原尾節點的next指針指向當前節點
 t.next = node;
 return node;
 }
 }
}

(1)嘗試獲取鎖,成功了就直接返回;

(2)未獲取到鎖,就進入隊列排隊;

(3)入隊之后,再次嘗試獲取鎖;

(4)如果不成功,就阻塞;

(5)如果成功了,就把頭節點后移一位,并清空當前節點的內容,且與上一個節點斷絕關系;

(6)加鎖結束;

解鎖

// 解鎖
public void unlock() {
 // 把state更新成0,這里不需要原子更新,因為同時只有一個線程訪問到這里
 state = 0;
 // 下一個待喚醒的節點
 Node next = head.next;
 // 下一個節點不為空,就喚醒它
 if (next != null) {
 unsafe.unpark(next.thread);
 }
}

(1)把state改成0,這里不需要CAS更新,因為現在還在加鎖中,只有一個線程去更新,在這句之后就釋放了鎖;

(2)如果有下一個節點就喚醒它;

(3)喚醒之后就會接著走上面lock()方法的while循環再去嘗試獲取鎖;

(4)喚醒的線程不是百分之百能獲取到鎖的,因為這里state更新成0的時候就解鎖了,之后可能就有線程去嘗試加鎖了。

測試

上面完整的鎖的實現就完了,是不是很簡單,但是它是不是真的可靠呢,敢不敢來試試?!

直接上測試代碼:

private static int count = 0;

public static void main(String[] args) throws InterruptedException {
 MyLock lock = new MyLock();

 CountDownLatch countDownLatch = new CountDownLatch(1000);

 IntStream.range(0, 1000).forEach(i -> new Thread(() -> {
 lock.lock();

 try {
 IntStream.range(0, 10000).forEach(j -> {
 count++;
 });
 } finally {
 lock.unlock();
 }
// System.out.println(Thread.currentThread().getName());
 countDownLatch.countDown();
 }, "tt-" + i).start());

 countDownLatch.await();

 System.out.println(count);
}

運行這段代碼的結果是總是打印出10000000(一千萬),說明我們的鎖是正確的、可靠的、完美的。

總結

(1)自己動手寫一個鎖需要做準備:一個變量、一個隊列、Unsafe類。

(2)原子更新變量為1說明獲得鎖成功;

(3)原子更新變量為1失敗說明獲得鎖失敗,進入隊列排隊;

(4)更新隊列尾節點的時候是多線程競爭的,所以要使用原子更新;

(5)更新隊列頭節點的時候只有一個線程,不存在競爭,所以不需要使用原子更新;

(6)隊列節點中的前一個節點prev的使用很巧妙,沒有它將很難實現一個鎖,只有寫過的人才明白,不信你試試^^

彩蛋

(1)我們實現的鎖支持可重入嗎?

答:不可重入,因為我們每次只把state更新為1。如果要支持可重入也很簡單,獲取鎖時檢測鎖是不是被當前線程占有著,如果是就把state的值加1,釋放鎖時每次減1即可,減為0時表示鎖已釋放。

(2)我們實現的鎖是公平鎖還是非公平鎖?

答:非公平鎖,因為獲取鎖的時候我們先嘗試了一次,這里并不是嚴格的排隊,所以是非公平鎖。

以上就是本文的全部內容,希望對大家的學習有所幫助,也希望大家多多支持億速云。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

AI

从江县| 蕲春县| 七台河市| 桐庐县| 长宁区| 黄浦区| 康保县| 泸水县| 无棣县| 图片| 普格县| 临漳县| 全州县| 故城县| 六安市| 民县| 堆龙德庆县| 尚义县| 河南省| 肇庆市| 庄浪县| 班戈县| 平凉市| 绵阳市| 临清市| 衡南县| 宁城县| 镇巴县| 新乡市| 吉木萨尔县| 阜平县| 贵阳市| 辉南县| 峨眉山市| 嘉禾县| 河津市| 喜德县| 沁源县| 浏阳市| 哈巴河县| 平南县|