MySQL InnoDB MVCC

MySQL 原理篇

MVCC

MVCC 的定義

MVCC(Multiversion concurrency control):多版本併發控制,併發訪問(讀或寫)數據庫時,對正在事務內處理的數據做多版本的管理。以達到用來避免寫操作的堵塞,從而引發讀操作的併發問題。

MVCC 邏輯流程

插入

MySQL 在每一行數據中都會默認添加一些隱藏列 DB_TRX_IDDB_ROLL_PT。

上面圖中的執行步驟如下:

  1. 手動開啟事務,從 InnoDB 引擎中獲取一個全局事務ID(1)
  2. 然後往 teacher 表中插入兩條數據,同時設置數據行的版本號為當前事務ID,刪除版本號為 NULL

思考:如果事務是自動提交的(SET AUTOCOMMIT = NO),且未手動開啟事務,執行如下兩條 SQL,插入的數據會是什麼樣子的?

INSERT INTO teacher (NAME, age) VALUE ('seven', 18) ;

INSERT INTO teacher (NAME, age) VALUE ('qingshan', 19) ;

因為事務是自動提交的,所以兩條插入語句會分別獲取事務ID,所以這裏插入的數據行的版本號是1和2。

刪除

上面圖中的執行步驟如下:

  1. 手動開啟事務,從 InnoDB 引擎中獲取一個全局事務ID(22)
  2. 然後執行一條刪除語句,InnoDB 會找到這條記錄,把它的刪除版本號設置為當前事務ID

修改

上面圖中的執行步驟如下:

  1. 手動開啟事務,從 InnoDB 引擎中獲取一個全局事務ID(33)
  2. 然後執行一條修改語句,InnoDB 會找到這條記錄,copy 一份原數據插入到表中,將新行數據的數據行的版本號的值設置為當前事務ID,將原行數據的刪除版本號的值設置為當前事務ID

查詢

上面圖中的執行步驟如下:

  1. 手動開啟事務,從 InnoDB 引擎中獲取一個全局事務ID(44)
  2. 根據數據查詢規則的描述
    1. 查找數據行版本早於當前事務版本的數據行,發現表中三行數據都滿足條件
    2. 查找刪除版本號要麼為 NULL,要麼大於當前事務版本號的記錄,發現只有最後一條數據滿足條件(1, seven, 19)

案例分析

數據準備:

CREATE TABLE `teacher` (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `name` varchar(32) NOT NULL,
  `age` int(11) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8mb4;

INSERT  INTO teacher(id,NAME,age) VALUES (1,'seven',18);
INSERT  INTO teacher(id,NAME,age) VALUES (2,'qingshan',20);

案例一

-- 事務A執行
BEGIN;                                     -- 1
SELECT * FROM teacher;                       -- 2
COMMIT;

--事務B執行
BEGIN;                                     -- 3
UPDATE teacher SET age =28 WHERE id=1;     -- 4
COMMIT;

案例一的執行步驟是:1,2,3,4,2,執行效果如下圖所示:

雖然在執行 3,4 步驟的時候更新 id=1 的數據,但是根據 MVCC 的查詢邏輯流程,再次執行2,獲取到的數據依然和第一次一樣。

案例二

-- 事務A執行
BEGIN;                                     -- 1
SELECT * FROM teacher;                       -- 2
COMMIT;

--事務B執行
BEGIN;                                     -- 3
UPDATE teacher SET age =28 WHERE id=1;     -- 4
COMMIT;

案例二的執行步驟是:3,4,1,2,執行效果如下圖所示:

根據 MVCC 的查詢邏輯流程,執行1,2,獲取到的數據是事務B未提交的數據,這個是有問題的。

分析了案例一和案例二,發現 MVCC 不能解決案例二的問題,InnoDB 會使用 Undo log 解決案例二的問題。

Undo Log

Undo Log 的定義

Undo:意為取消,以撤銷操作為目的,返回指定某個狀態的操作。

Undo Log:數據庫事務提交之前,會將事務修改數據的鏡像(即修改前的舊版本)存放到 undo 日誌里,當事務回滾時,或者數據庫奔潰時,可以利用 undo 日誌,即舊版本數據,撤銷未提交事務對數據庫產生的影響。。

  • 對於 insert 操作,undo 日誌記錄新數據的 PK(ROW_ID),回滾時直接刪除;
  • 對於 delete/update 操作,undo 日誌記錄舊數據 row,回滾時直接恢復;
  • 他們分別存放在不同的buffer里。

Undo Log 是為了實現事務的原子性而出現的產物。

 

Undo Log 實現事務原子性:事務處理過程中,如果出現了錯誤或者用戶執行了 ROLLBACK 語句,MySQL 可以利用 Undo Log 中的備份將數據恢復到事務開始之前的狀態。

InnoDB 發現可以基於 Undo Log 來實現多版本併發控制。

Undo Log 在 MySQL InnoDB 存儲引擎中用來實現多版本併發控制。

 

Undo Log 實現多版本併發控制:事務未提交之前,Undo Log 保存了未提交之前的版本數據,Undo Log 中的數據可作為數據舊版本快照供其他併發事務進行快照讀。

分析下圖中 SQL 的執行過程。

  • 事務A手動開啟事務,執行更新操作,首先會把更新命中的數據拷貝到 Undo Buffer 中
  • 事務B手動開啟事務,執行查詢操作,會讀取 Undo Log 中數據返回,進行快照度

當前讀和快照讀

快照讀

SQL 讀取的數據是快照版本,也就是歷史版本,普通的 SELECT 就是快照讀。

InnoDB 快照讀,數據的讀取將由 cache(原本數據)+ Undo Log(事務修改過的數據)兩部分組成。

當前讀

SQL 讀取的數據是最新版本,通過鎖機制來保證讀取的數據無法通過其他事務進行修改。

UPDATE 、DELETE 、INSERT 、SELECT … LOCK IN SHARE MODE 、SELECT … FOR UPDATE 都是當前讀,這些操作在《MySQL InnoDB 鎖》這篇文章中有過演示,事務A執行這些 SQL,會阻塞事務B的 SQL 執行。

在 InnoDB 引擎裏面,快照讀通過 MVCC 解決幻讀的問題,當前讀通過 Next-Key Locks 解決幻讀的問題。

Redo Log

Redo Log 的定義

Redo:顧名思義就是重做。以恢復操作為目的,重現操作。

Redo Log:指事務中操作的任何數據,將最新的數據備份到一個地方(Redo Log)。

Redo Log 的持久化:不是隨着事務的提交才寫入的,而是在事務的執行過程中,便開始寫入 Redo Log 中,具體的落盤策略可以進行配置。

Redo Log 是為了實現事務的持久性而出現的產物。

Redo Log 實現事務持久性:防止在發生故障的時間點,尚有臟頁未寫入表的 IBD 文件中,在重啟 MySQL 服務的時候,根據 Redo Log 進行重做,從而達到事務的未入磁盤數據進行持久化這一特性。

根據下圖分析 Redo Log 的執行流程

InnoDB 不是每一次提交事務都把數據從緩存區持久化到硬盤的,因為每次提交事務都把數據持久化到硬盤,效率很低,每一次持久化都需要執行 IO 操作。

InnoDB 會把每次數據變化會先進入 Redo Buffer 中,事務提交了,會根據策略把新的數據寫入 Redo Log 中,InnoDB 就會認為這次事務提交成功了,數據並不一定馬上就進入表的 IBD 文件中。

疑問:持久化到 Redo Log 中和持久化到表的 IBD 文件一樣都是 IO 操作,為什麼要設計 Redo Log 呢?

其實是因為持久化到 Redo Log 中是順序 IO 的操作,而持久化到表的 IBD 文件中是一個隨機 IO 的操作,比如我們需要更新 id=1 和 id=8 的數據,如果是 Redo Log,就只需要把更新的數據順序存入 Redo Log 中;但如果是表的 IBD 文件,就需要先找到 id=1 和 id=8 的兩個不連續的磁盤文件地址,再做持久化操作,影響數據庫服務的併發性能。

Redo Log 的持久化配置

指定 Redo Log 記錄在 {datadir}/ib_logfile1 和 ib_logfile2 兩個文件中,可以通過 innodb_log_group_home_dir配置指定目錄存儲。

一旦事務成功提交且數據持久化到表的 IBD 文件中之後,此時 Redo Log 中的對應事務數據記錄就失去了意義,所 以 Redo Log 的寫入是日誌文件循環寫入的過程,也就是覆蓋寫的過程。

  • 指定 Redo Log 日誌文件組中的數量 innodb_log_files_in_group 默認為2
  • 指定 Redo Log 每一個日誌文件最大存儲量 innodb_log_file_size 默認48M
  • 指定 Redo Log 在 cache/buffer 中的 buffer 池大小 innodb_log_buffer_size 默認16M

Redo Buffer 持久化到 Redo Log 的策略,通過設置 Innodb_flush_log_at_trx_commit 的值:

  • 取值0:每秒提交 Redo buffer -> Redo Log OS cache -> flush cache to disk,可能丟失一秒內的事務數據。
  • 取值1(默認值):每次事務提交執行 Redo Buffer -> Redo Log OS cache -> flush cache to disk,最安全,性能最差的方式
  • 取值2:每次事務提交執行 Redo Buffer -> Redo log OS cache 再每一秒執行 -> flush cache to disk 操作

一般建議選擇取值2,因為 MySQL 掛了最多損失一次事務提交的數據,整個服務期掛了才會損失一秒的事務提交數據。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

武漢肺炎疫情影響奈良鹿? 愛鹿協會:野生植物才是主食 鹿沒人餵不會餓死

文:宋瑞文

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

鮪魚業刺網混獲最大苦主 研究:印度洋海豚數量減少近90%

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

路上跑的全是電動車!印度設下 2030 年達標,擬推電動車免頭期款政策

被視為未來趨勢的電動車,目前在車市中的市占仍然相當低,而若談到推動電動車發展,印度絕對是當前最野心勃勃的國家。近日,印度電力部長提出新政策,要讓電動車購車族免付頭期款,以提高民眾購買的意願,最終目標是要在 2030 年前,讓印度境內上路的車輛 100% 為電動車。

印度為目前全球空氣污染最嚴重的國家之一,國內有 13 個城市名列全球前 20 大最受污染城市。為了從根本改善印度的空氣品質,降低對化石燃料的依賴勢在必行,而印度電力部懷抱著強大的信心,表示不造成人民經濟上的壓力,就能全力推動國內電動車發展。   印度電力部長 Piyush Goyal 日前信誓旦旦的表示,「印度將成為全球首個在如此國土規模大小之下,100% 全電動車上路的國家」,且 Goyal 透露,將努力嘗試讓這項計畫由電力部的資金供應運作,「我們不需要用到政府與印度人民的任何一毛錢」。   有了電動車購車零頭期款的政策,Goyal 相信,這可提高購車意願,使人們更輕鬆的將省下大筆油錢轉往購買電動車。Goyal 透露,目前正與其他政府官員合作,由印度道路運輸與公路部部長 Nitin Gadkari、石油部長 Dharmendra Pradhan 及環境部長 Prakash Javadekar 帶領成立的工作小組,商討與評估這個政策提議是否可行,以及「確保電動車車主能享有便宜的充電價格」。   雖然利用電動車取代排放溫室氣體的傳統汽車,能夠為空氣品質本就相當糟糕的印度,減少交通運輸這部分所造成的碳排放,但也有人質疑,推動電動車的結果將造成電力需求上升,最終可能會導致供給發電的能源產業製造更多碳排放量也說不定,畢竟,印度目前約 60% 的能源產出來自燃煤發電,而燃煤發電廠在印度已經是一大污染來源之一,且其溫室氣體排放量在這幾年依然持續成長中。   此外,印度曾對外公開其再生能源目標,要使再生能源裝置量從 2016 年的不到 12 GW,增加到 2022 年的 175 GW,根據先前的報導曾指出,印度要達到 175 GW 的目標,所需花費的資金相當「可觀」,若又加上推動電動車發展所提出的免付頭期款政策,可能會讓印度走向再生能源主導之路,更顯困難重重了。

(首圖來源: CC BY 2.0)

(本文授權轉載自《》─〈〉)

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

4. 彤哥說netty系列之Java NIO實現群聊(自己跟自己聊上癮了)

你好,我是彤哥,本篇是netty系列的第四篇。

歡迎來我的公從號彤哥讀源碼系統地學習源碼&架構的知識。

簡介

上一章我們一起學習了Java中的BIO/NIO/AIO的故事,本章將帶着大家一起使用純純的NIO實現一個越聊越上癮的“群聊系統”。

業務邏輯分析

首先,我們先來分析一下群聊的功能點:

(1)加入群聊,並通知其他人;

(2)發言,並通知其他人;

(3)退出群聊,並通知其他人;

一個簡單的群聊系統差不多這三個功能足夠了,為了方便記錄用戶信息,當用戶加入群聊的時候自動給他分配一個用戶ID。

業務實現

上代碼:

// 這是一個內部類
private static class ChatHolder {
    // 我們只用了一個線程,用普通的HashMap也可以
    static final Map<SocketChannel, String> USER_MAP = new ConcurrentHashMap<>();

    /**
     * 加入群聊
     * @param socketChannel
     */
    static void join(SocketChannel socketChannel) {
        // 有人加入就給他分配一個id,本文來源於公從號“彤哥讀源碼”
        String userId = "用戶"+ ThreadLocalRandom.current().nextInt(Integer.MAX_VALUE);
        send(socketChannel, "您的id為:" + userId + "\n\r");

        for (SocketChannel channel : USER_MAP.keySet()) {
            send(channel, userId + " 加入了群聊" + "\n\r");
        }

        // 將當前用戶加入到map中
        USER_MAP.put(socketChannel, userId);
    }

    /**
     * 退出群聊
     * @param socketChannel
     */
    static void quit(SocketChannel socketChannel) {
        String userId = USER_MAP.get(socketChannel);
        send(socketChannel, "您退出了群聊" + "\n\r");
        USER_MAP.remove(socketChannel);

        for (SocketChannel channel : USER_MAP.keySet()) {
            if (channel != socketChannel) {
                send(channel, userId + " 退出了群聊" + "\n\r");
            }
        }
    }

    /**
     * 擴散說話的內容
     * @param socketChannel
     * @param content
     */
    public static void propagate(SocketChannel socketChannel, String content) {
        String userId = USER_MAP.get(socketChannel);
        for (SocketChannel channel : USER_MAP.keySet()) {
            if (channel != socketChannel) {
                send(channel, userId + ": " + content + "\n\r");
            }
        }
    }

    /**
     * 發送消息
     * @param socketChannel
     * @param msg
     */
    static void send(SocketChannel socketChannel, String msg) {
        try {
            ByteBuffer writeBuffer = ByteBuffer.allocate(1024);
            writeBuffer.put(msg.getBytes());
            writeBuffer.flip();
            socketChannel.write(writeBuffer);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

服務端代碼

服務端代碼直接使用上一章NIO的實現,只不過這裏要把上面實現的業務邏輯適時地插入到相應的事件中。

(1)accept事件,即連接建立的時候,說明加入了群聊;

(2)read事件,即讀取數據的時候,說明有人說話了;

(3)連接斷開的時候,說明退出了群聊;

OK,直接上代碼,為了與上一章的代碼作區分,彤哥特意加入了一些標記:

public class ChatServer {
    public static void main(String[] args) throws IOException {
        Selector selector = Selector.open();
        ServerSocketChannel serverSocketChannel = ServerSocketChannel.open();
        serverSocketChannel.bind(new InetSocketAddress(8080));
        serverSocketChannel.configureBlocking(false);
        // 將accept事件綁定到selector上
        serverSocketChannel.register(selector, SelectionKey.OP_ACCEPT);

        while (true) {
            // 阻塞在select上
            selector.select();
            Set<SelectionKey> selectionKeys = selector.selectedKeys();
            // 遍歷selectKeys
            Iterator<SelectionKey> iterator = selectionKeys.iterator();
            while (iterator.hasNext()) {
                SelectionKey selectionKey = iterator.next();
                // 如果是accept事件
                if (selectionKey.isAcceptable()) {
                    ServerSocketChannel ssc = (ServerSocketChannel) selectionKey.channel();
                    SocketChannel socketChannel = ssc.accept();
                    System.out.println("accept new conn: " + socketChannel.getRemoteAddress());
                    socketChannel.configureBlocking(false);
                    socketChannel.register(selector, SelectionKey.OP_READ);
                    // 加入群聊,本文來源於公從號“彤哥讀源碼”
                    ChatHolder.join(socketChannel);
                } else if (selectionKey.isReadable()) {
                    // 如果是讀取事件
                    SocketChannel socketChannel = (SocketChannel) selectionKey.channel();
                    ByteBuffer buffer = ByteBuffer.allocate(1024);
                    // 將數據讀入到buffer中
                    int length = socketChannel.read(buffer);
                    if (length > 0) {
                        buffer.flip();
                        byte[] bytes = new byte[buffer.remaining()];
                        // 將數據讀入到byte數組中
                        buffer.get(bytes);

                        // 換行符會跟着消息一起傳過來
                        String content = new String(bytes, "UTF-8").replace("\r\n", "");
                        if (content.equalsIgnoreCase("quit")) {
                            // 退出群聊,本文來源於公從號“彤哥讀源碼”
                            ChatHolder.quit(socketChannel);
                            selectionKey.cancel();
                            socketChannel.close();
                        } else {
                            // 擴散,本文來源於公從號“彤哥讀源碼”
                            ChatHolder.propagate(socketChannel, content);
                        }
                    }
                }
                iterator.remove();
            }
        }
    }
}

測試

打開四個XSHELL客戶端,分別連接telnet 127.0.0.1 8080,然後就可以開始群聊了。

彤哥發現,自己跟自己聊天也是會上癮的,完全停不下來,不行了,我再去自聊一會兒^^

總結

本文彤哥跟着大家一起實現了“群聊系統”,去掉註釋也就100行左右的代碼,是不是非常簡單?這就是NIO網絡編程的魅力,我發現寫網絡編程也上癮了^^

問題

這兩章我們都沒有用NIO實現客戶端,你知道怎麼實現嗎?

提示:服務端需要監聽accept事件,所以需要有一個ServerSocketChannel,而客戶端是直接去連服務器了,所以直接用SocketChannel就可以了,一個SocketChannel就相當於一個Connection。

最後,也歡迎來我的公從號彤哥讀源碼系統地學習源碼&架構的知識。

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理
【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

併發編程-深入淺出AQS

AQS是併發編程中非常重要的概念,它是juc包下的許多併發工具類,如CountdownLatch,CyclicBarrier,Semaphore 和鎖, 如ReentrantLock, ReaderWriterLock的實現基礎,提供了一個基於int狀態碼和隊列來實現的併發框架。本文將對AQS框架的幾個重要組成進行簡要介紹,讀完本文你將get到以下幾個點:

  1. AQS進行併發控制的機制是什麼

  2. AQS獨佔和共享模式是如何實現的

  3. 同步隊列和條件等待隊列的區別,和數據出入隊原則

一,AQS基本概念

AQS(AbstractQueuedSynchronizer)是用來構建鎖或者其他同步組件的基礎框架,它使用了一個int成員變量來表示狀態,通過內置的FIFO(first in,first out)隊列來完成資源獲取線程的排隊工作。

隊列可分為兩種,一種是同步隊列,是程序執行入口出處的等待隊列;而另一種則是條件等待隊列,隊列中的元素是在程序執行時在某個條件上發生等待。

1.1 獨佔or共享模式

AQS支持兩種獲取同步狀態的模式既獨佔式和共享式。顧名思義,獨佔式模式同一時刻只允許一個線程獲取同步狀態,而共享模式則允許多個線程同時獲取。

1.2 同步隊列

當一個線程嘗試獲取同步狀態失敗時,同步器會將這個線程以及等待狀態等信息構造成一個節點加入到等待隊列中,同時會阻塞當前線程,當同步狀態釋放時,會把首節點中的線程喚醒,使其再次嘗試重複獲取同步隊列。

1.3 條件隊列

AQS內部類ConditionObject來實現的條件隊列,當一個線程獲取到同步狀態,但是卻通過Condition調用了await相關的方法時,會將該線程封裝成Node節點並加入到條件隊列中,它的結構和同步隊列相同。

二,獨佔or共享模式

AQS框架中,通過維護一個int類型的狀態,來進行併發控制,線程通常通過修改此狀態信息來表明當前線程持有此同步狀態。AQS則是通過保存修改狀態線程的引用來實現獨佔和共享模式的。

/**
 * 獲取同步狀態
 */
public final void acquire(int arg) {
    //嘗試獲取同步狀態, 如果嘗試獲取到同步狀態失敗,則加入到同步隊列中
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}
/**
 * 嘗試獲取同步狀態【子類中實現】,因為aqs基於模板模式,僅提供基於狀態和同步隊列的實 
 * 現思路,具體的實現由子類決定
 */
protected final boolean tryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    if (c == 0) {
        // 如果當前狀態值為0,並且等待隊列中沒有元素,執行修改狀態值操作
        if (!hasQueuedPredecessors() &&
            compareAndSetState(0, acquires)) {
            // 修改狀態值成功,記錄當前持有同步狀態的線程信息
            setExclusiveOwnerThread(current);
            return true;
        }
        // 如果當前線程已經持有同步狀態,繼續修改同步狀態【重入鎖實現原理】
    } else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0)
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

/**
 * 根據傳入的模式以及當前線程信息創建一個隊列的節點並加入到同步隊列尾部
 */
private Node addWaiter(Node mode) {
    Node node = new Node(Thread.currentThread(), mode);
    // Try the fast path of enq; backup to full enq on failure
    Node pred = tail;
    if (pred != null) {
        node.prev = pred;
        if (compareAndSetTail(pred, node)) {
            pred.next = node;
            return node;
        }
    }
    enq(node);
    return node;
}
/**
 * 同步隊列中節點,嘗試獲取同步狀態
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        // 自旋(死循環)
        for (;;) {
            // 只有當前節點的前驅節點是頭節點時才會嘗試執行獲取同步狀態操作
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

獨佔式是如何控製得?

當修改狀態信息成功后,如果執行的是獨佔式操作,AQS的具體實現類中會保存當前線程的信息來聲明同步狀態已被當前線程佔用,此時其他線程再嘗試獲取同步狀態會返回false。

三,同步隊列

3.1 隊列中保存那些信息?

同步隊列節點中主要保存着線程的信息以及模式(共享or獨佔)。

3.2 何時執行入隊操作?

/**
 * 獲取同步狀態
 */
public final void acquire(int arg) {
    //嘗試獲取同步狀態, 如果嘗試獲取到同步狀態失敗,則加入到同步隊列中
    if (!tryAcquire(arg) &&
        acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
        selfInterrupt();
}

復用上文中的代碼,不難看出再獲取同步狀態失敗后,會執行入隊操作。

3.3 何時執行出隊操作?

當線程獲取同步狀態失敗時,會被封裝成Node節點加入到等待隊列中,此時所有節點都回進入自旋過程,首先判斷自己prev是否時頭節點,如果是則嘗試獲取同步狀態。
被阻塞線程的喚醒主要以靠前驅節點的出隊或阻塞線程被中斷來實現。

/**
 * 同步隊列中節點,嘗試獲取同步狀態
 * 
 * 1. 當一個線程獲取到同步狀態時,會將當前線程構造程Node並設置為頭節點
 * 2. 並將原始的head節點設置為null,以便於垃圾回收
 */
final boolean acquireQueued(final Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        for (;;) {
            final Node p = node.predecessor();
            if (p == head && tryAcquire(arg)) {
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

四,條件等待隊列

條件變量(ConidtionObject)是AQS中的一個內部類,用來實現同步隊列機制。同步隊列復用了等待隊列中Node節點,所以同步隊列到等待隊列中不需要進行額外的轉換。

4.1 什麼時候執行入隊操作?

當線程獲取到同步狀態,但是在臨界區中調用了await()方法,此時該線程會被加入到對應的條件隊列匯總。
ps: 臨界區,加鎖和釋放鎖之間的代碼區域

/**
 * ConditionObject中的await方法,調用后使得當前執行線程加入條件等待隊列
 */
public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    Node node = addConditionWaiter();
    // -----省略代碼------
}
/**
 * 添加等待線程
 */
private Node addConditionWaiter() {
    Node t = lastWaiter;
    // -----省略代碼------
    // 將當前線程構造程條件隊列節點,並加入到隊列中
    Node node = new Node(Thread.currentThread(), Node.CONDITION);
    if (t == null)
        firstWaiter = node;
    else
        t.nextWaiter = node;
    lastWaiter = node;
    return node;
}

4.2 什麼時候執行出隊操作?

當對應的Conditioni調用signial/signalAll()方法時回選擇從條件隊列中出隊列,同步隊列是通過自旋的方式獲取同步狀態,而條件隊列中的節點則通過通知的方式出隊。條件隊列中的節點被喚醒後會加入到入口等待隊列中。

/**
 * 喚醒當前條件等到隊列中的所有等待線程
 */
public final void signalAll() {
    if (!isHeldExclusively())
        throw new IllegalMonitorStateException();
    Node first = firstWaiter;
    if (first != null)
        doSignalAll(first);
}
/**
 * 遍歷隊列,將元素從條件隊列 加入到 同步隊列
 */
private void doSignalAll(Node first) {
    lastWaiter = firstWaiter = null;
    do {
        Node next = first.nextWaiter;
        first.nextWaiter = null;
        transferForSignal(first);
        first = next;
    } while (first != null);
}
final boolean transferForSignal(Node node) {
    // -----省略代碼------
    // 執行入隊操作,將node添加到同步隊列中
    Node p = enq(node);
    int ws = p.waitStatus;
    if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
        LockSupport.unpark(node.thread);
    return true;
}

五,總結

  1. 使用Node實現的FIFO隊列,可以用於構建鎖或者其他同步裝置的基礎框架
  2. 利用一個int類型的屬性表示狀態
  3. 使用模板方法模式,子類可以通過繼承它來管理狀態實現各種併發工具
  4. 可以同時實現獨佔和共享模式

本文對AQS的基本原理進行的簡要的描述,對於子類的公平性和非公平行實現,中斷,隊列中節點的等待狀態,cas等操作沒有進行探討,感興趣的小夥伴可以進行源碼閱讀或者查閱相關資料。

六,Q&A

Question1: 在java中通常使用synchronized來實現方法同步,AQS中通過CAS保證了修改同步狀態的一致性問題,那麼對比synchronized,cas有什麼優勢不同與優勢呢?你還知道其他無鎖併發的策略嗎?

本站聲明:網站內容來源於博客園,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

生魚片注意!海獸胃線蟲暴增283倍

摘錄自2020年3月23日Yahoo!新聞報導

華盛頓大學研究團隊於近日指出,寄生在魚類的線蟲「海獸胃線蟲」 (Anisakis),自1978年至2015年的這段時間暴增283倍。

對此,威斯康辛大學水海洋學教授伍德(Chelsea Wood)表示,儘管對人類健康風險低,但對於海豚、鯨魚、海報等海洋哺乳動物,可能產生有害影響,內容提到若人類吃到活生生的海獸胃線蟲,蟲子會入侵腸壁,進而引發類似食物中毒的症狀,例如噁心嘔吐腹瀉等等,但蟲子幾乎會在幾天後就死亡,症狀隨即消失,因此大多數人都認為只是單純的食物中毒而很難被查覺到。

雖然海獸胃線蟲不能在人類體內存活太久,不過卻可以在海洋哺乳動物中生活並繁殖,伍德表示,他們仍不確定線蟲大量爆增的原因,氣候變遷、肥料、洋流等等所產生的養分,以及同其海洋哺乳動物的數量增加,都可能是潛在原因。

食品安全
糧食
生活環境
永續發展
國際新聞
生魚片
線蟲

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?

日本男童「免農藥除草大作戰」 農夫大讚:非常有效

摘錄自2020年3月24日中時報導

一名日本網友在推特表示,她在家中庭院內種植秋葵,但只要一到夏季,雜草就會生長得特別快,而她就讀小學的兒子看到這種情況,便主動說要以「除草」為主題,當作暑假作業來做研究,而且要「不使用農藥」。

兒子在三年間嘗試過多種方法,都無法完全清除雜草。某一天他突然靈機一動,發現土壤的軟硬度是導致雜草生長的關鍵,於是他決定每天在土壤上跑步。從今年的1月5日起到3月1日,不論晴雨,他每天都在土壤上奔跑,每次跑30分鐘,沒想到短短2個月後,雜草就不再生長了。

也有農民認同此方法「身為一名農民,這是非常有效的除草方法,可在雜草種子發芽時挪動土壤,在雜草生根前徹底將其消滅。」

環境經濟
農林漁牧業
國際新聞
日本
除草

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

10種改變 看懂武漢肺炎正對能源、氣候變遷造成什麼影響

環境資訊中心綜合外電;姜唯 編譯;林大利 審校

本站聲明:網站內容來源環境資訊中心https://e-info.org.tw/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※如何讓商品強力曝光呢? 網頁設計公司幫您建置最吸引人的網站,提高曝光率!!

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

大陸寄台灣空運注意事項

大陸海運台灣交貨時間多久?

※避免吃悶虧無故遭抬價!台中搬家公司免費估價,有契約讓您安心有保障!

上汽集團150億定增用於投資新能源汽車、車聯網等

上汽集團4月20日晚間公告稱,中國證監會發行審核委員會於4月20日對公司非公開發行A股股票的申請進行了審核。根據會議審核結果,公司本次非公開發行A股股票的申請獲得審核通過。

根據調整後的定增預案,上汽集團擬以不低於15.56元/股非公開發行不超過9.64億股,募集資金不超過150億元用於投資新能源汽車、智慧化大規模定制、前瞻技術與車聯網、汽車服務與汽車金融領域內多個項目,其中公司控股股東上汽總公司擬認購不超過30億元,公司員工持股計畫擬認購不超過11.6745億元。

定增募集資金主要投向新興領域:其中新能源相關專案72億、智慧定制化專案20億、前瞻技術(燃料電池/智慧汽車)及車聯網19億、汽車服務與金融專案39億。

戰略定位由汽車製造轉為綜合服務:研發製造業環節,資源更多的向新能源汽車、智慧汽車傾斜;同時更加注重汽車服務領域的潛力挖掘,包括汽車金融、維修服務等。

公司為業務轉型已做大量的準備和調整:1、組織架構調整,新成立服務貿易事業部、金融事業部,用於整合內部相關業務;2、廣泛儲備技術,公司先後投入60億用於新能源產品開發,是國內少有的推出插電式車型的車企;3、積極跨界合作,公司已與阿裡巴巴、中國石油等企業在車聯網、汽車後市場方面建立戰略合作;4、激勵機制調整,本次增發實現更大額度的持股,同時在部分新業務上還實現了更大幅度的員工持股。

公司新業務發展目標:1、新能源汽車領域,完善供應鏈體系,有效降低成本,加快推出產品,2016、2020年分別實現2.6、20萬銷量;2、智慧汽車領域,2016年下半年推出與阿裡巴巴合作開發的首款互聯網汽車;3、服務貿易領域,以車享網/車享家為服務平臺,建立涵蓋(自建1500家)1萬家店的服務體系,至2020年,實現收入3000億(含物流/維修/金融)。

本站聲明:網站內容來源於EnergyTrend https://www.energytrend.com.tw/ev/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

※為什麼 USB CONNECTOR 是電子產業重要的元件?

網頁設計一頭霧水??該從何著手呢? 找到專業技術的網頁設計公司,幫您輕鬆架站!

※想要讓你的商品成為最夯、最多人討論的話題?網頁設計公司讓你強力曝光

※想知道最厲害的台北網頁設計公司推薦台中網頁設計公司推薦專業設計師”嚨底家”!!

※專營大陸快遞台灣服務

台灣快遞大陸的貨運公司有哪些呢?