Jmeter(九) – 從入門到精通 – JMeter邏輯控制器 – 上篇(詳解教程)

1.簡介

Jmeter官網對邏輯控制器的解釋是:“Logic Controllers determine the order in which Samplers are processed.”。

意思是說,邏輯控制器可以控制採樣器(samplers)的執行順序。由此可知,控制器需要和採樣器一起使用,否則控制器就沒有什麼意義了。放在控制器下面的所有的採樣器都會當做一個整體,執行時也會一起被執行。

JMeter邏輯控制器可以對元件的執行邏輯進行控制,除僅一次控制器外,其他可以嵌套別的種類的邏輯控制器。

2.邏輯控制器分類

JMeter中的Logic Controller分為兩類:
(1)控制測試計劃執行過程中節點的邏輯執行順序,如:Loop Controller、If Controller等;
(2)對測試計劃中的腳本進行分組、方便JMeter統計執行結果以及進行腳本的運行時控制等,如:Throughput Controller、Transaction Controller。

3.預覽邏輯控制器家族

首先我們來看一下JMeter的邏輯控制器,路徑:線程組(用戶)->添加->邏輯控制器(Logic Controller);我們可以清楚地看到JMeter5中共有17個邏輯控制器,如下圖所示:

如果上圖您看得不是很清楚的話,宏哥總結了一個思維導圖,關於JMeter5的邏輯控制器類型,如下圖所示: 

 通過以上的了解,我們對邏輯控制器有了一個大致的了解和認識。下面宏哥就給小夥伴或則童鞋們分享講解一些通常在工作中會用到的邏輯控制器。 

4.常用邏輯控制器詳解

  這一小節,宏哥就由上而下地詳細地講解一下常用的邏輯控制器。由於時間關係,宏哥將這部分分為上、中、下三個部分講解。

4.1if Controller

在實際工作中,當使用Jmeter進行接口測試或者性能測試時,有時需要根據不同條件做不同的操作,為了解決這個問題,Jmeter提供了IF控制器。顧名思義,IF控制器實現了代碼中IF的功能,通過判斷表達式的True/False來判定是否執行相應的操作。通過條件判斷下邊的節點執行不執行。

1、我們先來看看這個if Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 如果 (if) 控制器,如下圖所示:

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空。

Expression (must evaluate to true or false) :表達式(值必須是true或false),也就是說,在右邊文本框中輸入的條件值必須是true 或 false,(默認情況下)

Interpret Condition as Variable Expression?:默認勾選項,將條件解釋為變量表達式(需要使用__jexl3 or __groovy 表達式)

Evaluate for all children?:條件作用於每個子項(具體理解宏哥會在後邊實戰篇列舉例子說明),判斷條件是否針對所有子節點,默認不勾選,只在if Controller的入口處判斷一次。

注意:敲黑板!!!敲腦殼啦!!!

1、文本框上的黃色感嘆號,就是提示你,建議採用__jexl3 or __groovy 表達式,以提高性能,也就是默認的方式。

2、if 控制器 只能作用於其下的子項

4.1.1默認用法

1、默認用法,就是採用__jexl3 or __groovy 表達式if 控制器下有一個 訪問北京宏哥的博客園首頁的取樣器,只有if條件滿足時,才會執行該取樣器。採用默認方式,將條件’北京宏哥’==’北京宏哥’,放入 __jexl3表達式中。如下圖所示:

 2、如果不知道表達式如何使用,可使用Jmeter 的函數助手,函數助手圖標 > 選擇_jexl3 > 在值的輸入框輸入’北京宏哥’==’北京宏哥’  > 點擊‘生成’ > 全選Ctrl+C複製 > Ctrl+V粘貼到表達式處 如下圖所示:

3、配置好以後,運行JMeter,選擇HTML,然後查看結果樹,如下圖所示:

4.1.2直接輸入條件

1、直接輸入只需要去掉 “Interpret Condition as Variable Expression?” 前面複選框,直接輸入條件: ‘北京宏哥’==’北京宏哥’ 。訪問北京宏哥的博客園的首頁的取樣器將被執行。如下圖所示:

2、配置好以後,運行JMeter,選擇HTML,然後查看結果樹,如下圖所示:

4.13條件中使用變量

我們在日常工作中在很多的測試場景下,需要根據用戶變量或者上一個取樣器的返回值來進行條件判斷,從而決定是否需要執行某一個的取樣器。

1、首先我們新增一個用戶變量:北京宏哥。條件:北京宏哥 的值為 宏哥 的時候,才執行訪問北京宏哥博客園的首頁的取樣器。如下圖所示:

用戶變量及配置,如下圖所示:

2、IF Controller及配置,或者可以用表達式:${__jexl3(‘${北京宏哥}’==’宏哥’ ,)}。如下圖所示: 

4.1.4Evaluate for all children? 的用法

1、宏哥在上面的小節中講解和分享了在條件中如何使用變量,我們假設一種測試場景:如果 if 控制器下的取樣器執行后,改變了該變量的值,if 控制器下 其後的取樣器還會被繼續執行嗎?跟隨宏哥一起來看看下面的列子:

2、改變“北京宏哥”變量的值為“北京宏哥”,如下圖所示:

3、JMeter執行過程的邏輯分析:

(1) if 控制器下 有 3 個取樣器,變量 北京宏哥 的初始值為 宏哥,if 控制器的條件為:${__jexl3(“${北京宏哥}”==”宏哥”,)}

(2)開始執行的時候滿足條件,那麼按理說應該執行 訪問博客園首頁訪問北京宏哥的博客園首頁訪問宏哥的JMeter系列文章 3個取樣器,

(3)但是 訪問北京宏哥的博客園首頁 執行后,將 北京宏哥 的值變了 北京宏哥,已經不能滿足 “${北京宏哥}”==”宏哥” 條件。

(4)所以 訪問宏哥的JMeter系列文章 這個取樣器不會被執行。

4、運行JMeter,查看結果樹,對比運行結果和宏哥分析的一致,如下圖所示:

5、如果這個時候,去掉  Evaluate for all children? 的勾選,會發生什麼呢,大家可以自己動手試試。修改後記得點擊“保存”。下邊是宏哥的執行結果,如下下圖所示:

另外,如果時字符串必須要用引號,變量都認為是字符串的形式,如:${__jexl3(“${北京宏哥}”==”宏哥”,)}。

4.2Transaction Controller

  事務響應時間是我們衡量業務性能的主要指標,事務控制器可以把其他節點下的取樣器執行消耗時間累加在一起,便於統計。同時對每一個取樣器的執行時間進行統計。
  如果事務控制器下的取樣器有多個,只有當所有的取樣器都運行成功,整個事務控制器定義的事物才算成功。
  用於將Test Plan中的特定部分組織成一個Transaction,JMeter中Transaction的作用在於,可以針對Transaction統計其響應時間、吞吐量等。比如說,一個用戶操作可能需要多個Sampler來模擬,此時使用Transaction Controller,可以更準確地得到該用戶操作的性能指標,如響應時間等。這個時間包含該控制器範圍內的所有處理時間,而不僅僅是採樣器的。

這個就非常有用了。我們前面有提到過事務的概念,有時候我們不關心單個請求的響應時間,而是關心一組相關請求的整體響應時間,怎麼來統計呢?就需要藉助事務這個概念,把這組請求,放到一個事務控制器下面。

1、我們先來看看這個Transaction Controller長得是啥樣子,路徑:線程組 > 添加 > 邏輯控制器 > 事務控制器,如下圖所示:

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

generate parent sample:選擇是否生成一個父取樣器;

include duration of timer and pre-post processors in generated samle:是否包含定時器,選擇將在取樣器前與后加上延時。(宏哥建議大家不要勾選,否則統計就比較麻煩了,還需要你扣除延時)

4.2.1generate parent sample用法

1、宏哥列舉一個測試場景:我們需要了解 訪問博客園首頁  訪問北京宏哥的博客園首頁這兩個請求的單個請求的響應時間,那麼就來看看如下實例。

(1)單個請求,那麼不勾選generate parent sample,如下圖所示:

2、運行JMeter,查看聚合報告的單個請求的響應時間,如下圖所示:

1、宏哥列舉一個測試場景:我們需要了解 訪問博客園首頁  訪問北京宏哥的博客園首頁這兩個請求作為一組請求的響應時間,那麼就來看看如下實例。

(1)一組請求,那麼勾選generate parent sample,如下圖所示:

2、運行JMeter,查看聚合報告的一組請求的響應時間,如下圖所示:

4.3Loop Controller

循環控制器可以控制在其節點下的元件的執行次數,可以是具體数字,也可以是變量。

1、我們先來看看這個Loop Controller長得是啥樣子,默認循環一次。路徑:線程組 > 添加 > 邏輯控制器 > 循環控制器,如下圖所示:

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

Forever:勾選上這一項表示一直循環下去。

注意:敲黑板,敲腦殼!!! 

如果同時設置了線程組的循環次數和循環控制器的循環次數,那循環控制器的子節點運行的次數為兩個數值相乘的結果。

4.3.1Thread Group和循環控制器的區別

1、現在宏哥準備兩個請求,設置線程組1個線程,5次loop,下邊有一個請求:訪問北京宏哥的博客園首頁  一個Loop Controller(設置2次loop),下邊有一個請求:訪問博客園首頁  

(1)線程組,如下圖所示:

(2)循環控制器,如下圖所示:

 

2、運行JMeter,查看結果樹,為了清楚地看出結果,宏哥將第一個請求故意配置成失敗的;如下圖所示:

從上邊的結果可以看出:

(1)如果同時設置了線程組的循環次數和循環控制器的循環次數,那循環控制器的子節點運行的次數為兩個數值相乘的結果。

(2)運行順序是:先執行線程組裡的循環,再執行循環控制器里的循環。

4.4While Controller

While條件控制器,其節點下的元件將一直運行直到While 條件為false。

1、我們先來看看這個While Controller長得是啥樣子,默認循環一次。路徑:線程組 > 添加 > 邏輯控制器 > While控制器,如下圖所示:

2、關鍵參數說明如下:

Name:名稱,可以隨意設置,甚至為空;

Comments:註釋,可隨意設置,可以為空;

Condition:接受變量表達式與變量。條件為 Flase 的時候,才會跳出 While 循環,否則一直執行 While 控制器下的元件。

3、While控制器提供三個常量

(1)Blank:當循環中最後一個取樣器失敗后停止

(2)LAST:當循換前有取樣器失敗,不進入循環

(3)Otherwise:當判斷條件為false時,停止循環

4.4.1Blank

1、不填(空):當 While 控制器下最後一個樣例執行失敗后 跳出循環,如下圖所示:

2、運行JMeter,查看結果樹,(你可以通過鼠標拖動最後失敗的取樣器,移動到第一個或者第二個位置的時候,運行JMeter后,會發現在一直運行);如下圖所示:

4.4.2LAST

LAST :當 While 控制器下最後一個樣例執行失敗后 跳出循環,如果 While 控制器 前一個樣例執行失敗,則不會進入While循環,也就是不會執行While控制器下的樣例。

1、取樣器樹還是上邊的位置和順序。這次我們在While控制器表達式處填寫:LAST,如下圖所示:

2、運行JMeter,查看結果樹,(你可以通過鼠標拖動最後失敗的取樣器,移動到第一個或者第二個位置的時候,運行JMeter后,會發現在一直運行);細心的你可以發現循環只跑一遍,與不填 的結果是一樣的如下圖所示:

3、但是輸入LAST的時候,還會出現一個結果,那就是:如果While 控制器 的前一個樣例執行失敗,則不會進入While 控制器

在While 控制器 前面 添加兩個取樣器:取樣器1 訪問百度,取樣器2 訪問北京宏哥 使取樣器2 訪問北京宏哥 執行失敗。取樣器2必須在While控制器前邊且執行失敗。如下圖所示:

 4、運行JMeter,查看結果樹,執行結果發現,取樣器1、取樣器2 執行了,但沒有進入While 控制器,如下圖所示:

4.4.3Otherwise

自定義條件:值為True 或 False的函數/變量/屬性 表達式;類似前邊講解的IF控制器,宏哥這裏就照貓畫虎的舉個例子。

1、用戶自定義變量,變量名:北京宏哥,變量值:true,如下圖所示:

2、While控制器配置,取到變量的值:${北京宏哥},填寫到表達式的地方,如下圖所示: 

3、JMeter執行過程的邏輯分析:

(1)北京宏哥用戶(線程組)下 有 1 個用戶自定義變量,變量 北京宏哥 的值為 true,While控制器的條件為:${北京宏哥} 取到的值始終是 true

(2)所以一旦開始執行始終滿足條件,那麼按理說就應該一直執行 訪問博客園首頁 、訪問北京宏哥的博客園首頁訪問宏哥的JMeter系列文章 3個取樣器,

4、運行JMeter,查看結果樹,(運行JMeter后,會發現在一直運行),對比一下,與宏哥的分析是不是高度一致哈;如下圖所示:

5.小結

 好了,今天關於邏輯控制器的上篇就講解到這裏,這一篇主要介紹了 IF控制器Transaction ControllerLoop ControllerWhile控制器

 

您的肯定就是我進步的動力。如果你感覺還不錯,就請鼓勵一下吧!記得隨手點波  推薦  不要忘記哦!!!

別忘了點 推薦 留下您來過的痕迹

 

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

【其他文章推薦】

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

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

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

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

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

【大廠面試07期】說一說你對synchronized鎖的理解?

synchronized鎖的原理也是大廠面試中經常會涉及的問題,本文主要通過對以下問題進行分析講解,來幫助大家理解synchronized鎖的原理。

1.synchronized鎖是什麼?鎖的對象是什麼?

2.偏向鎖,輕量級鎖,重量級鎖的執行流程是怎樣的?

3.為什麼說是輕量級,重量級鎖是不公平的?

4.重量級鎖為什麼需要自旋操作?

5.什麼時候會發生鎖升級,鎖降級?

6.偏向鎖,輕量鎖,重量鎖的適用場景,優缺點是什麼?

1.synchronized鎖是什麼?鎖的對象是什麼?

synchronized的英文意思就是同步的意思,就是可以讓synchronized修飾的方法,代碼塊,每次只能有一個線程在執行,以此來實現數據的安全。

一般可以修飾同步代碼塊、實例方法、靜態方法,加鎖對象分別為同步代碼塊塊括號內的對象、實例對象、類。

在實現原理上,

  • synchronized修飾同步代碼塊,javac在編譯時,在synchronized同步塊的進入的指令前和退出的指令后,會分別生成對應的monitorenter和monitorexit指令進行對應,代表嘗試獲取鎖和釋放鎖。
    (為了保證拋異常的情況下也能釋放鎖,所以javac為同步代碼塊添加了一個隱式的try-finally,在finally中會調用monitorexit命令釋放鎖。)
  • synchronized修飾方法,javac為方法的flags屬性添加了一個ACC_SYNCHRONIZED關鍵字,在JVM進行方法調用時,發現調用的方法被ACC_SYNCHRONIZED修飾,則會先嘗試獲得鎖。
public class SyncTest {
    private Object lockObject = new Object();
    public void syncBlock(){
        //修飾代碼塊,加鎖對象為lockObject
        synchronized (lockObject){
            System.out.println("hello block");
        }
    }
    //修飾實例方法,加鎖對象為當前的實例對象
    public synchronized void syncMethod(){
        System.out.println("hello method");
    }
    //修飾靜態方法,加鎖對象為當前的類
    public static synchronized void staticSyncMethod(){
        System.out.println("hello method");
    }
}

2.偏向鎖,輕量級鎖,重量級鎖的執行流程是怎樣的?

在JVM中,一個Java對象其實由對象頭+實例數據+對齊填充三部分組成,而對象頭主要包含Mark Word+指向對象所屬的類的指針組成(如果是數組對象,還會包含長度)。像下圖一樣:

Mark Word:存儲對象自身的運行時數據,例如hashCode,GC分代年齡,鎖狀態標誌,線程持有的鎖等等。在32位系統佔4字節,在64位系統中佔8字節,所以它能存儲的數據量是有限的,所以主要通過設立是否偏向鎖的標誌位鎖標誌位用於區分其他位數存儲的數據是什麼,具體請看下圖:

鎖信息都是存在鎖對象的Mark Word中的,當對象狀態為偏向鎖時,Mark Word存儲的是偏向的線程ID;當狀態為輕量級鎖時,Mark Word存儲的是指向線程棧中Lock Record的指針;當狀態為重量級鎖時,Mark Word為指向堆中的monitor對象的指針。

這是網上找到的一個流程圖,可以先看流程圖,結合著文字來了解執行流程

偏向鎖

Hotspot的作者經過以往的研究發現大多數情況下鎖不僅不存在多線程競爭,而且總是由同一線程多次獲得,於是引入了偏向鎖。

簡單的來說,就是主要鎖處於偏向鎖狀態時,會在Mark Word中存當前持有偏向鎖的線程ID,如果獲取鎖的線程ID與它一致就說明是同一個線程,可以直接執行,不用像輕量級鎖那樣執行CAS操作來加鎖和解鎖。

偏向鎖的加鎖過程:

場景一:當鎖對象第一次被線程獲得鎖的時候

線程發現是匿名偏向狀態(也就是鎖對象的Mark Word沒有存儲線程ID),則會用CAS指令,將mark word中的thread id由0改成當前線程Id。如果成功,則代表獲得了偏向鎖,繼續執行同步塊中的代碼。否則,將偏向鎖撤銷,升級為輕量級鎖。

場景二:當獲取偏向鎖的線程再次進入同步塊時

發現鎖對象存儲的線程ID就是當前線程的ID,會往當前線程的棧中添加一條Displaced Mark Word為空的Lock Record中,然後繼續執行同步塊的代碼,因為操縱的是線程私有的棧,因此不需要用到CAS指令;由此可見偏向鎖模式下,當被偏向的線程再次嘗試獲得鎖時,僅僅進行幾個簡單的操作就可以了,在這種情況下,synchronized關鍵字帶來的性能開銷基本可以忽略。

場景二:當沒有獲得鎖的線程進入同步塊時

當沒有獲得鎖的線程進入同步塊時,發現當前是偏向鎖狀態,並且存儲的是其他線程ID(也就是其他線程正在持有偏向鎖),則會進入到撤銷偏向鎖的邏輯里,一般來說,會在safepoint中去查看偏向的線程是否還存活

  • 如果線程存活且還在同步塊中執行,
    則將鎖升級為輕量級鎖,原偏向的線程繼續擁有鎖,只不過持有的是輕量級鎖,繼續執行代碼塊,執行完之後按照輕量級鎖的解鎖方式進行解鎖,而其他線程則進行自旋,嘗試獲得輕量級鎖。
  • 如果偏向的線程已經不存活或者不在同步塊中,
    則將對象頭的mark word改為無鎖狀態(unlocked)

由此可見,偏向鎖升級的時機為:當一個線程獲得了偏向鎖,在執行時,只要有另一個線程嘗試獲得偏向鎖,並且當前持有偏向鎖的線程還在同步塊中執行,則該偏向鎖就會升級成輕量級鎖。

偏向鎖的解鎖過程

因此偏向鎖的解鎖很簡單,其僅僅將線程的棧中的最近一條lock recordobj字段設置為null。需要注意的是,偏向鎖的解鎖步驟中並不會修改鎖對象Mark Word中的thread id,簡單的說就是鎖對象處於偏向鎖時,Mark Word中的thread id 可能是正在執行同步塊的線程的id,也可能是上次執行完已經釋放偏向鎖的thread id,主要是為了上次持有偏向鎖的這個線程在下次執行同步塊時,判斷Mark Word中的thread id相同就可以直接執行,而不用通過CAS操作去將自己的thread id設置到鎖對象Mark Word中。
這是偏向鎖執行的大概流程:

輕量級鎖

重量級鎖依賴於底層的操作系統的Mutex Lock來實現的,但是由於使用Mutex Lock需要將當前線程掛起並從用戶態切換到內核態來執行,這種切換的代價是非常昂貴的,而在大部分時候可能並沒有多線程競爭,只是這段時間是線程A執行同步塊,另外一段時間是線程B來執行同步塊,僅僅是多線程交替執行,並不是同時執行,也沒有競爭,如果採用重量級鎖效率比較低。以及在重量級鎖中,沒有獲得鎖的線程會阻塞,獲得鎖之後線程會被喚醒,阻塞和喚醒的操作是比較耗時間的,如果同步塊的代碼執行比較快,等待鎖的線程可以進行先進行自旋操作(就是不釋放CPU,執行一些空指令或者是幾次for循環),等待獲取鎖,這樣效率比較高。所以輕量級鎖天然瞄準不存在鎖競爭的場景,如果存在鎖競爭但不激烈,仍然可以用自旋鎖優化,自旋失敗后再升級為重量級鎖。

輕量級鎖的加鎖過程

JVM會為每個線程在當前線程的棧幀中創建用於存儲鎖記錄的空間,我們稱為Displaced Mark Word。如果一個線程獲得鎖的時候發現是輕量級鎖,會把鎖的Mark Word複製到自己的Displaced Mark Word裏面。

然後線程嘗試用CAS操作將鎖的Mark Word替換為自己線程棧中拷貝的鎖記錄的指針。如果成功,當前線程獲得鎖,如果失敗,表示Mark Word已經被替換成了其他線程的鎖記錄,說明在與其它線程競爭鎖,當前線程就嘗試使用自旋來獲取鎖。

自旋:不斷嘗試去獲取鎖,一般用循環來實現。

自旋是需要消耗CPU的,如果一直獲取不到鎖的話,那該線程就一直處在自旋狀態,白白浪費CPU資源。

JDK採用了適應性自旋,簡單來說就是線程如果自旋成功了,則下次自旋的次數會更多,如果自旋失敗了,則自旋的次數就會減少。

自旋也不是一直進行下去的,如果自旋到一定程度(和JVM、操作系統相關),依然沒有獲取到鎖,稱為自旋失敗,那麼這個線程會阻塞。同時這個鎖就會升級成重量級鎖。

輕量級鎖的釋放流程

在釋放鎖時,當前線程會使用CAS操作將Displaced Mark Word的內容複製回鎖的Mark Word裏面。如果沒有發生競爭,那麼這個複製的操作會成功。如果有其他線程因為自旋多次導致輕量級鎖升級成了重量級鎖,那麼CAS操作會失敗,此時會釋放鎖並喚醒被阻塞的線程。
輕量級鎖的加鎖解鎖流程圖:

重量級鎖

當多個線程同時請求某個重量級鎖時,重量級鎖會設置幾種狀態用來區分請求的線程:

Contention List:所有請求鎖的線程將被首先放置到該競爭隊列,我也不知道為什麼網上的文章都叫它隊列,其實這個隊列是先進后出的,更像是棧,就是當Entry List為空時,Owner線程會直接從Contention List的隊列尾部(后加入的線程中)取一個線程,讓它成為OnDeck線程去競爭鎖。(主要是剛來獲取重量級鎖的線程是回進行自旋操作來獲取鎖,獲取不到才會進從Contention List,所以OnDeck線程主要與剛進來還在自旋,還沒有進入到Contention List的線程競爭)

Entry List:Contention List中那些有資格成為候選人的線程被移到Entry List,主要是為了減少對Contention List的併發訪問,因為既會添加新線程到隊尾,也會從隊尾取線程。

Wait Set:那些調用wait方法被阻塞的線程被放置到Wait Set。

OnDeck:任何時刻最多只能有一個線程正在競爭鎖,該線程稱為OnDeck。

Owner:獲得鎖的線程稱為Owner

!Owner:釋放鎖的線程

重量級鎖執行流程:

流程圖如下:

步驟1是線程在進入Contention List時阻塞等待之前,程會先嘗試自旋使用CAS操作獲取鎖,如果獲取不到就進入Contention List隊列的尾部。

步驟2是Owner線程在解鎖時,如果Entry List為空,那麼會先將Contention List中隊列尾部的部分線程移動到Entry List

步驟3是Owner線程在解鎖時,如果Entry List不為空,從Entry List中取一個線程,讓它成為OnDeck線程,Owner線程並不直接把鎖傳遞給OnDeck線程,而是把鎖競爭的權利交給OnDeck,OnDeck需要重新競爭鎖,JVM中這種選擇行為稱為 “競爭切換”。(主要是與還沒有進入到Contention
List,還在自旋獲取重量級鎖的線程競爭)

步驟4就是OnDeck線程獲取到鎖,成為Owner線程進行執行。

步驟5就是Owner線程調用鎖對象的wait()方法進行等待,會移動到Wait Set中,並且會釋放CPU資源,也同時釋放鎖,

步驟6.就是當其他線程調用鎖對象的notify()方法,之前調用wait方法等待的這個線程才會從Wait Set移動到Entry List,等待獲取鎖。

3.為什麼說是輕量級,重量級鎖是不公平的?

偏向鎖由於不涉及到多個線程競爭,所以談不上公平不公平,輕量級鎖獲取鎖的方式是多個線程進行自旋操作,然後使用用CAS操作將鎖的Mark Word替換為指向自己線程棧中拷貝的鎖記錄的指針,所以誰能獲得鎖就看運氣,不看先後順序。重量級鎖不公平主要在於剛進入到重量級的鎖的線程不會直接進入Contention List隊列,而是自旋去獲取鎖,所以後進來的線程也有一定的幾率先獲得到鎖,所以是不公平的。

4.重量級鎖為什麼需要自旋操作?

因為那些處於ContetionList、EntryList、WaitSet中的線程均處於阻塞狀態,阻塞操作由操作系統完成(在Linxu下通過pthread_mutex_lock函數)。線程被阻塞后便進入內核(Linux)調度狀態,這個會導致系統在用戶態與內核態之間來回切換,嚴重影響鎖的性能。如果同步塊中代碼比較少,執行比較快的話,後進來的線程先自旋獲取鎖,先執行,而不進入阻塞狀態,減少額外的開銷,可以提高系統吞吐量。

5.什麼時候會發生鎖升級,鎖降級?

偏向鎖升級為輕量級鎖:
就是有不同的線程競爭鎖時。具體來看就是當一個線程發現當前鎖狀態是偏向鎖,然後鎖對象存儲的Thread id是其他線程的id,並且去Thread id對應的線程棧查詢到的lock record的obj字段不為null(代表當前持有偏向鎖的線程還在執行同步塊)。那麼該偏向鎖就會升級成輕量級鎖。

輕量級鎖升級為重量級鎖:
就是在輕量級鎖中,沒有獲取到鎖的線程進行自旋,自旋到一定次數還沒有獲取到鎖就會進行鎖升級,因為自旋也是佔用CPU的,長時間自旋也是很耗性能的。
鎖降級
因為如果沒有多線程競爭,還是使用重量級鎖會造成額外的開銷,所以當JVM進入SafePoint安全點(可以簡單的認為安全點就是所有用戶線程都停止的,只有JVM垃圾回收線程可以執行)的時候,會檢查是否有閑置的Monitor,然後試圖進行降級。

6.偏向鎖,輕量鎖,重量鎖的適用場景,優缺點是什麼?

篇幅有限,下面是各種鎖的優缺點,來自《併發編程的藝術》:

優點 缺點 適用場景
偏向鎖 加鎖和解鎖不需要額外的消耗,和執行非同步方法比僅存在納秒級的差距。 如果線程間存在鎖競爭,會帶來額外的鎖撤銷的消耗。 適用於只有一個線程訪問同步塊場景。
輕量級鎖 競爭的線程不會阻塞,提高了程序的響應速度。 如果始終得不到鎖競爭的線程使用自旋會消耗CPU。 追求響應時間。同步塊執行速度非常快。
重量級鎖 線程競爭不使用自旋,不會消耗CPU。 線程阻塞,響應時間緩慢。 追求吞吐量。同步塊執行速度較長。

參考鏈接:
https://github.com/farmerjohngit/myblog/issues/12
http://redspider.group:4000/article/02/9.html
https://blog.csdn.net/bohu83/article/details/51141836
https://blog.csdn.net/Dev_Hugh/article/details/106577862

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

【其他文章推薦】

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

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

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

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

DRY原則的一個簡單實踐

轉載請註明出處:葡萄城官網,葡萄城為開發者提供專業的開發工具、解決方案和服務,賦能開發者。

原文出處:https://dzone.com/articles/dry-dont-repeat-yourself

 

我們之前就發過一篇相關的文章:https://www.cnblogs.com/powertoolsteam/p/12758496.html 其中也提到了包括DRY在內的一些軟件開發的原則。

DRY 是軟件開發的原則之一,其目的主要是為了避免代碼重複,指導開發者盡量以抽象的思維去解決重複,基本上是,當您發現自己一遍又一遍地編寫相同的代碼時,可能會有更好的方法。 

實際案例

讓我們先看一個例子,看看這個例子是否可以改進,以及如何通過重構來避免代碼重複。

這裡有一個簡單的Report類,該類接收一些數據並通過控制台以格式化的方式直接輸出。

我們這裏使用php的一個代碼片段來舉例,相信大家對代碼的結構和想要完成的工作都不難理解,所以為了大家更容易理解,我只對一些下面用到的php函數定義做一個解釋:

  1. echo()  函數輸出一個或多個字符串
  2. ucwords()函數把字符串中每個單詞的首字符轉換為大寫。
  3. strtolower() 函數把字符串轉換為小寫。
  4. file_put_contents() 函數把一個字符串寫入文件中。
  5. floor() 函數向下舍入為最接近的整數。
class Report
{
   public function show(array $data)
   {
       echo "Report: " . ucwords(strtolower($data["name"])) . "\n";
       echo "Product: " . ucwords(strtolower($data["product"])) . "\n";
       echo "Start date: " . date("Y/m/d", $data["startDate"]) . "\n";
       echo "End date: " . date("Y/m/d", $data["endDate"]) . "\n";
       echo "Total: " . $data["total"] . "\n";
       echo "Average x day: " . floor($data["total"] / 365) . "\n";
       echo "Average x week: " . floor($data["total"] / 52) . "\n";
   }
}

可以看到,上面的代碼完成目標是沒有任何問題的。

這時我們對Report類提出一個新的需求:把所有字符串也可以保存到文件中。

我們經過一通複製和粘貼上面的代碼,新建一個名為saveToFile的函數,就可以很快的完成這個需求,代碼如下:

class Report
{
   public function show(array $data)
   {
       echo "Report: " . ucwords(strtolower($data["name"])) . "\n";
       echo "Product: " . ucwords(strtolower($data["product"])) . "\n";
       echo "Start date: " . date("Y/m/d", $data["startDate"]) . "\n";
       echo "End date: " . date("Y/m/d", $data["endDate"]) . "\n";
       echo "Total: " . $data["total"] . "\n";
       echo "Average x day: " . floor($data["total"] / 365) . "\n";
       echo "Average x week: " . floor($data["total"] / 52) . "\n";
       echo "Average x month: " . floor($data["total"] / 12) . "\n";
   }
   public function saveToFile(array $data)
   {
       $report = '';
       $report .= "Report: " . ucwords(strtolower($data["name"])) . "\n";
       $report .= "Product: " . ucwords(strtolower($data["product"])) . "\n";
       $report .= "Start date: " . date("Y/m/d", $data["startDate"]) . "\n";
       $report .= "End date: " . date("Y/m/d", $data["endDate"]) . "\n";
       $report .= "Total: " . $data["total"] . "\n";
       $report .= "Average x day: " . floor($data["total"] / 365) . "\n";
       $report .= "Average x week: " . floor($data["total"] / 52) . "\n";
       $report .= "Average x month: " . floor($data["total"] / 12) . "\n";
       file_put_contents("./report.txt", $report);
   }
}

那麼,上面的代碼能夠滿足我們提出的需求嗎?答案當然“是的”。但是從技術角度來看,這段代碼似乎是有些問題的,它的重複代碼到處都是。無論是對代碼閱讀及後期維護來講,這都是一場噩夢。

所以我們需要進行一些重構,抽象能抽象的方法,讓冗繁的代碼變得更簡潔。

首先,我們對Report類進行功能上的抽象,生成報告並輸出一共可以分為兩個功能,一個只負責創建Report,一個只負責如何處理Report,那麼讓我們開始重構吧。  

class Report
{
   public function show(array $data)
   {
       echo $this->createReport($data);
   }
   public function saveToFile(array $data)
   {
       file_put_contents("./report.txt", $this->createReport($data));
   }
   private function createReport(array $data): string
   {
       $report = '';
       $report .= "Report: " . ucwords(strtolower($data["name"])) . "\n";
       $report .= "Product: " . ucwords(strtolower($data["product"])) . "\n";
       $report .= "Start date: " . date("Y/m/d", $data["startDate"]) . "\n";
       $report .= "End date: " . date("Y/m/d", $data["endDate"]) . "\n";
       $report .= "Total: " . $data["total"] . "\n";
       $report .= "Average x day: " . floor($data["total"] / 365) . "\n";
       $report .= "Average x week: " . floor($data["total"] / 52) . "\n";
       $report .= "Average x month: " . floor($data["total"] / 12) . "\n";
       return $report;
   }
}

現在看起來更清楚一些,對嗎?

下面我們還有函數使用重複的問題要解決,例如,Report和Products的名稱函數使用重複:

$report .= "Report: " . ucwords(strtolower($data["name"])) . "\n";
$report .= "Product: " . ucwords(strtolower($data["product"])) . "\n";

我們可以將這些轉換抽象為一個新的函數:

private function normalizeName($name): string
{
   return ucwords(strtolower($name));
}

另一個重複:日期格式。

$report .= "Start date: " . date("Y/m/d", $data["startDate"]) . "\n";
$report .= "End date: " . date("Y/m/d", $data["endDate"]) . "\n";

讓我們將其抽象為:

private function formatDate($date): string
{
   return date("Y/m/d", $date);
}

最後一個:平均值計算。

$report .= "Average x day: " . floor($data["total"] / 365) . "\n";
$report .= "Average x week: " . floor($data["total"] / 52) . "\n";
$report .= "Average x month: " . floor($data["total"] / 12) . "\n";

儘管計算結果並不完全相同,但執行的操作大家是一致的,所以可以抽象為如下:

private function calculateAverage(array $data, $period): string
{
   return floor($data["total"] / $period);
}

所以,經過了一番重構,最終的Report類變為了如下:

class Report
{
   public function show(array $data)
   {
       echo $this->createReport($data);
   }
   public function saveToFile(array $data)
   {
       file_put_contents("./report.txt", $this->createReport($data));
   }
   private function createReport(array $data)
   {
       $report = '';
       $report .= "Report: " . $this->normalizeName($data["name"]) . "\n";
       $report .= "Product: " . $this->normalizeName($data["product"]) . "\n";
       $report .= "Start date: " . $this->formatDate($data["startDate"]) . "\n";
       $report .= "End date: " . $this->formatDate($data["endDate"]) . "\n";
       $report .= "Total: " . $data["total"] . "\n";
      $report .= "Average x day: " . $this->calculateAverage($data, 365) . "\n";
      $report .= "Average x week: " . $this->calculateAverage($data, 52) . "\n";
      $report .= "Average x month: " . $this->calculateAverage($data, 12) . "\n";
      return $report;
  }
  private function formatDate($date): string
  {
       return date("Y/m/d", $date);
   }
  private function calculateAverage(array $data, $period): string
  {
      return floor($data["total"] / $period);
  }
  private function normalizeName($name): string
  {
      return ucwords(strtolower($name));
  }
}

這是一個簡單的例子,實際情況可能比這要更加複雜的多,但我僅想通過這個實例向大家說明一個問題,那就是避免重複代碼的重要性及我們如何通過重構去處理重複代碼。

有時候重複一次相同的代碼可能沒問題,但是當第三次​​我們寫出相同的代碼時,那就說明是時候重構你的代碼了。

結論:

請記住DRY原則,並隨時抱着不要重複自己代碼的想法去完成開發工作。

 

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

【其他文章推薦】

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

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

※超省錢租車方案

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

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

野火包圍 舊金山灣區空污嚴重危害居民健康

摘錄自2020年8月25日中央社報導

舊金山灣區四周被野火包圍,有害的煙塵讓許多民眾呼吸困難、眼睛發炎,原本有呼吸系統宿疾者病情加劇。舊金山灣區空氣品質管理區(Bay Area Air Quality Management District)指出,野火將有害的煙塵送入天空,讓今天的空氣品質盤旋在不健康的指數。

舊金山多個城市的細懸浮微粒(PM2.5)都達到對健康有威脅的程度,並讓許多民眾難以呼吸。舊金山紀事報網站今(25日)指出,許多居住在火災區附近的醫生通報,有呼吸系統宿疾的患者在上週末病情加劇,也有醫生認為未來幾週內都將陸續出現更多呼吸系統疾病相關的病人。

公衛專家警告,惡化的空氣品質伴隨著武漢肺炎(COVID-19)疫情,會讓原本就有氣喘與肺部疾病的患者更不舒服,同時可能讓染疫的民眾更虛弱。

污染治理
國際新聞
美國
加州
舊金山
空氣污染
空氣品質
火災

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

【其他文章推薦】

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

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

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

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

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

用社群媒體對抗珊瑚白化 菲律賓鼓勵潛客拍照上傳App 民間自發響應監測

環境資訊中心綜合外電;黃鈺婷 翻譯;林大利 審校;稿源:Mongabay

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

【其他文章推薦】

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

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

※想知道購買電動車哪裡補助最多?台中電動車補助資訊懶人包彙整

南投搬家公司費用,距離,噸數怎麼算?達人教你簡易估價知識!

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

※超省錢租車方案

英雞肉加工廠爆疫情 7人陽性、5人隔離

摘錄自2020年8月25日自由時報報導

歐洲部分國家先前接連出現肉品加工廠員工確診武漢肺炎(COVID-19)。英國家禽生產公司「班翰姆家禽」(Banham Poultry)的1處雞肉加工廠傳出7名工作人員檢測陽性,另有5人隔離等待結果出爐。

綜合外媒報導,公衛主任史密斯(Louise Smith)透露,位於英格蘭諾福克郡阿特爾伯勒鎮、有數百人的雞肉加工廠出現疫情,將針對更多員工進行檢測,同時採取行動阻止病毒傳播。加工廠高層倫斯伯格(Blaine van Rensburg)回應,公司仍在營運中。

國際新聞
英國
疫情下的社會衝突

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

【其他文章推薦】

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

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

※Google地圖已可更新顯示潭子電動車充電站設置地點!!

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

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

網頁設計最專業,超強功能平台可客製化

助世界農業遺產「水山葵」度武肺危機 日本食材宅配公司推地產地銷

文:宋瑞文

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

【其他文章推薦】

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

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

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

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

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

科學家首次分析 地球30年間失去28兆噸冰

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

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

【其他文章推薦】

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

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

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

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

※產品缺大量曝光嗎?你需要的是一流包裝設計!

阿富汗首都北方城市暴洪肆虐 至少100死

摘錄自2020年8月27日中央社報導

阿富汗官員今(26日)表示,暴雨引發的暴洪,肆虐首都喀布爾北方一座城市,造成至少100人罹難、約100人受傷以及將近500棟民宅被毀,罹難者包括許多小孩。

軍方救難人員從巴萬省(Parwan)首府查里喀爾市(Charikar)倒塌建築物的瓦礫堆中,拉出數十名受害人。當地昨晚的暴雨,助長猛烈洪水侵襲這座城市。

阿富汗國家災難管理局表示,罹難人數攀升至100人,另有約100人受傷以及將近500棟民宅被洪水摧毀。

國際新聞
阿富汗
暴雨
洪患

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

【其他文章推薦】

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

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

※超省錢租車方案

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

網頁設計最專業,超強功能平台可客製化

※產品缺大量曝光嗎?你需要的是一流包裝設計!

國產SUV新貴GS8大熱 那麼更便宜的GS7會怎樣呢?

GS7的側面造型和他大哥在C柱之前相似度較高,但是因為GS7的軸距較短,所以GS7看起來顯得緊湊很多。不過GS7的側窗並沒有沿襲GS8隱藏式的D柱設計,而是被銀色的鍍鉻裝飾條完整的包圍起來。對於這種設計,小編覺得GS7前臉都和大哥相似了,如果這部分再在用相同的設計,那麼設計師肯定會被吐槽偷懶的,其實這樣的設計也好,畢竟國人也很喜歡鍍鉻裝飾。

GS8無疑是2016年一款極其重要的車型,但是GS8全係為七座車型,有些家庭用不上那麼多的座位,買來之後便成了擺設。這一點讓喜歡GS8的人有點拿不定主意了。

所以大家都在議論着,要是GS8有個五座車型就好了。其實這點不用擔心的,因為傳祺在推出GS8的時候就已經有了一些關於GS7的傳聞,只不過,這次的GS7,終於要以真面目示眾了。

說到GS7那麼必須要提到北美車展,北美車展是世界五大車展之一,同時也是北美洲規模最大的國際車展,此車展每年一月份在美國底特律舉辦。因為是舉辦的日期比較特殊(新年的第一個大規模車展),所以北美車展具有很強的市場前瞻性,當然,我們很期待的傳祺GS7,也在此時亮相了。

廣汽傳祺GS7在北美車展全球首發,看點十足,當然不能錯過。GS7和GS8關係非常密切,你可以看作是短軸距版的GS8,GS7定位中型SUV,全係為五座車型。

GS7的車身長度和軸距要比GS8短80mm,但是寬度和高度與GS8一致。要說外觀,尤其是車頭的部分,看了之後你的第一反應肯定會是這個車頭真是很霸氣,但是隨後你可能會想,怎麼感覺這麼面熟呢?沒錯,因為GS7的車頭部分的設計基本和GS8一致,凌雲翼式的造型是很大氣,因為見過了GS8,所以當你看到GS7前臉的時候就不會覺得那麼驚艷了。這就是所謂的家族式設計,雖然依舊好看,但是不會讓消費者感到會有大的新鮮感。

小編最喜歡大燈組的設計,看起來感覺很高級,但是又不會顯得很臃腫。至於大家期待的LED大燈應該不會全系標配了(可以參考GS8)。

GS7的側面造型和他大哥在C柱之前相似度較高,但是因為GS7的軸距較短,所以GS7看起來顯得緊湊很多。

不過GS7的側窗並沒有沿襲GS8隱藏式的D柱設計,而是被銀色的鍍鉻裝飾條完整的包圍起來。對於這種設計,小編覺得GS7前臉都和大哥相似了,如果這部分再在用相同的設計,那麼設計師肯定會被吐槽偷懶的,其實這樣的設計也好,畢竟國人也很喜歡鍍鉻裝飾。

剛說過上面的不同點,這下子又被打臉了。因為尾部造型和大哥幾乎完全一致,肉眼很難看出有什麼太大的差別,畢竟,這兩個車子關係很親密么!

內飾還是那一句話,參考GS8,GS7的內飾造型大致和GS8相同。

後排地板中央凸起不是很高,同時後排空間的表現也比較寬裕,家用足夠。

至於動力系統,GS7將會搭載代號為4B20M1的2.0T發動機(和GS8是同款發動機)。最大功率201馬力,最大扭矩320,變速箱為6速手自一體,同時有4種變速箱模式可以切換。

其實看到這裏,GS7的身份已經很明確了,大致上就是一款縮小的GS8,但是可以肯定的是GS7的入門價肯定會更加地氣,對於那些不需要7座車型,同時覺得GS8有點貴的消費者,可以看一下GS7。

競爭對手

哈弗H7

指導價:14.98-19.38萬

H7是一款性價比很高的SUV,H7和H7L互相配合交出了不錯的銷售業績,不過GS7的帶來,肯定會帶給H7一些壓力的。本站聲明:網站內容來源於http://www.auto6s.com/,如有侵權,請聯繫我們,我們將及時處理

【其他文章推薦】

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

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

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

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

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