標籤: 新北清潔

  • 氣候變遷衝擊農民生計 菲青年下鄉助行銷轉型

    摘錄自2019年10月2日中央社報導

    菲律賓存在乾季和雨季,但氣候變遷改變降雨季節,衝擊農民生計。為此,25名菲律賓青年創業家組成的「根源聯合會」(Roots Collective)與「和平與公平基金會」(Peace and Equity Foundation)合作,即日起到6日在位於大馬尼拉「波尼法西奧堡環球城」(BGC)的商場展售在地農民、手工藝工作者與社會企業合作開發的織品、包包、鞋子、食品及日用品。

    青年創業家佛蘭查(John Francia)與夥伴創立「編織工藝」(Woven Crafts),行銷販售來自菲律賓東部薩馬省(Samar)巴席鎮(Basey)的織品,並開發出筆電包、手拿包、化妝包等新產品。佛蘭查說,他們正把觸角延伸到民答那峨島和2017年受叛亂襲擊的馬拉韋市(Marawi)等地,希望開發新的工藝素材,幫助更多菲律賓貧困社區提高收入、脫離貧窮。

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

    【其他文章推薦】

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

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※推薦評價好的iphone維修中心

  • 布袋蓮洗刷惡名 成為肯亞再生能源金礦

    摘錄自2019年9月30日聯合報報導

    布袋蓮為原產於南美洲的水生植物,20世紀初首次出現於非洲。自從布袋蓮開始在1900年從南非開普敦開始向外蔓延,並開始堵塞主要水壩和河流後,科學家稱它為「世界最嚴重的水生雜草」。布袋蓮常因阻塞重要水道且難以根除而遭到敵視,但它在非洲肯亞卻有助提供更乾淨的能源,進而保護居民健康。

    英國衛報報導,在肯亞基蘇木郡(Kisumu)維多利亞湖的Winam灣岸邊附近,大量布袋蓮腐爛在湖邊,但那些逐漸腐壞的蠟質葉片卻是可再生能源的金礦。事實證明,布袋蓮不僅繁殖能力出眾,它的葉子還含有高比例的碳和氮。早期研究預測,僅約4公斤的乾燥布袋蓮,就能滿足一個大家庭的日常能源需求。

    肯亞學者證實,布袋蓮能將有機物轉化為甲烷(沼氣的主要成分)、二氧化碳及水。因為沼氣容易燃燒,一般天然氣能做的事情它都能辦到,像是煮飯、加熱或作為動力來源。Dunga村2018年獲贈兩個沼氣池,能為村莊60%人口提供服務,現在肯亞約有50個沼氣池。

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

    【其他文章推薦】

    USB CONNECTOR掌控什麼技術要點? 帶您認識其相關發展及效能

    台北網頁設計公司這麼多該如何選擇?

    ※智慧手機時代的來臨,RWD網頁設計為架站首選

    ※評比南投搬家公司費用收費行情懶人包大公開

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※回頭車貨運收費標準

  • 中國新鑽井平台是否進入南海 越南密切關注

    摘錄自2019年10月3日中央社報導

    中國新鑽井平台「海洋石油982」傳出在海上準備運作,或許會在南海使用。越南政府今天表示,正在查證這個消息,強調各方在南海的任何行動都要遵守1982年聯合國海洋法公約。

    越南外交部今天在河內舉行例行記者會,發言人黎氏秋恆(Le Thi Thu Hang)回答記者提問時表示,越南相關部門密切關注且正在查證中華人民共和國可能將「海洋石油982」鑽井平台駛入南海海域(越稱東海)的消息。

    黎氏秋恆說:「越南認為,(各方)在東海的任何行動都要遵守1982年聯合國海洋法公約,包括遵守沿海國家主權、主權權利和管轄權在內,為維護區域和平與穩定做出切實貢獻。」

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

    【其他文章推薦】

    網頁設計公司推薦不同的風格,搶佔消費者視覺第一線

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    南投搬家公司費用需注意的眉眉角角,別等搬了再說!

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※教你寫出一流的銷售文案?

  • 科學家發現具有三類性別的線蟲 砷耐受性極高

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

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

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

  • 南北韓非軍事區 驚見野豬患有非洲豬瘟

    摘錄自2019年10月4日自由時報報導

    據報導,北韓於5月份出現首起非洲豬瘟病例,韓國則努力地避免疫情擴散過來,甚至在邊界建起圍欄。韓國各處養豬場已確認有13起病例,不過,長4公里、佈滿地雷、守衛嚴密的南北韓非軍事區(DMZ)更發現患有非洲豬瘟的野豬,目前推測韓國的疫情可能是被帶原的野豬給傳染過來的。

    DMZ向來避免擦槍走火,但韓國軍方已被授權殺死穿越DMZ的野豬。

     

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

    【其他文章推薦】

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※別再煩惱如何寫文案,掌握八大原則!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 米塔颱風襲南韓已釀9死 日本高知暴雨淹大水

    摘錄自2019年10月3日自由時報報導

    米塔颱風在週三晚間襲擊南韓南部,南韓南部港口城市釜山發生土石流,南韓行政安全部3日表示,截至下午,全國共有9人喪生,但傷亡人數預計會在攀升,因為還有數人下落不明。

    南韓國防部表示,米塔風災造成1000多棟房屋損壞,超過1500人提前被撤離。

    米塔離開朝鮮半島後,已往日本西部前進,同樣帶來驚人雨量,日本氣象廳警告,儘管米塔颱風可能在4日減弱為溫帶氣旋,但朝著日本北部前進時,強風豪雨可能引發洪水、土石流等災情。

    【動画】高知県内で大雨 道路冠水も

    — 高知新聞 (公式)Kochinews (@Kochi_news)

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

    【其他文章推薦】

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

  • 搞定ReentrantReadWriteLock 幾道小小數學題就夠了

    搞定ReentrantReadWriteLock 幾道小小數學題就夠了

    | 好看請贊,養成習慣

    • 你有一個思想,我有一個思想,我們交換后,一個人就有兩個思想

    • If you can NOT explain it simply, you do NOT understand it well enough

    現陸續將Demo代碼和技術文章整理在一起 Github實踐精選 ,方便大家閱讀查看,本文同樣收錄在此,覺得不錯,還請Star

    前言

    • 文章 Java AQS隊列同步器以及ReentrantLock的應用 介紹了AQS獨佔式獲取同步狀態的實現,並以 ReentrantLock 為例說明其是如何自定義同步器實現互斥鎖的
    • 文章 Java AQS共享式獲取同步狀態及Semaphore的應用分析 介紹 AQS 共享式獲取同步狀態的實現,並說明了 Semaphore 是如何自定義同步器實現簡單限流作用的

    有了以上兩篇文章的鋪墊,來理解本文要介紹的既有獨佔式,又有共享式獲取同步狀態的 ReadWriteLock,就非常輕鬆了

    ReadWriteLock

    ReadWriteLock 直譯過來為【讀寫鎖】。現實中,讀多寫少的業務場景是非常普遍的,比如應用緩存

    一個線程將數據寫入緩存,其他線程可以直接讀取緩存中的數據,提高數據查詢效率

    之前提到的互斥鎖都是排他鎖,也就是說同一時刻只允許一個線程進行訪問,當面對可共享讀的業務場景,互斥鎖顯然是比較低效的一種處理方式。為了提高效率,讀寫鎖模型就誕生了

    效率提升是一方面,但併發編程更重要的是在保證準確性的前提下提高效率

    一個寫線程改變了緩存中的值,其他讀線程一定是可以 “感知” 到的,否則可能導致查詢到的值不準確

    所以關於讀寫鎖模型就了下面這 3 條規定:

    1. 允許多個線程同時讀共享變量
    2. 只允許一個線程寫共享變量
    3. 如果寫線程正在執行寫操作,此時則禁止其他讀線程讀共享變量

    ReadWriteLock 是一個接口,其內部只有兩個方法:

    public interface ReadWriteLock {
        // 返回用於讀的鎖
        Lock readLock();
    
        // 返回用於寫的鎖
        Lock writeLock();
    }
    

    所以要了解整個讀/寫鎖的整個應用過程,需要從它的實現類 ReentrantReadWriteLock 說起

    ReentrantReadWriteLock 類結構

    直接對比ReentrantReadWriteLock 與 ReentrantLock的類結構

    他們又很相似吧,根據類名稱以及類結構,按照咱們前序文章的分析,你也就能看出 ReentrantReadWriteLock 的基本特性:

    其中黃顏色標記的的 鎖降級 是看不出來的, 這裏先有個印象,下面會單獨說明

    另外,不知道你是否還記得,Java AQS隊列同步器以及ReentrantLock的應用 說過,Lock 和 AQS 同步器是一種組合形式的存在,既然這裡是讀/寫兩種鎖,他們的組合模式也就分成了兩種:

    1. 讀鎖與自定義同步器的聚合
    2. 寫鎖與自定義同步器的聚合
        public ReentrantReadWriteLock(boolean fair) {
            sync = fair ? new FairSync() : new NonfairSync();
            readerLock = new ReadLock(this);
            writerLock = new WriteLock(this);
        }
    

    這裏只是提醒大家,模式沒有變,不要被讀/寫兩種鎖迷惑

    基本示例

    說了這麼多,如果你忘了前序知識,整體理解感覺應該是有斷檔的,所以先來看個示例(模擬使用緩存)讓大家對 ReentrantReadWriteLock 有個直觀的使用印象

    public class ReentrantReadWriteLockCache {
    
    	// 定義一個非線程安全的 HashMap 用於緩存對象
    	static Map<String, Object> map = new HashMap<String, Object>();
    	// 創建讀寫鎖對象
    	static ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
    	// 構建讀鎖
    	static Lock rl = readWriteLock.readLock();
    	// 構建寫鎖
    	static Lock wl = readWriteLock.writeLock();
    
    	public static final Object get(String key) {
    		rl.lock();
    		try{
    			return map.get(key);
    		}finally {
    			rl.unlock();
    		}
    	}
    
    	public static final Object put(String key, Object value){
    		wl.lock();
    		try{
    			return map.put(key, value);
    		}finally {
    			wl.unlock();
    		}
    	}
    }
    

    你瞧,使用就是這麼簡單。但是你知道的,AQS 的核心是鎖的實現,即控制同步狀態 state 的值,ReentrantReadWriteLock 也是應用AQS的 state 來控制同步狀態的,那麼問題來了:

    一個 int 類型的 state 怎麼既控制讀的同步狀態,又可以控制寫的同步狀態呢?

    顯然需要一點設計了

    讀寫狀態設計

    如果要在一個 int 類型變量上維護多個狀態,那肯定就需要拆分了。我們知道 int 類型數據佔32位,所以我們就有機會按位切割使用state了。我們將其切割成兩部分:

    1. 高16位表示讀
    2. 低16位表示寫

    所以,要想準確的計算讀/寫各自的狀態值,肯定就要應用位運算了,下面代碼是 JDK1.8,ReentrantReadWriteLock 自定義同步器 Sync 的位操作

    abstract static class Sync extends AbstractQueuedSynchronizer {
           
    
            static final int SHARED_SHIFT   = 16;
            static final int SHARED_UNIT    = (1 << SHARED_SHIFT);
            static final int MAX_COUNT      = (1 << SHARED_SHIFT) - 1;
            static final int EXCLUSIVE_MASK = (1 << SHARED_SHIFT) - 1;
    
    
            static int sharedCount(int c) { 
              return c >>> SHARED_SHIFT; 
            }
    
            static int exclusiveCount(int c) { 
              return c & EXCLUSIVE_MASK; 
            }
    }
    

    乍一看真是有些複雜的可怕,別慌,咱們通過幾道小小數學題就可以搞定整個位運算過程

    整個 ReentrantReadWriteLock 中 讀/寫狀態的計算就是反覆應用這幾道數學題,所以,在閱讀下面內容之前,希望你搞懂這簡單的運算

    基礎鋪墊足夠了,我們進入源碼分析吧

    源碼分析

    寫鎖分析

    由於寫鎖是排他的,所以肯定是要重寫 AQS 中 tryAcquire 方法

            protected final boolean tryAcquire(int acquires) {        
                Thread current = Thread.currentThread();
              	// 獲取 state 整體的值
                int c = getState();
                // 獲取寫狀態的值
                int w = exclusiveCount(c);
                if (c != 0) {
                    // w=0: 根據推理二,整體狀態不等於零,寫狀態等於零,所以,讀狀態大於0,即存在讀鎖
                  	// 或者當前線程不是已獲取寫鎖的線程
                  	// 二者之一條件成真,則獲取寫狀態失敗
                    if (w == 0 || current != getExclusiveOwnerThread())
                        return false;
                    if (w + exclusiveCount(acquires) > MAX_COUNT)
                        throw new Error("Maximum lock count exceeded");
                    // 根據推理一第 1 條,更新寫狀態值
                    setState(c + acquires);
                    return true;
                }
                if (writerShouldBlock() ||
                    !compareAndSetState(c, c + acquires))
                    return false;
                setExclusiveOwnerThread(current);
                return true;
            }
    

    上述代碼 第 19 行 writerShouldBlock 也並沒有什麼神秘的,只不過是公平/非公平獲取鎖方式的判斷(是否有前驅節點來判斷)

    你瞧,寫鎖獲取方式就是這麼簡單

    讀鎖分析

    由於讀鎖是共享式的,所以肯定是要重寫 AQS 中 tryAcquireShared 方法

            protected final int tryAcquireShared(int unused) {
                Thread current = Thread.currentThread();
                int c = getState();
              	// 寫狀態不等於0,並且鎖的持有者不是當前線程,根據約定 3,則獲取讀鎖失敗
                if (exclusiveCount(c) != 0 &&
                    getExclusiveOwnerThread() != current)
                    return -1;
              	// 獲取讀狀態值
                int r = sharedCount(c);
              	// 這個地方有點不一樣,我們單獨說明
                if (!readerShouldBlock() &&
                    r < MAX_COUNT &&
                    compareAndSetState(c, c + SHARED_UNIT)) {
                    if (r == 0) {
                        firstReader = current;
                        firstReaderHoldCount = 1;
                    } else if (firstReader == current) {
                        firstReaderHoldCount++;
                    } else {
                        HoldCounter rh = cachedHoldCounter;
                        if (rh == null || rh.tid != getThreadId(current))
                            cachedHoldCounter = rh = readHolds.get();
                        else if (rh.count == 0)
                            readHolds.set(rh);
                        rh.count++;
                    }
                    return 1;
                }
              	// 如果獲取讀鎖失敗則進入自旋獲取
                return fullTryAcquireShared(current);
            }
    

    readerShouldBlockwriterShouldBlock 在公平鎖的實現上都是判斷是否有前驅節點,但是在非公平鎖的實現上,前者是這樣的:

    final boolean readerShouldBlock() {
    	return apparentlyFirstQueuedIsExclusive();
    }
    
    final boolean apparentlyFirstQueuedIsExclusive() {
      Node h, s;
      return (h = head) != null &&
        // 等待隊列頭節點的下一個節點
        (s = h.next)  != null &&
        // 如果是排他式的節點
        !s.isShared()         &&
        s.thread != null;
    }
    

    簡單來說,如果請求讀鎖的當前線程發現同步隊列的 head 節點的下一個節點為排他式節點,那麼就說明有一個線程在等待獲取寫鎖(爭搶寫鎖失敗,被放入到同步隊列中),那麼請求讀鎖的線程就要阻塞,畢竟讀多寫少,如果還沒有這點判斷機制,寫鎖可能會發生【飢餓】

    上述條件都滿足了,也就會進入 tryAcquireShared 代碼的第 14 行到第 25 行,這段代碼主要是為了記錄線程持有鎖的次數。讀鎖是共享式的,還想記錄每個線程持有讀鎖的次數,就要用到 ThreadLocal 了,因為這不影響同步狀態 state 的值,所以就不分析了, 只把關係放在這吧

    到這裏讀鎖的獲取也就結束了,比寫鎖稍稍複雜那麼一丟丟,接下來就說明一下那個可能讓你迷惑的鎖升級/降級問題吧

    讀寫鎖的升級與降級

    個人理解:讀鎖是可以被多線程共享的,寫鎖是單線程獨佔的,也就是說寫鎖的併發限制比讀鎖高,所以

    在真正了解讀寫鎖的升級與降級之前,我們需要完善一下本文開頭 ReentrantReadWriteLock 的例子

    	public static final Object get(String key) {
    		Object obj = null;
    		rl.lock();
    		try{
          // 獲取緩存中的值
    			obj = map.get(key);
    		}finally {
    			rl.unlock();
    		}
    		// 緩存中值不為空,直接返回
    		if (obj!= null) {
    			return obj;
    		}
    		
        // 緩存中值為空,則通過寫鎖查詢DB,並將其寫入到緩存中
    		wl.lock();
    		try{
          // 再次嘗試獲取緩存中的值
    			obj = map.get(key);
          // 再次獲取緩存中值還是為空
    			if (obj == null) {
            // 查詢DB
    				obj = getDataFromDB(key); // 偽代碼:getDataFromDB
            // 將其放入到緩存中
    				map.put(key, obj);
    			}
    		}finally {
    			wl.unlock();
    		}
    		return obj;
    	}
    

    有童鞋可能會有疑問

    在寫鎖裏面,為什麼代碼第19行還要再次獲取緩存中的值呢?不是多此一舉嗎?

    其實這裏再次嘗試獲取緩存中的值是很有必要的,因為可能存在多個線程同時執行 get 方法,並且參數 key 也是相同的,執行到代碼第 16 行 wl.lock() ,比如這樣:

    線程 A,B,C 同時執行到臨界區 wl.lock(), 只有線程 A 獲取寫鎖成功,線程B,C只能阻塞,直到線程A 釋放寫鎖。這時,當線程B 或者 C 再次進入臨界區時,線程 A 已經將值更新到緩存中了,所以線程B,C沒必要再查詢一次DB,而是再次嘗試查詢緩存中的值

    既然再次獲取緩存很有必要,我能否在讀鎖里直接判斷,如果緩存中沒有值,那就再次獲取寫鎖來查詢DB不就可以了嘛,就像這樣:

    	public static final Object getLockUpgrade(String key) {
    		Object obj = null;
    		rl.lock();
    		try{
    			obj = map.get(key);
    			if (obj == null){
    				wl.lock();
    				try{
    					obj = map.get(key);
    					if (obj == null) {
    						obj = getDataFromDB(key); // 偽代碼:getDataFromDB
    						map.put(key, obj);
    					}
    				}finally {
    					wl.unlock();
    				}
    			}
    		}finally {
    			rl.unlock();
    		}
    
    		return obj;
    	}
    

    這還真是不可以的,因為獲取一個寫入鎖需要先釋放所有的讀取鎖,如果有兩個讀取鎖試圖獲取寫入鎖,且都不釋放讀取鎖時,就會發生死鎖,所以在這裏,鎖的升級是不被允許的

    讀寫鎖的升級是不可以的,那麼鎖的降級是可以的嘛?這個是 Oracle 官網關於鎖降級的示例 ,我將代碼粘貼在此處,大家有興趣可以點進去連接看更多內容

     class CachedData {
       Object data;
       volatile boolean cacheValid;
       final ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    
       void processCachedData() {
         rwl.readLock().lock();
         if (!cacheValid) {
            // 必須在獲取寫鎖之前釋放讀鎖,因為鎖的升級是不被允許的
            rwl.readLock().unlock();
            rwl.writeLock().lock();
            try {
              // 再次檢查,原因可能是其他線程已經更新過緩存
              if (!cacheValid) {
                data = ...
                cacheValid = true;
              }
    					//在釋放寫鎖前,降級為讀鎖
              rwl.readLock().lock();
            } finally {
              //釋放寫鎖,此時持有讀鎖
              rwl.writeLock().unlock(); 
            }
         }
    
         try {
           use(data);
         } finally {
           rwl.readLock().unlock();
         }
       }
     }
    

    代碼中聲明了一個 volatile 類型的 cacheValid 變量,保證其可見性。

    1. 首先獲取讀鎖,如果cache不可用,則釋放讀鎖
    2. 然後獲取寫鎖
    3. 在更改數據之前,再檢查一次cacheValid的值,然後修改數據,將cacheValid置為true
    4. 然後在釋放寫鎖前獲取讀鎖 此時
    5. cache中數據可用,處理cache中數據,最後釋放讀鎖

    這個過程就是一個完整的鎖降級的過程,目的是保證數據可見性,聽起來很有道理的樣子,那麼問題來了:

    上述代碼為什麼在釋放寫鎖之前要獲取讀鎖呢?

    如果當前的線程A在修改完cache中的數據后,沒有獲取讀鎖而是直接釋放了寫鎖;假設此時另一個線程B 獲取了寫鎖並修改了數據,那麼線程A無法感知到數據已被修改,但線程A還應用了緩存數據,所以就可能出現數據錯誤

    如果遵循鎖降級的步驟,線程A 在釋放寫鎖之前獲取讀鎖,那麼線程B在獲取寫鎖時將被阻塞,直到線程A完成數據處理過程,釋放讀鎖,從而保證數據的可見性

    那問題又來了:

    使用寫鎖一定要降級嗎?

    如果你理解了上面的問題,相信這個問題已經有了答案。假如線程A修改完數據之後, 經過耗時操作后想要再使用數據時,希望使用的是自己修改后的數據,而不是其他線程修改后的數據,這樣的話確實是需要鎖降級;如果只是希望最後使用數據的時候,拿到的是最新的數據,而不一定是自己剛修改過的數據,那麼先釋放寫鎖,再獲取讀鎖,然後使用數據也無妨

    在這裏我要額外說明一下你可能存在的誤解:

    • 如果已經釋放了讀鎖再獲取寫鎖不叫鎖的升級

    • 如果已經釋放了寫鎖在獲取讀鎖也不叫鎖的降級

    相信你到這裏也理解了鎖的升級與降級過程,以及他們被允許或被禁止的原因了

    總結

    本文主要說明了 ReentrantReadWriteLock 是如何應用 state 做位拆分實現讀/寫兩種同步狀態的,另外也通過源碼分析了讀/寫鎖獲取同步狀態的過程,最後又了解了讀寫鎖的升級/降級機制,相信到這裏你對讀寫鎖已經有了一定的理解。如果你對文中的哪些地方覺得理解有些困難,強烈建議你回看本文開頭的兩篇文章,那裡鋪墊了非常多的內容。接下來我們就看看在應用AQS的最後一個併發工具類 CountDownLatch 吧

    靈魂追問

    1. 讀鎖也沒修改數據,還允許共享式獲取,那還有必要設置讀鎖嗎?
    2. 在分佈式環境中,你是如何保證緩存數據一致性的呢?
    3. 當你打開看ReentrantReadWriteLock源碼時,你會發現,WriteLock 中可以使用 Condition,但是ReadLock 使用Condition卻會拋出UnsupportedOperationException,這是為什麼呢?
    // WriteLock
    public Condition newCondition() {
    	return sync.newCondition();
    }
    
    // ReadLock
    public Condition newCondition() {
    	throw new UnsupportedOperationException();
    }
    

    個人博客:https://dayarch.top
    加我微信好友, 進群娛樂學習交流,備註「進群」

    歡迎持續關注公眾號:「日拱一兵」

    • 前沿 Java 技術乾貨分享
    • 高效工具匯總 | 回復「工具」
    • 面試問題分析與解答
    • 技術資料領取 | 回復「資料」

    以讀偵探小說思維輕鬆趣味學習 Java 技術棧相關知識,本着將複雜問題簡單化,抽象問題具體化和圖形化原則逐步分解技術問題,技術持續更新,請持續關注……

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

    【其他文章推薦】

    ※自行創業缺乏曝光? 網頁設計幫您第一時間規劃公司的形象門面

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※想知道最厲害的網頁設計公司"嚨底家"!

    ※幫你省時又省力,新北清潔一流服務好口碑

    ※別再煩惱如何寫文案,掌握八大原則!

  • React實戰教程之從零開始手把手教你使用 React 最新特性Hooks API 打造一款計算機知識測驗App

    React實戰教程之從零開始手把手教你使用 React 最新特性Hooks API 打造一款計算機知識測驗App

    項目演示地址

    項目演示地址

    項目代碼結構

    前言

    React 框架的優雅不言而喻,組件化的編程思想使得React框架開發的項目代碼簡潔,易懂,但早期 React 類組件的寫法略顯繁瑣。React Hooks 是 React 16.8 發布以來最吸引人的特性之一,她簡化了原有代碼的編寫,是未來 React 應用的主流寫法。

    本文通過一個實戰小項目,手把手從零開始帶領大家快速入門React Hooks。

    在本項目中,會用到以下知識點:

    • React 組件化設計思想
    • React State 和 Props
    • React 函數式組件的使用
    • React Hooks useState 的使用
    • React Hooks useEffect 的使用
    • React 使用 Axios 請求遠程接口獲取問題及答案
    • React 使用Bootstrap美化界面

    Hello React

    (1)安裝node.js 官網鏈接

    (2)安裝vscode 官網鏈接

    (3)安裝 creat-react-app 功能組件,該組件可以用來初始化一個項目, 即按照一定的目錄結構,生成一個新項目。
    打開cmd 窗口 輸入:

    npm install --g create-react-app 
    npm install --g yarn
    

    (-g 代表全局安裝)

    如果安裝失敗或較慢。需要換源,可以使用淘寶NPM鏡像,設置方法為:

    npm config set registry https://registry.npm.taobao.org
    

    設置完成后,重新執行

    npm install --g create-react-app
    npm install --g yarn
    

    (4)在你想創建項目的目錄下 例如 D:/project/ 打開cmd命令 輸入

    create-react-app react-exam
    

    去使用creat-react-app命令創建名字是react-exam的項目

    安裝完成后,移至新創建的目錄並啟動項目

    cd react-exam
    yarn start
    

    一旦運行此命令,localhost:3000新的React應用程序將彈出一個新窗口。

    項目目錄結構

    右鍵react-exam目錄,使用vscode打開該目錄。
    react-exam項目目錄中有一個/public和/src目錄,以及node_modules,.gitignore,README.md,和package.json。

    在目錄/public中,重要文件是index.html,其中一行代碼最重要

    <div id="root"></div>
    

    該div做為我們整個應用的掛載點

    /src目錄將包含我們所有的React代碼。

    要查看環境如何自動編譯和更新您的React代碼,請找到文件/src/App.js
    將其中的

            <a
              className="App-link"
              href="https://reactjs.org"
              target="_blank"
              rel="noopener noreferrer"
            >
              Learn React
            </a>
    

    修改為

            <a
              className="App-link"
              href="https://reactjs.org"
              target="_blank"
              rel="noopener noreferrer"
            >
              和豆約翰 Learn React
            </a>
    

    保存文件后,您會注意到localhost:3000編譯並刷新了新數據。

    React-Exam項目實戰

    1. 首頁製作

    1.安裝項目依賴,在package.json中添加:

      "dependencies": {
        "@testing-library/jest-dom": "^4.2.4",
        "@testing-library/react": "^9.3.2",
        "@testing-library/user-event": "^7.1.2",
        "react": "^16.13.1",
        "react-dom": "^16.13.1",
        "react-scripts": "3.4.1",
        "axios": "^0.19.2",
        "bootstrap": "^4.5.0",
        "he": "^1.2.0",
        "react-loading": "^2.0.3",
        "reactstrap": "^8.4.1"
      },
    

    執行命令:

    yarn install
    

    修改index.js,導入bootstrap樣式

    import "bootstrap/dist/css/bootstrap.min.css";
    

    修改App.css代碼

    html {
      width: 80%;
      margin-left: 10%;
      margin-top: 2%;
    }
    
    .ansButton {
      margin-right: 4%;
      margin-top: 4%;
    }
    

    修改App.js,引入Quiz組件

    import React from 'react';
    import './App.css'
    import { Quiz } from './Exam/Quiz';
    
    function App() {
      return (
        <div className = 'layout'>
        <Quiz></Quiz>
        </div>
      );
    }
    
    export default App;
    
    

    在項目src目錄下新增Exam目錄,Exam目錄中新建Quiz.js

    Quiz組件的定義如下:
    Quiz.js,引入開始頁面組件Toggle。

    import React, { useState } from "react";
    import { Toggle } from "./Toggle";
    export const Quiz = () => {
      const [questionData, setQuestionData] = useState([]);
      const questions = questionData.map(({ question }) => [question]);
      const answers = questionData.map(({ incorrect_answers, correct_answer }) =>
        [correct_answer, incorrect_answers].flat()
      );
      return (
        <>
          <Toggle
            setQuestionData={setQuestionData}
          />
        </>
      );
    };
    

    Toggle.js,點擊開始按鈕,通過axios訪問遠程接口,獲得題目及答案。

    import React from "react";
    import axios from "axios";
    import ToggleHeader from "./ToggleHeader";
    import {
      Button,
      Form,
    } from "reactstrap";
    
    export const Toggle = ({
      setQuestionData,
    }) => {
      const getData = async () => {
        try {
          const incomingData = await axios.get(
            `https://opentdb.com/api.php?amount=10&category=18&difficulty=easy&type=multiple`
          );
          setQuestionData(incomingData.data.results);
        } catch (err) {
          console.error(err);
        }
      };
    
      return (
        <>
          <ToggleHeader />
          <Form
            onSubmit={(e) => {
              e.preventDefault();
              getData();
            }}
          >
            <Button color="primary">開始</Button>
          </Form>
        </>
      );
    };
    
    

    ToggleHeader.js

    import React from "react";
    import { Jumbotron, Container} from "reactstrap";
    
    export default function ToggleHeader() {
      return (
        <Jumbotron fluid>
          <Container fluid>
            <h1 className="display-4">計算機知識小測驗</h1>
          </Container>
        </Jumbotron>
      );
    }
    

    https://opentdb.com/api.php接口返回的json數據格式為

    {
    	"response_code": 0,
    	"results": [{
    		"category": "Science: Computers",
    		"type": "multiple",
    		"difficulty": "easy",
    		"question": "The numbering system with a radix of 16 is more commonly referred to as ",
    		"correct_answer": "Hexidecimal",
    		"incorrect_answers": ["Binary", "Duodecimal", "Octal"]
    	}, {
    		"category": "Science: Computers",
    		"type": "multiple",
    		"difficulty": "easy",
    		"question": "This mobile OS held the largest market share in 2012.",
    		"correct_answer": "iOS",
    		"incorrect_answers": ["Android", "BlackBerry", "Symbian"]
    	}, {
    		"category": "Science: Computers",
    		"type": "multiple",
    		"difficulty": "easy",
    		"question": "How many values can a single byte represent?",
    		"correct_answer": "256",
    		"incorrect_answers": ["8", "1", "1024"]
    	}, {
    		"category": "Science: Computers",
    		"type": "multiple",
    		"difficulty": "easy",
    		"question": "In computing, what does MIDI stand for?",
    		"correct_answer": "Musical Instrument Digital Interface",
    		"incorrect_answers": ["Musical Interface of Digital Instruments", "Modular Interface of Digital Instruments", "Musical Instrument Data Interface"]
    	}, {
    		"category": "Science: Computers",
    		"type": "multiple",
    		"difficulty": "easy",
    		"question": "In computing, what does LAN stand for?",
    		"correct_answer": "Local Area Network",
    		"incorrect_answers": ["Long Antenna Node", "Light Access Node", "Land Address Navigation"]
    	}]
    }
    

    程序運行效果:

    當前項目目錄結構為:

    2. 問題展示頁面

    Quiz.js,新增toggleView變量用來切換視圖。

      const [toggleView, setToggleView] = useState(true);
    

    Quiz.js,其中Question和QuestionHeader 組件,參見後面。

    import { Question } from "./Question";
    import { Jumbotron } from "reactstrap";
    import QuestionHeader from "./QuestionHeader";
    
    ...
    export const Quiz = () => {
      var [index, setIndex] = useState(0);
      const [questionData, setQuestionData] = useState([]);
    ...
     return (
        <>
          {toggleView && (
            <Toggle
              setIndex={setIndex}
              setQuestionData={setQuestionData}
              setToggleView={setToggleView}
            />
          )}
           {!toggleView &&
            (
              <Jumbotron>
                <QuestionHeader
                  setToggleView={setToggleView}
                />
                <Question question={questions[index]} />
              </Jumbotron>
            )}
        </>
      );
    

    使用index控制題目索引

    var [index, setIndex] = useState(0);
    

    修改Toggle.js
    獲取完遠程數據,通過setToggleView(false);切換視圖。

    export const Toggle = ({
      setQuestionData,
      setToggleView,
      setIndex,
    }) => {
      
    ...
    
      return (
        <>
          <ToggleHeader />
          <Form
            onSubmit={(e) => {
              e.preventDefault();
              getData();
              setToggleView(false);
              setIndex(0);
            }}
          >
            <Button color="primary">開始</Button>
          </Form>
        </>
      );
    };
    

    QuestionHeader.js代碼:
    同樣的,點擊 返回首頁按鈕 setToggleView(true),切換視圖。

    import React from "react";
    import { Button } from "reactstrap";
    export default function QuestionHeader({ setToggleView, category }) {
      return (
        <>
          <Button color="link" onClick={() => setToggleView(true)}>
            返回首頁
          </Button>
        </>
      );
    }
    
    

    Question.js代碼
    接受父組件傳過來的question對象,並显示。
    其中he.decode是對字符串中的特殊字符進行轉義。

    import React from "react";
    import he from "he";
    export const Question = ({ question }) => {
      // he is a oddly named library that decodes html into string values
    
      var decode = he.decode(String(question));
    
      return (
        <div>
          <hr className="my-2" />
          <h1 className="display-5">
            {decode}
          </h1>
          <hr className="my-2" />
          <br />
        </div>
      );
    };
    
    

    程序運行效果:
    首頁

    點擊開始后,显示問題:

    當前項目目錄結構為:

    3. 加載等待動畫

    新增LoadingSpin.js

    import React from "react";
    import { Spinner } from "reactstrap";
    export default function LoadingSpin() {
      return (
        <>
          <Spinner type="grow" color="primary" />
          <Spinner type="grow" color="secondary" />
          <Spinner type="grow" color="success" />
          <Spinner type="grow" color="danger" />
        </>
      );
    }
    
    

    修改Quiz.js

    
    import LoadingSpin from "./LoadingSpin";
    
    export const Quiz = () => {
    
      const [isLoading, setLoading] = useState(false);
    
    
      return (
        <>
          {toggleView && (
            <Toggle
              ...
              setLoading={setLoading}
            />
          )}
          {!toggleView &&
            (isLoading ? (
              <LoadingSpin />
            ) : 
            (
              ...
            ))}
        </>
      );
    };
    
    

    修改Toggle.js

    
    
    export const Toggle = ({
    ...
      setLoading,
    }) => {
      const getData = async () => {
        try {
          setLoading(true);
          const incomingData = await axios.get(
            `https://opentdb.com/api.php?amount=10&category=18&difficulty=easy&type=multiple`
          );
          setQuestionData(incomingData.data.results);
          setLoading(false);
        } catch (err) {
          console.error(err);
        }
      };
    
     ...
    };
    
    

    運行效果:

    目前代碼結構:

    4. 實現下一題功能

    新增Answer.js,用戶點擊下一題按鈕,修改index,觸發主界面刷新,显示下一題:

    import React from "react";
    import { Button } from "reactstrap";
    
    export const Answer = ({ setIndex, index }) => {
      function answerResult() {
        setIndex(index + 1);
      }
    
      return (
        <Button className="ansButton" onClick={answerResult}>
          下一題
        </Button>
      );
    };
    
    

    修改Quiz.js,添加Answer組件:

    import { Answer } from "./Answer";
    ...
     {!toggleView &&
            (isLoading ? (
              <LoadingSpin />
            ) : 
            (
              <Jumbotron>
                ...
                <Answer
                setIndex={setIndex}
                index={index}
                />
              </Jumbotron>
    
            ))}
    

    運行效果:

    點擊下一題:

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

    【其他文章推薦】

    ※廣告預算用在刀口上,台北網頁設計公司幫您達到更多曝光效益

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※別再煩惱如何寫文案,掌握八大原則!

    ※教你寫出一流的銷售文案?

    ※超省錢租車方案

  • 使用反應式關係數據庫連接規範R2DBC操作MySQL數據庫

    使用反應式關係數據庫連接規範R2DBC操作MySQL數據庫

    1. 簡介

    三月份已經介紹過R2DBC,它是一種異步的、非阻塞的關係式數據庫連接規範。儘管一些NoSQL數據庫供應商為其數據庫提供了反應式數據庫客戶端,但對於大多數項目而言,遷移到NoSQL並不是一個理想的選擇。這促使了一個通用的響應式關係數據庫連接規範的誕生。 作為擁有龐大用戶群的關係式數據庫MySQL也有了反應式驅動,不過並不是官方的。但是Spring官方將其納入了依賴池,說明該類庫的質量並不低。所以今天就嘗嘗鮮,試一下使用R2DBC連接MySQL

    2. 環境依賴

    基於Spring Boot 2.3.1Spring Data R2DBC,還有反應式Web框架Webflux,同時也要依賴r2dbc-mysql庫,所有的Maven依賴為:

           <!--r2dbc mysql 庫-->
            <dependency>
                <groupId>dev.miku</groupId>
                <artifactId>r2dbc-mysql</artifactId>
            </dependency>
            <!--Spring r2dbc 抽象層-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-r2dbc</artifactId>
            </dependency>
            <!--自動配置需要引入的一個嵌入式數據庫類型對象-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-data-jdbc</artifactId>
            </dependency>
           <!--反應式web框架-->
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-webflux</artifactId>
            </dependency>
    

    MySQL版本為5.7,沒有測試其它版本。

    3. R2DBC配置

    所有的R2DBC自動配置都在org.springframework.boot.autoconfigure.data.r2dbc包下,如果要配置MySQL必須針對性的配置對應的連接工廠接口ConnectionFactory,當然也可以通過application.yml配置。個人比較喜歡JavaConfig

    @Bean
    ConnectionFactory connectionFactory() {
        return MySqlConnectionFactory.from(MySqlConnectionConfiguration.builder()
                .host("127.0.0.1")
                .port(3306)
                .username("root")
                .password("123456")
                .database("database_name")
                 // 額外的其它非必選參數省略                          
                .build());
    }
    

    詳細配置可參考r2dbc-mysql的官方說明:https://github.com/mirromutth/r2dbc-mysql

    ConnectionFactory配置好后,就會被注入DatabaseClient 對象。該對象是非阻塞的,用於執行數據庫反應性客戶端調用與反應流背壓請求。我們可以通過該接口反應式地操作數據庫。

    4. 編寫反應式接口

    我們先創建一張表並寫入一些數據:

    create table client_user
    (
        user_id         varchar(64)                              not null comment '用戶唯一標示' primary key,
        username        varchar(64)                              null comment '名稱',
        phone_number    varchar(64)                              null comment '手機號',
        gender          tinyint(1) default 0                     null comment '0 未知 1 男 2 女  '
    )
    

    對應的實體為:

    package cn.felord.r2dbc.config;
    
    import lombok.Data;
    
    /**
     * @author felord.cn
     */
    @Data
    public class ClientUser {
    
        private String userId;
        private String username;
        private String phoneNumber;
        private Integer gender;
    }
    

    然後我們編寫一個Webflux的反應式接口:

    package cn.felord.r2dbc.config;
    
    import org.springframework.data.r2dbc.core.DatabaseClient;
    import org.springframework.web.bind.annotation.GetMapping;
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.web.bind.annotation.RestController;
    import reactor.core.publisher.Flux;
    import reactor.core.publisher.Mono;
    
    import javax.annotation.Resource;
    
    /**
     * The type User controller.
     *
     * @author felord.cn
     * @since 17 :07
     */
    @RestController
    @RequestMapping("/user")
    public class UserController {
        @Resource
        private DatabaseClient databaseClient;
    
        /**
         * 查詢
         *
         * @return 返回Flux序列 包含所有的ClientUser
         */
        @GetMapping("/get")
        public Flux<ClientUser> clientUserFlux() {
            return databaseClient.execute("select * from client_user").as(ClientUser.class)
                    .fetch()
                    .all();
        }
    
        /**
         * 響應式寫入.
         *
         * @return Mono對象包含更新成功的條數
         */
        @GetMapping("/add")
        public Mono<Integer> insert() {
            ClientUser clientUser = new ClientUser();
            clientUser.setUserId("34345514644");
            clientUser.setUsername("felord.cn");
            clientUser.setPhoneNumber("3456121");
            clientUser.setGender(1);
    
            return databaseClient.insert().into(ClientUser.class)
                    .using(clientUser)
                    .fetch().rowsUpdated();
        }
    
    }
    

    調用接口就能獲取到期望的數據結果。

    5. 總結

    乍一看R2DBC並沒有想象中的那麼難,但是間接的需要了解FluxMono等抽象概念。同時目前來說如果不和Webflux框架配合也沒有使用場景。就本文的MySQL而言,R2DBC驅動還是社區維護(不得不說PgSQL就做的很好)。

    然而需要你看清的是反應式才是未來。如果你要抓住未來就需要現在就了解一些相關的知識。這讓我想起五年前剛剛接觸Spring Boot的感覺。另外這裡有一份Spring官方關於R2DBC的PPT,也是讓你更好了解R2DBC的權威資料。可以關注:碼農小胖哥 回復r2dbc獲取。

    關注公眾號:Felordcn 獲取更多資訊

    個人博客:https://felord.cn

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

    【其他文章推薦】

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※別再煩惱如何寫文案,掌握八大原則!

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※超省錢租車方案

    ※教你寫出一流的銷售文案?

  • 萬安灘因探勘石油與中對峙 越南學者籲提交聯合國安理會

    摘錄自2019年10月7日中央社報導

    中國7月起派遣探勘船「海洋地質8號」等船隻進入南沙群島最西側淺灘萬安灘(Vanguard Bank)探勘石油,越南將萬安灘稱為思政灘(Tu Chinh),聲稱依據聯合國海洋法公約,萬安灘位於越南200海里專屬經濟區內,出動相關船隻阻擾,與中方船隻發生對峙,越方並多次向中方抗議。兩國對峙數月,越南法律政策與發展研究所6日舉行座談會,與會學者建議越南將中國近期在萬安灘海域所採取威脅和武力行為提交聯合國安全理事會。

    學者表示,中國近年來企圖將整個南海變成「家裡水池」,對東沙、西沙、中沙和南沙四大群島提出主權聲索,其中主張在與越南有爭議的西沙和南沙兩個群島設立專屬經濟區和大陸棚,目的是將沒有爭議海域變成有爭議海域,從而提出「共同資源開採」的聲索。

    越南法律政策與發展研究所所長黃玉交(HoangNgoc Giao)表示,中國近年來在南海採取的行為,尤其是近期在歸屬越南專屬經濟區的萬安灘海域的既威脅又使用武力的行為,已違反聯合國憲章;國際社會反應也顯示,這是威脅區域和平、安全以及違反國際法基本原則的行為,中國不僅侵犯越南海域,還侵犯馬來西亞和菲律賓海域。越南應趁著擔任聯合國安理會2020年至2021年非常任理事國之際,將中國在南海威脅和武力行為向聯合國安理會提交。

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

    【其他文章推薦】

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

    網頁設計一頭霧水該從何著手呢? 台北網頁設計公司幫您輕鬆架站!

    ※台北網頁設計公司全省服務真心推薦

    ※想知道最厲害的網頁設計公司"嚨底家"!

    新北清潔公司,居家、辦公、裝潢細清專業服務

    ※推薦評價好的iphone維修中心