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

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

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

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

 

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

搞定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框架開發的項目代碼簡潔,易懂,但早期 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數據庫

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維修中心

日本岩手、宮城水產品可望出口 歐盟:最快2019年內解禁

摘錄自2019年10月05日東森新聞日本報導

日本2011年4月發生9.0地震,福島第一核電站因此放射性物質外洩,成為嚴重的核災事故,讓各國紛紛對日本核災地區設下禁令,不願讓汙染物入國。如今,日本政府表示,歐盟在今年內將會放寬對日本食品的進口限制。

綜合日媒報導,日本執政黨相關人士5日指出,歐盟委員會主席容克(Jean-Claude Juncker)於之前的布魯塞爾會談時向日本首相安倍晉三透露,歐盟很快就會放寬對日本食品的進口限制,尤其是取消岩手、宮城縣的水產品進口禁令。

事實上,歐盟早在2017年就取消對福島縣大米的禁令,不過日本仍然致力於說服其他國家,包括歐盟、中國、韓國及美國等,希望解除福島食品的禁令,也強調日本出口食品時都會通過嚴格檢驗,出口品絕對安全。

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

【其他文章推薦】

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

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

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

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

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

※回頭車貨運收費標準

厄瓜多油價漲惹民怨 示威者衝進國民議會爆衝突

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

抗議厄瓜多總統莫雷諾(Lenin Moreno)政府取消燃料補助導致油價飆漲的示威活動越演越烈,警方和示威者8日在國民議會爆發衝突。

根據Ecuavisa頻道報導,示威者成功突破國會大廈封鎖線,當中有許多是手持棍棒和鞭子的原住民男子。示威者衝進會議室霸占講台,但幾分鐘後就遭到安全部隊驅離。

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

【其他文章推薦】

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

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

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

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

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

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

巴西東北海岸油污擴散 專家憂影響生物

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

自今年9月以來,大片黑色和黏稠的原油油污持續被海水拖動到巴西東北海岸沙灘,目前已在阿拉戈斯(Alagoas)、巴伊亞(Bahia)、塞阿拉(Ceara)、馬蘭豪(Maranhao)、巴萊巴(Paraiba)、培南布可(Pernambuco)、彼奧(Piaui)、北大河(Rio Grande do Norte)和謝吉貝(Sergipe)9州共61個城市超過132個地點發現油污紀錄,至少8隻海洋動物遭油污覆蓋全身窒息死亡。

巴西環保署(Ibama)稱,已經與受影響各州的消防隊合作,採取一系列措施紓緩油污問題,同時與海軍和巴西石油公司(Petrobras)的技術團隊一起試圖查明海上原油油污的來源。

謝吉貝州政府已宣布進入緊急狀態,並要求居民遠離沙灘,即使民眾有意幫忙淨灘活動。生物」。

沒有人知道這些油污來自何處,但專家擔心將對當地生物造成毀滅性的影響。巴西海龜保護計劃(Tamar)也因這個問題而暫停把新孵化出的小海龜放生到大海中。生物學家擔心污染會擴散得更嚴重,危及海龜、海豚和座頭鯨等海洋動物的繁殖。

阿拉戈斯州生物保護研究所長奧里維拉(Bruno Stefanis Santos Pereira de Oliveira)說,當務之急是防止油污擴散和造成更多損害,防止動物和人類接觸油污,避免其他人誤食體積較小的魚貝類而受到污染。

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

【其他文章推薦】

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

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

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

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

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

諾貝爾物理獎揭曉 三得主讓宇宙、天文學徹底改觀

摘錄自2019年10月8日聯合報報導

諾貝爾物理獎8日揭曉,總獎金900萬瑞典克朗(約台幣2800萬元),諾貝爾委員會決定,其中一半頒給加拿大裔美籍宇宙學家皮博(James Peebles),表彰他在物理宇宙學的理論發現,另一半頒給瑞士科學家梅爾(Michel Mayor)和奎洛茲(Didier Queloz),表彰他們發現太陽系之外的行星「系外行星」,是人類首次發現系外行星。

皮博現年84歲,目前是美國普林斯頓大學名譽教授,被譽為最有影響力的宇宙學家之一。諾貝爾委員會說,皮博關於宇宙及其數以十億計星系與星系團的理論架構,是「從大爆炸至今,我們了解的宇宙史的基礎」,他的研究使用理論工具和計算過程,詮釋宇宙初期以來的痕跡,這些研究創造了適當條件,促使過50年來的宇宙學「徹底改觀」。

皮博的研究顯示,人類知道的物質如恆星、行星和人類自己,只占宇宙5%,其餘95%都是由「未知的暗物質和暗能量」構成。

梅爾和奎洛茲1995年發現系外行星「飛馬座51b」,與木星大小類似,諾貝爾委員會說,這兩名科學家「帶動天文學的革命」,從那時候開始,科學家在銀河系發現四千多顆系外行星,「如今仍在這個奇異新世界持續探索,發現各色各樣的系外行星,在大小、形態和軌道方面都不同」。

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

聯合利華承諾:2025年前減半塑膠用量

摘錄自2019年10月8日聯合報報導

英荷消費品生產商聯合利華(Unilever)今天表示,2025年前將減半旗下產品使用的新塑膠量,從目前1年使用70萬公噸塑膠,減半到低於35萬公噸。

聯合利華將減少35萬公噸「原生塑膠」用量,其中10萬公噸來自直接減少塑膠包裝,例如製造可重複利用的包裝或可補充包裝、採用替代包裝,或是完全不會用到塑膠的「裸賣」方式。

另25公噸減量目標則會從使用回收塑膠下手。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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