Jmeter(十三) – 從入門到精通 – JMeter定時器 – 上篇(詳解教程)

1.簡介

  用戶實際操作時,並非是連續點擊,而是存在很多停頓的情況,例如:用戶需要時間閱讀文字內容、填表、或者查找正確的鏈接等。為了模擬用戶實際情況,在性能測試中我們需要考慮思考時間。若不認真考慮思考時間很可能會導致測試結果的失真。例如,估計的可支撐用戶數偏小。在性能測試中,訪問請求之間的停頓時間被稱之為思考時間,那麼如何模擬這種停頓呢?我們可以藉助JMeter的定時器實現。

  JMeter中的定時器一般被我們用來設置延遲與同步。定時器的執行優先級高於Sampler(取樣器),在同一作用域(例如控制器下)下有多個定時器存在時,每一個定時器都會執行,如果想讓某一定時器僅對某一Sampler有效,則可以把定時器加在此Sampler節點下。

2.預覽定時器

首先我們來看一下JMeter的定時器,路徑:線程組(用戶)->添加->定時器(Timer);我們可以清楚地看到JMeter5中共有9個定時器,如下圖所示:

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

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

4.常用定時器詳解

這一小節,宏哥就由上而下地詳細地講解一下常用的定時器。

4.1Constant Timer

固定定時器,看名稱大家也知道是一個固定定時器,多用來模擬思考時間,顧名思義是:請求之間的間隔時間為固定值。

作用:通過ThreadDelay設定每個線程請求之前的等待時間(單位為毫秒)。注意:固定定時是有作用域的,放到線程組下其作用域是所有請求都會延遲固定器設置的時間,如果放到請求內,作用域是單個請求延遲時間(常用)。

1、我們先來看看這個Constant Timer長得是啥樣子,路徑:線程組 > 添加 > 定時器 > 固定定時器,如下圖所示: 

2、關鍵參數說明如下:

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

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

Thread Delay(in milliseconds):線程等待時間,單位毫秒。

用法(場景),更真實的模擬用戶場景,需要設置等待時間,或是等待上一個請求的時間,才執行,給sampler之間的思考時間;

4.1.1實例

場景應用:性能測試中,根據用戶操作預估時間,或者需要等待一段時間來加載數據。
PS:在實際模擬用戶請求的過程中,會失去靈活性,不推薦大量使用

1、新建測試計劃,線程組下添加2個取樣器 訪問博客園首頁、訪問度娘,如下圖所示:

2、然後再添加固定定時器,設置延遲時間3000ms,即3s,如下圖所示:

3、配置好以後,點擊“保存”,運行JMeter,查看錶格結果(取樣器訪問博客園首頁和訪問度娘間隔3s),如下圖所示:

4.2Uniform Random Timer

統一(均勻)隨機定時器,也是讓線程暫停一個隨機時間,只不過力求隨機時間能夠更均勻,都會出現。均勻隨機定時器,顧名思義,它產生的延遲時間是個隨機值,而各隨機值出現的概率均等。總的延遲時間等於一個隨機延遲時間加上一個固定延遲時間,用戶可以設置隨機延遲時間和固定延遲時間。

作用:它產生的延遲時間是個隨機值,而各隨機值出現的概率均等。總的延遲時間等於一個隨機延遲時間加上一個固定延遲時間,用戶可以設置隨機延遲時間和固定延遲時間。每個線程的延遲時間是符合標準正態分佈的隨機時間停頓,那麼使用這個定時器,總延遲 = 高斯分佈值(平均0.0和標準偏差1.0)* 指定的偏差值+固定延遲偏移(Math.abs((this.random.nextGaussian() * 偏差值) + 固定延遲偏移))

總延遲時間 = 指定範圍內的隨機時間(在範圍內各隨機值等概率)+ 固定延遲時間

1、我們先來看看這Uniform Random Timer長得是啥樣子,路徑:線程組 > 添加 > 定時器 > 統一隨機定時器,如下圖所示: 

2、關鍵參數說明如下:

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

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

Random Delay Maximum:最大隨機延遲時間;

Constant Delay Offset: 固定延遲時間。

4.2.1實例

1、新建測試計劃,線程組下添加2個取樣器 訪問博客園首頁、訪問度娘,如下圖所示:

2、然後再添加統一隨機定時器,設置延遲時間3s,如下圖所示:

3、配置好以後,點擊“保存”,運行JMeter,查看錶格結果(取樣器訪問博客園首頁和訪問度娘間隔4s = 1000ms + 3000ms),如下圖所示:

4.3Precise Throughput Timer

準確的吞吐量定時器,顧名思義,這個就是控制吞吐量的。和Constant Throughput Timer類似,但是能更精準的控制請求。區別就是Constant Throughput Timer根據時間來做定時器(到了多少秒就發請求);Precise Throughput Timer是根據吞吐量在做計時器(到了多少量就發請求)。也就是能做到控制請求的速度和個數。

1、我們先來看看這個Precise Throughput Timer長得是啥樣子,路徑:線程組 > 添加 > 定時器 > 準確的吞吐量定時器,如下圖所示: 

2、關鍵參數說明如下:

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

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

Thread Delay:忽略子控制器,即子控制器失效,由交替控制器接管。

Target Throught:目標吞吐量

Throught Period:表示在多長時間內發送Target Throught指定的請求數(以秒為單位)

Test Druation:指定測試運行時間(以秒為單位)

Number of threads in the bath:用來設置集合點,等到指定個數的請求后併發執行

4.3.1實例

1、新建測試計劃,線程組(設置線程組,保證有足夠的時間)下添加2個取樣器 訪問博客園首頁(已禁用)、訪問度娘,如下圖所示:

2、然後再添加準確的吞吐量定時器,設置10個吞吐量,設置10s啟動完10個請求,設置運行時間20s,如下圖所示:

3、配置好以後,點擊“保存”,運行JMeter,查看錶格結果(大約用了20秒啟動了21個線程),如下圖所示:

4、設置集合點在Precise Throughput Timer中設置集合點為10,其它參數不變,如下圖所示:

5、在Thread Group中設置線程數為10,如下圖所示: 

6、配置好以後,點擊“保存”,運行JMeter,查看錶格結果(可以看到,每10個線程為1組,同時啟動。),如下圖所示: 

4.4Constant Throughput Timer

固定吞吐量定時器,這個定時器引入了變量暫停,通過計算使總吞吐量(以每分鐘去楊樹計)盡可能接近給定的数字。當然,如果服務器不能處理它,或者如果其他定時器或耗時的測試原件阻止它,那麼吞吐量將更低。
雖然計時器被稱為常數吞吐量定時器,但吞吐量值並不一定是常數。它可以根據變量或函數調用定義,並且可以在測試期間改變該值。通過以下多種方式都可以改變:
使用計數器變量
使用一個 __jexl3, __groovy 函數來提供一個變化的值
使用遠程BeeShell服務器更改Jmeter屬性
請注意,在測試期間,不應該頻繁地更改吞吐量值——新值生效需要一段時間。

常數吞吐量定時器作用:控制吞吐量(線上壓測時候,避免一下就上百上千的吞吐量影響線上性能,加上這個之後較安全,可以一點一點往上加); 按指定的吞吐量執行,以每分鐘為單位。計算吞吐量依據是最後一次線程的執行時延。

作用域:此定時器放在請求的下級,只對它的上級請求起作用

1、我們先來看看這個Constant Throughput Timer長得是啥樣子,路徑:線程組 > 添加 > 定時器 > 常數吞吐量定時器,如下圖所示: 

2、關鍵參數說明如下:

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

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

Target throughput(in samples per minute):目標吞吐量。注意這裡是每分鐘發送的請求數,可以選擇作用的線程:當前線程、當前線程組、所有線程組等,具體含義如下:

this thread only: 設置每個線程的吞吐量。總的吞吐量=線程數*該值。

all active threads in current thread group:吞吐量被分攤到當前線程組所有的活動線程上。每個線程將根據上次運行時間延遲。

all active threads:吞吐量被分配到所有線程組的所有活動線程的總吞吐量。每個線程將根據上次運行時間延遲。在這種情況下,每個線程組需要一個具有相同設置的固定吞吐量定時器。(不常用)

all active threads in current thread group (shared):同上,但是每個線程是根據組中的線程的上一次運行時間來延遲。相當於線程組組內排隊。(不常用)

all active threads (shared):同上,但每個線程是根據線程的上次運行時間來延遲。相當於讓所有線程組整體排隊。(不常用)

 4.4.1實例

1、新建測試計劃,線程組下添加1個取樣器 訪問博客園首頁(已禁用)、訪問度娘,如下圖所示:

2、然後再添加常數吞吐量定時器,設置目標吞吐量為300,如下圖所示:

3、配置好以後,點擊“保存”,運行JMeter,查看jp@gc – Transactions per Second(常數吞吐量定時器設置300/分鐘,也就是5/秒,故tps最大5,這裏的tps大約都是5,說明已經超過5,可以往上增加了),如下圖所示:

5. 定時器的作用域

1. 定時器是在每個sampler(採樣器)之前執行的,而不是之後(無論定時器位置在sampler之前還是下面);
2. 當執行一個sampler之前時,所有當前作用域內的定時器都會被執行;
3. 如果希望定時器僅應用於其中一個sampler,則把定時器作為子節點加入;
4. 如果希望在sampler執行完之後再等待,則可以使用Test Action;

6.小結

6.1安裝插件管理

1、安裝前查看選項,沒有看到插件管理,如下圖所示:

2、想安裝一個jmeter的插件,到官網(http://jmeter-plugins.org)上去下載插件安裝包,但是頁面一直都是搜索狀態,如下圖所示:

3、然後宏哥找了一個下載一個jmeter的插件管理工具 地址: http://jmeter-plugins.org/get/

4、將下載的文件拷貝的你的JMeter根目錄下的 lib/ext 目錄,如下圖所示:

5、 重啟jmeter,在選項中可以看到插件管理工具已經安裝成功,如下圖所示:

 6、勾選要下載的插件,點擊Apply changes and restart JMeter按鈕就完成了

Installed Plugins:用於查看已安裝的插件,並可通過 取消勾選 – 應用操作 來卸載插件

Available Plugins:用於查看和安裝可用的插件,通過 勾選-應用操作(右下側有按鈕Apply changes and restart JMeter) 來安裝插件

Upgrades:用於升級插件

   好了,今天關於定時器的上篇就講解到這裏,這一篇主要介紹了 Constant Timer 、Uniform Random Timer、Precise Throughput Timer和 Constant Throughput Timer。感謝你耐心的閱讀和學習。

 

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

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

 

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

【其他文章推薦】

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

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

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

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

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

Spring Cloud Alibaba系列(五)sentinel實現服務限流降級

一、sentinel是什麼

sentinel的官方名稱叫分佈式系統的流量防衛兵。Sentinel 以流量為切入點,從流量控制、熔斷降級、系統負載保護等多個維度保護服務的穩定性。在Spring Cloud項目中最開始我們使用的是Hystrix,目前已停止更新了。現在Spring Cloud官方推薦的是rensilience4j。當然還有我們今天學習的sentinel。

Sentinel 具有以下特徵:

  • 豐富的應用場景:Sentinel 承接了阿里巴巴近 10 年的雙十一大促流量的核心場景,例如秒殺(即突發流量控制在系統容量可以承受的範圍)、消息削峰填谷、集群流量控制、實時熔斷下游不可用應用等。
  • 完備的實時監控:Sentinel 同時提供實時的監控功能。您可以在控制台中看到接入應用的單台機器秒級數據,甚至 500 台以下規模的集群的匯總運 行情況。
  • 廣泛的開源生態:Sentinel 提供開箱即用的與其它開源框架/庫的整合模塊,例如與 Spring Cloud、Dubbo、gRPC 的整合。您只需要引入相應的依賴並進行簡單的配置即可快速地接入 Sentinel。
  • 完善的 SPI 擴展點:Sentinel 提供簡單易用、完善的 SPI 擴展接口。您可以通過實現擴展接口來快速地定製邏輯。例如定製規則管理、適配動態數據源等。

二、sentinel實現限流

2.1 安裝sentinel控制台

  • 下載地址:https://github.com/alibaba/Sentinel/releases

這裏我們直接下載jar包即可,下載后通過命令行啟動:

java -jar sentinel-dashboard-1.7.2.jar
  • 默認端口:8080
  • 默認用戶名:sentinel
  • 默認密碼:sentinel

啟動成功后,我們瀏覽器訪問http://localhost:8080,出現如下界面。

2.2 微服務繼承sentinel

  • 引入sentinel依賴
<dependency>
    <groupId>com.alibaba.cloud</groupId>
    <artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
  • 添加sentinel的相關配置
server:
  port: 7003
spring:
  application:
    name: sentinel-provider
  cloud:
	nacos:
      discovery:
        server-addr: 127.0.0.1:8848
    sentinel:
      transport:
        dashboard: 127.0.0.1:8080
  • 提供個接口用來測試限流
@SpringBootApplication
public class SentinelApplication {

    public static void main(String[] args) {
        SpringApplication.run(SentinelApplication.class, args);
    }
}

@RestController
class TestController{
    @GetMapping("/test")
    public String test(){
        return "hello! sentinel!";
    }
}

我們請求幾次這個接口后,打開sentinel控制台,就可以實時監控到這個sentinel-provider服務接口調用情況了。

2.3 配置限流規則

我們這裏做一個簡單的規則配置:

  • 閥值類型:QPS

  • 單機閥值:2

意思就是:該接口每秒最多允許進入兩個請求。

點擊新增后,在流控規則里發現了一條規則:

現在,我們繼續請求3次這個接口。第三次響應的內容如下:

Blocked by Sentinel (flow limiting)

我們打開控制台發現拒絕了一條請求。

三、Sentinel規則介紹

不管是限流還是降級,它都是按照某種規則進行的,下面具體介紹一下sentinel支持的幾種規則。

3.1 流控規則

流量控制,其原理是監控應用流量的QPS(每秒查詢率) 或併發線程數等指標,當達到指定的閾值時

對流量進行控制,以避免被瞬時的流量高峰衝垮,從而保障應用的高可用性。

資源名:唯一名稱,默認是請求路徑,可自定義

針對來源:指定對哪個微服務進行限流,默認指default,意思是不區分來源,全部限制

閾值類型/單機閾值:

  • QPS(每秒請求數量): 當調用該接口的QPS達到閾值的時候,進行限流

  • 線程數:當調用該接口的線程數達到閾值的時候,進行限流

3.2 降級規則

降級規則就是當滿足什麼條件時,對服務降級——即將請求轉發到另外接口上,這個接口與業務無關,只是為了保證系統的完整性。

  • RT(平均響應時間) :當資源的平均響應時間超過閾值(以 ms 為單位)之後,資源進入准降級狀態。如果接下來 1s 內持續進入 5 個請求,它們的 RT都持續超過這個閾值,那麼在接下的時間窗口(以 s 為單位)之內,就會對這個方法進行服務降級。

    注意 Sentinel 默認統計的 RT 上限是 4900 ms,超出此閾值的都會算作 4900 ms,若需要變更此上限可以通過啟動配置項 -Dcsp.sentinel.statistic.max.rt=xxx 來配置。

  • 異常比例:當資源的每秒異常總數占通過量的比值超過閾值之後,資源進入降級狀態,即在接下的時間窗口(以 s 為單位)之內,對這個方法的調用都會自動地返回。異常比率的閾值範圍是 [0.0,1.0]。

  • 異常數 :當資源近 1 分鐘的異常數目超過閾值之後會進行服務降級。注意由於統計時間窗口是分鐘級別的,若時間窗口小於 60s,則結束熔斷狀態后仍可能再進入熔斷狀態。

3.3 熱點規則

熱點規則允許將規則具體到參數上。

我們用個例子來看看效果。

  • 編寫接口
@GetMapping("/myTest")
@SentinelResource("test3")
public String test123(String name,String age){
    return  name + "----"+ age;
}
  • 添加規則
  • 運行效果

結果显示,第一個參數被限流了,而第二個參數正常。

3.4 系統規則

系統保護規則是從應用級別的入口流量進行控制,從單台機器的總體 Load、RT、入口 QPS 、CPU使用率和線程數五個維度監控應用數據,讓系統盡可能跑在最大吞吐量的同時保證系統整體的穩定性。

系統保護規則是應用整體維度的,而不是資源維度的,並且僅對入口流量 (進入應用的流量) 生效。

  • Load(僅對 Linux/Unix-like 機器生效):當系統 load1 超過閾值,且系統當前的併發線程數超過系統容量時才會觸發系統保護。系統容量由系統的 maxQps * minRt 計算得出。設定參考值一般是 CPU cores * 2.5。

  • RT:當單台機器上所有入口流量的平均 RT 達到閾值即觸發系統保護,單位是毫秒。

  • 線程數:當單台機器上所有入口流量的併發線程數達到閾值即觸發系統保護。

  • 入口 QPS:當單台機器上所有入口流量的 QPS 達到閾值即觸發系統保護。

  • CPU使用率:當單台機器上所有入口流量的 CPU使用率達到閾值即觸發系統保護。

3.5 授權規則

很多時候,我們需要根據調用來源來判斷該次請求是否允許放行,這時候可以使用 Sentinel 的來源問控制的功能。來源訪問控制根據資源的請求來源(origin)限制資源是否通過:

  • 若配置白名單,則只有請求來源位於白名單內時才可通過;

  • 若配置黑名單,則請求來源位於黑名單時不通過,其餘的請求通過。

流控應用:sentinel提供了RequestOriginParser來處理接口來源。

我們運行abc來源的請求訪問/test接口。

@Component
class requestOrigin implements RequestOriginParser{

    @Override
    public String parseOrigin(HttpServletRequest httpServletRequest) {
        String server = httpServletRequest.getParameter("server");
        return server;
    }
}

我們請求http://localhost:7003/test?server=abc 和 http://localhost:7003/test?server=ab來分別看看效果。

@SentinelResource的使用

@SentinelResource 用於定義資源,並提供可選的異常處理和 fallback 配置項。

主要參數有以下幾個

屬性 作用
value 資源名稱
entryType entry類型,標記流量的方向,取值IN/OUT,默認是OUT
blockHandler 處理BlockException的函數名稱,函數要求:1. 必須是 public;2.返回類型 參數與原方法一致;3. 默認需和原方法在同一個類中。若希望使用其他類的函數,可配置blockHandlerClass ,並指定blockHandlerClass裏面的方法。
blockHandlerClass 存放blockHandler的類,對應的處理函數必須static修飾。
fallback 1. 返回類型與原方法一致;2. 參數類型需要和原方法相匹配;3. 默認需和原方法在同一個類中。若希望使用其他類的函數,可配置fallbackClass
fallbackClass 存放fallback的類。對應的處理函數必須static修飾。
defaultFallback 若同時配置了 fallback 和 defaultFallback,以fallback為準。
exceptionsToIgnore 指定排除掉哪些異常。排除的異常不會計入異常統計,也不會進入fallback邏輯,而是原樣拋出。
exceptionsToTrace 需要trace的異常

@sentinelResource可結合blockHandler用於限流處理,結合fallback用於降級處理。具體規則可通過sentinel控制台配置,具體我就不演示了,在下一章內容中,我會分別演示限流和降級的應用。

public class MySentinelResource {

    @SentinelResource(value="message",blockHandler="blockHandler",fallback="fallback")
    public String message(String str){
        if(StringUtils.isBlank(str)){
            throw new RuntimeException();
        }
        return str;
    }
    /**
     * 限流處理
     * @param str
     * @param ex
     * @return
     */
    public String blockHandler(String str, BlockedException ex){
        return str + "--"+ ex;
    }
    /**
     * 降級處理
     * @param str
     * @return
     */
    public String fallback(String str){
        return null;
    }
}

代碼示例

gitee:https://gitee.com/zhixie/spring-cloud-alibaba-learning/tree/master/sentinel-server

github:https://github.com/binzh303/spring-cloud-alibaba-learning/tree/master/sentinel-server

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

【其他文章推薦】

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

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

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

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

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

易到用車APP上線數千輛新能源車 最低5元人民幣即可搭乘

日前,易到用車啟動了「E-Car計畫」,將陸續在北京、上海、廣州、深圳上線數千輛新能源車型,打造新能源出行平台;與此同時易到用車還宣佈將與普天新能源、龐大集團、騰勢等品牌展開深度合作,共同打造新能源出行聯盟,共建新能源城市。   屆時,易到用車App將上線開通「新能源」車型選項。此次加入易到「E-Car計畫」的車型包括混合動力的雙擎普銳斯、沃爾沃S60L、北汽EV系列以及特斯拉,用戶可通過易到用車App,在「新能源」車型中按需選擇,價格最低5元人民幣(下同)起步,0元/分鐘,0.99元/公里。   易到方面介紹,作為聯盟計畫的一部分,易到用車將與普天合作,在未來共同建設1000個充電樁設施,也同時尋找更多合作夥伴,提供場地共同建樁,而且充電樁建好後,將面向社會全面開放使用。

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

【其他文章推薦】

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

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

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

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

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

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

為電動車佈局?鴻海2250萬美元入股上海嘉銘

根據《經濟日報》5月19日報導,鴻海集團昨日代子公司富泰華工業(深圳)有限公司發佈公告,取得嘉銘融資租賃(上海)有限公司股權,預計投資金額為2,250萬美元,持股比例將達75%。   市場猜測,鴻海集團投資此融資租賃公司,應該與電動車佈局有關。現在中國大陸融資租賃、分時租賃是電動車銷售的主要動力,大陸日前也有報告指出,目前,新能源汽車領域融資租賃的滲透率約為14%。預計到2025年,新能源汽車銷量將不低於500萬輛,融資租賃滲透率至少達到22%。汽車金融及租賃服務是新能源汽車業價值鏈中最具有獲利能力的環節,毛利率甚至可望高達30%左右。

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

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

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

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

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

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

※回頭車貨運收費標準

併發07–線程池及Executor框架

一、JAVA中的線程池

線程池的實現原理及流程如下圖所示:

 

 

   如上圖所示,當一個線程提交到線程池時(execute()或submit()),先判斷核心線程數(corePoolSize)是否已滿,如果未滿,則直接創建線程執行任務;如果已滿,則判斷隊列(BlockingQueue)是否已滿,如果未滿,則將線程添加到隊列中;如果已滿,則判斷線程池(maximumPoolSize)是否已滿,如果未滿,則創建線程池執行任務;如果線程池已滿,則交給飽和策略(RejectedExecutionHandler.rejectExcution())來處理。

  可以看下線程池ThreadPoolExecutor的全參構造函數源碼:

    public ThreadPoolExecutor(int corePoolSize,
                              int maximumPoolSize,
                              long keepAliveTime,
                              TimeUnit unit,
                              BlockingQueue<Runnable> workQueue,
                              ThreadFactory threadFactory,
                              RejectedExecutionHandler handler) {
        if (corePoolSize < 0 ||
            maximumPoolSize <= 0 ||
            maximumPoolSize < corePoolSize ||
            keepAliveTime < 0)
            throw new IllegalArgumentException();
        if (workQueue == null || threadFactory == null || handler == null)
            throw new NullPointerException();
        this.acc = System.getSecurityManager() == null ?
                null :
                AccessController.getContext();
        this.corePoolSize = corePoolSize;
        this.maximumPoolSize = maximumPoolSize;
        this.workQueue = workQueue;
        this.keepAliveTime = unit.toNanos(keepAliveTime);
        this.threadFactory = threadFactory;
        this.handler = handler;
    }

對其入參釋義如下:

參數 描述 作用
coolPoolSize 線程核心線程數 當一個任務提交到線程池時,線程池會創建一個線程來執行任務,即使其他的核心線程足夠執行新任務,也會創建線程,直到需要執行的任務數大於核心線程數后才不再創建;如果線程池先調用了preStartAllCoreThread()方法,則會先啟動所有核心線程。
maximumPoolSize 線程池最大線程數 如果隊列滿了,並且已創建的線程數小於該值,則會創建新的線程執行任務。這裏需要說明一點,如果使用的隊列時無界隊列,那麼該值無用。
keepAliveTime 存活時間 當線程池中線程超過超時時間沒有新的任務進入,則停止該線程;只會停止多於核心線程數的那幾個線程。
unit 線程存活的時間單位 可以有天、小時、分鐘、秒、毫秒、微妙、納秒
workQueue 任務隊列

用於保存等待執行任務的阻塞隊列。可以選擇如下幾個隊列:數組結構的有界隊列ArrayBlockingQueue、鏈表結果的有界隊列LinkedBlockingQueue、不存儲元素的阻塞隊列SynchronousQueue、一個具有優先級的無界阻塞隊列PriortyBlockingQueue

threadFactory 創建線程的工廠

可以通過工廠給每個線程創建更有意義的名字。使用Guava提供的ThreadFactoryBuilder可以快速的給線程池裡的線程創建有意義的名字,代碼如下

new ThreadFactoryBuilder().setNameFormat(“aaaaaaaa”).build();

handler 包和策略

當隊列和線程都滿了,說明線程池處於飽和狀態,那麼必須採取一種策略來處理新提交的任務。

AbortPolicy(默認),表示無法處理新任務時拋出異常。

CallerRunsPolicy:只有調用者所在線程來運行

DiscardOldestPolicy:丟棄隊列里最近的一個任務,並執行當前任務

DiscardPolicy:不處理,直接丟棄

  上面說到,向線程池提交任務有兩種方法,分別是execute()和submit(),兩者的區別主要是execute()提交的是不需要有返回值的任務,而submit提交的是需要有返回值的任務,並且submit()會返回一個Furure對象,並且可以使用future.get()方法獲取返回值,並且get方法會阻塞,直到有返回值。

  線程池的關閉有shutdown()和shutdownNow兩個方法,他們的原理是遍歷線程池中的工作線程,然後逐個調用interrupt方法來中斷線程,所以無法中斷的線程可能永遠無法終止;但是二者也有區別,shutdownNow是將線程池的狀態設置為STOP,然後嘗試停止所有正在執行或者暫停的線程,並返回等待執行任務列表;而shutdown只是將線程池的狀態設置成SHUTDOWN,然後中斷所有沒有正在執行的任務。當調用這兩個方法中的任何一個后,isShutdown方法就會返回true,當所有任務都已經關閉后,調用isTerminaed方法會返回true。

  使用線程池時,需要從任務的性質(IO密集型還是CPU密集型或是混合型)、任務的優先級、任務的執行時常、任務的依賴性(是否依賴其他系統資源,如數據庫連接等)來綜合判斷,比如說,CPU密集型,就可以就可以配置N+1個線程個數,其中N為CPU核數,如果是IO密集型,則可以配置2*N個線程數;如果是混合型的任務,可以將其拆分成IO密集型和CPU密集型,但是如果兩個任務的執行時間相差較大,則沒有必要進行拆分;優先級不同的任務可以使用優先級隊列PriortyBlockingQueue來處理;依賴數據出等其它資源的線程池,比如說依賴數據庫,那麼就可以加大線程數量,因為在等待sql執行的時候,線程是處於空閑狀態;另外,最好使用有界隊列,因為無界隊列,因為有界隊列可以增加系統的穩定性和預警能力。

  對於線程的監控,還有以下幾個方法可以使用:

方法 描述
taskCount() 線程池需要執行的任務數量
completedTskCount 線程池運行過程中已經執行完畢的任務數量
IarestPoolSize 線程池中曾經創建過的最大線程數
getPoolSize 線程池的線程數量
getActiveCount 獲取活動的線程數

二、Exector框架

   在java中,是用線程來異步執行任務,java線程的創建與銷毀需要一定的開銷。如果我們為每一個任務創建一個線程的話,這些線程就會消耗大量的計算資源,會使處於高負荷的應用崩潰。

  在HotSpot虛擬機中,JAVA線程被一對一的映射為本地操作系統線程。JAVA線程啟動時會創建一個本地操作系統線程,當該JAVA線程終止時,這個操作系統線程也會被收回,操作系統會調用多有線程並將他們分配給可用的CPU。

 

   Executor框架的兩級調度模型如上圖所示,應用程序通過Executor控制上層的調度,而下層的調用由操作系統內核控制,將線程映射到硬件處理器上,下層的調用不受應用程序的控制。

   關於Executor的組成部分如下所示:

元素 描述
任務 包括被執行任務需要實現的接口Runnable和Callable接口
任務的執行 包括任務執行機制的核心接口Executor,以及繼承自Executor的ExecutorService接口。Executor接口有兩個關鍵的實現類實現了ExecutorService接口:ThreadPoolExecutor和ScheduledThreadPoolExecutor
異步計算的結果 包括接口Future和實現Future接口的FurureTask類

  Executor框架使用示意圖如下:

 

 

   如上圖所示,主線程首先創建實現Runnable或Callable接口的任務對象,然後把任務對象提交給ExecutorService執行,如果使用的是submit提交,執行完畢后將返回一個實現Future接口的對象,最後,主線程可以執行FutureTask.get()方法來獲取返回值;主線程也可以調用FutureTask.cancel()方法來取消此任務的執行。

  Executor框架的成員如下:

成員 描述 子類 描述
ThreadPoolExecutor

通常使用工廠類Executors來創建,Executors可以創建三種類型的ThreadPoolExecutor

固定線程數的FixedThreadPool

適用於為了滿足資源管理的需求,而需要限制當前線程數量的應用場景,它適用於負載比較重的應用。
單一線程的SingleThreadPool 適用於需要保證順序的執行各個任務,並且在任意時間點都不會有多個線程活動的場景。
根據需要創建線程的CacheThreadPool 這是一個無界的線程池,適用於執行很多短期異步任務的小程序,或者是負載比較輕的服務器。
ScheduledThreadPoolExecutor 通常使用工廠類Executors創建,Executors可以創建兩種類型的ScheduledThreadPoolExecutor 包含若干線程的ScheduledThreadPoolExecutor 適用於需要多個後台線程執行周期任務,同時為了滿足資源管理的需求而需要限制後台線程數量的應用場景。
只包含一個線程的SingleThreadScheduledExecutor                         適用於需要單個後台線程執行周期任務,同時需要保證順序的執行各個任務的場景。                                               
ForkJoinsPool

 newWorkStealingPool適合使用在很耗時的操作,但是newWorkStealingPool不是ThreadPoolExecutor的擴展,它是新的線程池類ForkJoinPool的擴展,但是都是在統一的一個Executors類中實現,由於能夠合理的使用CPU進行對任務操作(并行操作),所以適合使用在很耗時的任務中 

   
Future Future接口和實現了該接口的FutureTask類來表示異步計算的結果    
Runnable和Callable接口

Runnable和Callable接口的實現類,都可以被ThreadPoolExecutor、ScheduledThreadPool、ForkJoinThred執行;除了可以自己實現Callable接口外,我們還可以使用工廠類Executors來把一個Runnable包裝成一個Callable

   

 

ThreadPoolExecutor詳解

1、ThreadPoolExecutor

  (1)FixedThreadPool

  構造函數如下:

public static ExecutorService newFixedThreadPool(int nThreads) {
    return new ThreadPoolExecutor(nThreads, nThreads,
                                  0L, TimeUnit.MILLISECONDS,
                                  new LinkedBlockingQueue<Runnable>());
}

  構造函數中,核心線程數和最大線程數一致,keepAliveTime為0,隊列使用的是無界阻塞隊列LinkedBlockingQueue(最大值是Integer.MAX_VALUE);

  核心線程數和最大線程數保持一致,表明:如果隊列滿了之後,不會再創建新的線程;

  keepAliveTime為0,表明:如果運行線程數大於核心線程數時,如果線程執行完畢,空閑線程立刻被終止;

  使用無界阻塞隊列,表明:當運行線程到達核心線程數時,不會再創建線程,只會將任務加入阻塞隊列;因此最大線程數參數無效;因此keepAliveTime參數無效;且不會拒絕任務(既不會執行包和策略)

  (2)SingleThreadExecutor

  構造函數如下:

public static ExecutorService newSingleThreadExecutor() {
    return new FinalizableDelegatedExecutorService
        (new ThreadPoolExecutor(1, 1,
                                0L, TimeUnit.MILLISECONDS,
                                new LinkedBlockingQueue<Runnable>()));
}

  構造函數中,核心線程數和最大線程數均為1,keepAliveTime為0,隊列使用的是無界阻塞隊列LinkedBlockingQueue(最大值是Integer.MAX_VALUE)

  除了固定了核心線程數和最大線程數為1外,其餘的參數均與FixedThreadPool一致,那麼就是只有一個線程會反覆循環從阻塞隊列中獲取任務執行

  (3)CacheThreadPool

  構造函數如下:

public static ExecutorService newCachedThreadPool() {
    return new ThreadPoolExecutor(0, Integer.MAX_VALUE,
                                  60L, TimeUnit.SECONDS,
                                  new SynchronousQueue<Runnable>());
}

  構造函數中,核心線程數為0,最大線程數為Integer.MAX_VALUE,意味着無界,keepAliveTime為60秒,阻塞隊列使用沒有存儲空間的SynchronousQueue

  核心線程數為0,最大線程數為無界,表明:只要隊列滿了,就會創建新的線程放入線程池

  使用沒有存儲空間的SynchronousQueue表明:線程提交的速度高於線程被消費的速度,那麼線程會被不斷的創建,最終會因為線程創建過多而耗盡CPU和內存資源

2、ScheduledThreadPoolExecutor

  ScheduledThreadPoolExecutor的運行機制如下:

   (1)當調用ScheduledTreadPoolExecutor的scheduleAtFixedRate()方法或者scheduleWithFixedDelay()方法時會向ScheduledThreadPoolExecutor的DelayQueue添加一個實現了RunnableScheduledFuture接口的ScheduledFutureTask

  (2)線程池中的線程從DelayQueue中獲取ScheduledFutureTask,然後執行任務。

  ScheduledFutureTask主要包含以下三個成員變量

成員變量 描述
long time 表示這個任務要被執行的時間
long sequenceNumber 表示該任務被添加到ScheduledThreadPoolExecutor中的序號
long period 表示任務執行的間隔周期

  DelayQueue封裝了一個PriorityQueue,當添加任務時,這個PriorityQueue會對隊列中的ScheduledFutureTask進行排序,time最小的在最前面(最先被執行),如果time一致,就比較sequenceNumber,sequenceNumber小的排在前面。

  當線程執行任務時,先從DelayQueue隊列中獲取已經到期的任務(time大於當前時間),然後執行該任務,執行完畢后,根據任務的執行周期,修改任務下次的執行時間time,並重新將任務添加到DelayQueue

 

 

  FutureTask詳解

  Future接口和實現該接口的FutureTask類,代表異步計算的結果。

  FutureTask的使用方法是將其交給Executor執行,也可以通過ExecutorService.submit()方法返回一個FutureTask,然後執行FutureTask.get()方法或FutureTask.cancel()方法,除此之外,還可以但是使用FutureTask。

  FutureTask有三種狀態:未啟動(FutureTask.run()沒有被執行之前的狀態)、已啟動(FutureTask.run()方法執行過程中)、已完成(FutureTask.run()方法執行完成或被取消),這三種狀態的流轉如下圖所示:

 

  FutureTask的實現是基於AQS(AbstractQueuedSynchrouizer)來實現的,之前已經說過,每一個基於AQS實現的同步器都會至少包含一個acquire操作和至少一個release操作。AQS被作為模板方法模式的基礎類提供給FutureTask的內部子類Sync實現了AQS的tryAcquireShared(int)方法和tryReleaseShared(int)方法,Sync通過這兩個方法來檢查和更新同步狀態。

  FutureTask涉及示意圖如下圖所示:

 

  如上圖所示,FutureTask.get()方法會調用AQS的acquireSharedInterruptibly(int)方法,該方法首先會回調在子類Sync中的tryAcquireShared()方法來判斷acquire操作是否成功(state狀態狀態是否為執行完成RAN或取消狀態CANCELED&runner不為null),如果成功則get()方法立刻返回,如果失敗則到線程等待隊列中去等待其他線程執行release操作;當其他線程執行release操作(比如FutureTask.run()或FutureTask.cancel())喚醒當前線程后,當前線程再次執行tryAcquireShared()將返回正值1,當前線程將離開線程等待隊列並喚醒它的後繼線程。

  Run方法執行過程如下:

  執行在構造函數中指定的任務(Callable.call()),然後以原子方式來更新狀態(調用AQS.compareAndSetState(int expect, int update),設置state的狀態為RAN),如果這個原子操作成功,就設置代表計算結果的變量result的值為Callable.call()的返回值,然後調用AQS.release(int)。

  AQS.rease首先會調用子類Sync中實現的tryReleaseShared方法來執行release操作(設置運行任務的線程為null,然後返回false),然後喚醒等待隊列中的第一個線程。

  最後調用Future.done()方法。

 

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

【其他文章推薦】

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

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

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

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

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

面向對象再探究

在還不清楚怎樣面向對象?一文中,已經簡單介紹了面向對象的基本思想和三大特性,但是不夠詳細。本文再來具體探究一下面向對象。

1. 概述

1.1. 面向過程編程

面向過程編程(Procedure Oriented Programming,POP)是一種以過程為中心的編程思想,開發人員在解決問題時更專註於過程。

當我們遇到一個問題時,只需要分析出第1步要做什麼、第2步要做什麼……直到解決問題,然後把這些步驟一步步地實現即可。

比如,我現在要編寫一個面向過程的程序來模擬「我要讓我的好朋友行小觀去幫我買瓶水」這個問題,如下:

  1. 給行小觀10塊錢
  2. 告訴行小觀去哪買水
  3. 行小觀找到我要的那瓶水
  4. 付錢,找零錢
  5. 把水帶回來給我

在從「給行小觀錢」到「行小觀把水給我」的整個過程,我和行小觀都專註於完成每一個步驟(事件),行小觀只是一個執行我事先描述好的步驟的無思想的工具人而已。

如果我還有其他問題需要行小觀幫忙,那麼我就還得把如何完成這些問題的詳細步驟全都告訴他,這麼麻煩那我還找別人幫忙干什麼呢?還不如我自己去做。而且有些問題我自己也不會做,那怎麼辦?所以行小觀並不是一個合格的工具人。

1.2. 面向對象編程

與面向過程編程不同,面向對象編程(Object Oriented Programming,OOP)是一種以對象為中心的編程思想,對象是現實世界中的一個個事物的實體。

對象包含了用戶可以使用(公開)的功能部分和對用戶隱藏的實現部分。

在開發過程中,我們可以使用功能部分來解決問題,但是並不關心功能是怎樣實現的。

還是上面的那個買水的例子,使用面向對象來實現:

  1. 我給行小觀10塊錢,讓他幫我買瓶水
  2. 行小觀把水和零錢帶回來給我

在這個例子中,我只需要給行小觀錢,然後他就能幫我買水。我相信我請行小觀有「買水的能力」,他一定能幫我買到水。至於去哪買?怎麼買?行小觀自己知道,我並不關心他是怎麼買到水的,因為我的目的很簡單:「我想要一瓶水」。

如果我有其他問題需要行小觀幫忙,無論這些問題我會不會做,直接告訴他就好了,他會幫我完成。

我只專註於問題本身,具體的操作我並不關心。現在行小觀是一個合格的工具人了。

2. 類和對象

2.1. 二者之間的關係

這裏通過一個大家耳熟能詳的神話——女媧造人,來說明類和對象之間的關係。

相傳女媧以泥土仿照自己摶土造人,創造並構建人類社會。

在這個神話里,「女媧」是一個藍圖、模板,「人」是依據該藍圖被創造出來的個體。

「女媧」可以看做類(Class),「人」可以看做對象(Object)。

跳出神話,來到真實世界。

我們目能所及的事物都可以看做是「對象」,比如說你用的桌子、坐的椅子、玩的電腦、養的狗……這些一個個真實存在,你能摸到的物品都是對象。

狗有千千萬……高的、矮的、胖的、瘦的、黑色的、白色的等各不相同,但是總能在這些不同的狗之中找到相同的特性,這些不同品種的狗我們把它統稱為「狗」。「狗」即為類,而我們養的真實存在的狗為對象。

總結一下:

  • 類是對一類具有共同特徵的事物的抽象,是一類事物的統稱,是一個抽象概念(比如“人類”這個名詞)。

  • 對象是這類事物相對應的具體存在的實體,是一個具體存在的事物(比如“行小觀”這個具體的人)。

  • 類是創建單個對象時的藍圖、模板。

2.2. 類

當我們說到「狗」這個類的時候,會很自然地想到和狗相關的一些特點和習性。

比如,名字、品種、顏色、年齡等,這些是屬性。

還有,吠叫、看門等,這些是行為。

一個類包括了屬性和行為,行為可以操縱屬性。

對應到代碼中,屬性即為成員變量,行為即為成員方法,成員方法可以操縱成員變量。

下面是一個具體的類:

程序2-1
/**
 * 狗類
 * @author Xing Xiaoguan
 */

public class Dog {
    //屬性——成員變量
    String name;
    int age;
    int legs;

    //行為——成員方法
    //行為——吠叫
    public void say() {
        System.out.println("我是" + name + "汪汪汪");
    }

    //行為——看門
    public void watchDoor() {
        System.out.println("趕走陌生人");
    }
}

2.3. 對象

類是一個抽象概念,而對象則是一個具體的實例。

以狗為例,我們養的不可能是「一類狗」,而是在和一隻「具體的狗」玩耍,比如說哮天犬。

回到女媧(類)造人(對象)這個神話中,人是以女媧為模板被造出來的,女媧造人的過程,即由類構造對象的過程稱為創建類的實例(instance)。

程序2-2
public static void main(String[] args) {
   Dog dog = new Dog();//創建類的實例,對象dog
   dog.name = "哮天犬";
   dog.age = 2;
   dog.legs = 4;
   dog.say();
}

對於被創建出來的對象而言,它們都不一樣,每一個特定的對象(實例)都有一組特定的屬性值(成員變量),這些屬性值的集合就是這個對象的當前狀態,只要對象使用者通過行為(成員方法)向該對象發送消息,這些狀態就可能被改變。

上面這句話怎麼理解?

現在有兩隻狗(兩個對象):哮天犬和哮地犬,這兩個對象的名字、年齡等屬性不同,即當前狀態不同。每隻狗都有一個行為:可以「每過一年,年齡增長1歲」,當通過該行為向哮天犬發送消息時,哮天犬的狀態就被改變了。

可以看出,一個對象由狀態(state)和行為(behavior)組成,對象在成員變量中存儲狀態,通過成員方法公開其行為。

研究一個對象,我們要去關注它處於什麼狀態?具有哪些行為?

對象的三個主要特性:

  • 對象的行為——可以對對象施加哪些操作(方法)?
  • 對象的狀態——當施加那些方法時,對象如何響應?
  • 對象標識——如何辨別具有相同狀態與行為的不同對象?

3. 三大特性

3.1. 封裝

封裝(encapsulation)是Java面向對象的三大特行之一。

一個對象具有屬性和行為,封裝把其屬性和行為組合在了一起,但是為什麼需要封裝?

上文已經介紹了,一個對象由其狀態和行為組成。我們回看程序2-1,雖然這段代碼錶示出了Dog類,但是有一個很大的問題:創建出來的Dog對象的狀態很容易被改變。

比如我們可以直接修改程序2-2中的對象的狀態:

dog.name = "哮地犬";
dog.legs = 3;

你的狗的名字被不懷好意的人給改了,腿也少了一條!這種事情是危險、可怕的!

我們希望別人能夠“知道”自己的狗叫什麼名字、有幾條腿等信息,但是又要防止不懷好意的人隨便“傷害”自己的狗,怎麼辦呢?答案是封裝!

將對象的狀態和行為封裝起來,使用該對象的用戶只能通過對象本身提供的方法來訪問該對象的狀態。前面也說過,對象的當前狀態可能會改變,但是這種改變不是對象自發的,必須通過調用對象本身提供的方法來改變。如果不通過調用方法就能改變對象狀態,只能說明封裝性被破壞了。

換句話說,我們將對象的屬性(狀態)對外隱藏起來,這些狀態能否被訪問或修改,由對象自己來決定,決定的方式就是「給對象的使用者提供可調用的方法,用戶通過這些方法來進行訪問和修改」。

程序2-1可以改進為:

程序3-1
/**
 * 封裝后的狗類
 * @author Xing Xiaoguan
 */

public class Dog {
    private String name;
    private int age;
    private int legs;

    public String getName() {
        return name;
    }

    public int getAge() {
        return age;
    }

    public int getLegs() {
        return legs;
    }

    public void setName(String name) {
        this.name = name;
    }

    public void setAge(int age) {
        this.age = age;
    }

    //行為——吠叫
    public void say() {
        System.out.println("我是" + name + "汪汪汪");
    }

    //行為——看門
    public void watchDoor() {
        System.out.println("趕走陌生人");
    }

}

乍一看多了許多代碼,其實就多了兩個部分:

  1. 使用private修飾符來修飾成員變量,將其私有化,確保只能在本類內被訪問到,實現隱藏。
  2. 給成員變量提供了對應的訪問方法(getter方法)和修改方法(setter方法)。

其中我們給用戶能夠訪問或修改的成員變量都編寫上對應的setter或getter方法,如name。用戶不能訪問或修改的成員變量不寫setter或getter方法即可。

/**
 * 實例化一隻小狗,並和它玩
 */
public class Play {
    public static void main(String[] args) {
        Dog dog = new Dog();
        dog.setName("哮天犬");;
        dog.setAge(2);
        dog.say();
        dog.watchDoor();
    }
}

現在,用戶不能直接訪問或修改對象的狀態,必須通過提供的方法。對象並沒有給legs變量提供setter方法,這樣用戶就只能訪問狗有幾條腿,但是不能修改。狗腿被人“偷”了的事情也不會再發生了。

而且,當我們使用setter方法修改成員變量時,可以進行其他的操作,如錯誤檢查。比如設置age變量時:

//狗的平均壽命為10~15年,太大了不合理
public void setName(String name) {
    if (name > 0 && name < 30)
    	this.name = name;
}

現在,Dog類就比較安全了。

由上面的代碼可以看出,如果要訪問或修改成員變量,需要:

  • 成員變量是私有的
  • 一個公有的getter方法
  • 一個公有的setter方法

封裝還有一個優點就是:對外隱藏了具體實現,這樣的好處就是:我們可以修改內部實現,除了修改了該類的方法外,不會影響其他代碼。

封裝使對象對外變成了一個“黑箱”,用戶只會使用,但不清楚內部情況。

前面買水的例子也體現了封裝思想:行小觀買水的方式有很多,走路去、騎車去、甚至找比人幫忙,但是他改變買水的方式並不會對我造成影響。

3.2. 繼承

生活中除了狗,還有許多其他動物,比如貓、兔子……

程序3-2
/**
 * 貓類
 * @author Xing Xiaoguan
 */

public class Cat {
    private String name;
    private int age;
    private int legs;
    private String owner;//主人

    //getters and setters……

    //行為——叫
    public void say() {
        System.out.println("我是" + name + "喵喵喵");
    }

    //行為——捉老鼠
    public void catchMouse() {
        System.out.println("捉到一隻老鼠");
    }
}
程序2-5
/**
 * 兔子類
 * @author Xing Xiaoguan
 */

public class Rabbit {
    private String name;
    private int age;
    private int legs;
    private String home;//住址

	//getters and setters……

    //行為——叫
    public void say() {
        System.out.println("我是" + name + "咕咕咕");
    }

    //行為——搗葯
    public void makeMedicine() {
        System.out.println("在" + home + "搗葯");
    }
}

寫完這兩個類,發現有許多屬性和方法是重複的,如果需要再寫100個動物的類,那得

這些動物形態各異,但是它們都被統稱為“動物”,也就是說,我們仍能在它們身上找出相同的特點,比如它們都有名字、年齡、腿、能發出聲音……

前面介紹類的時候已經說了,類是對一類具有共同特徵的事物的抽象,所以此時我們還能從狗、貓、兔子這些類中再抽象出一個類——動物類。

程序3-3
/**
 * 動物類
 * @author Xing Xiaoguan
 */

public class Animal {
    private String name;
    private Integer age;
    private Integer legs;

    public void say() {
        System.out.println("我是"+name+"發出聲響");
    }
    //setters and getters……
}

這個更抽象的類就是父類,而狗、貓、兔子類是子類。子類可以使用extends關鍵字繼承父類的屬性和方法,這意味着相同的代碼只需要寫一遍。

程序3-4
/**
 * 狗類繼承父類
 * @author Xing Xiaoguan
 */

public class Dog extends Animal{

    //行為——吠叫
    public void say() {
        System.out.println("我是" + getName() + "汪汪汪");
    }

    //行為——看門
    public void watchDoor() {
        System.out.println("趕走陌生人");
    }
}
程序3-5
/**
 * 貓類繼承父類
 * @author Xing Xiaoguan
 */

public class Cat extends Animal {
    private String owner;

    public String getOwner() {
        return owner;
    }

    public void setOwner(String owner) {
        this.owner = owner;
    }

    //行為——喵喵叫
    public void say() {
        System.out.println("我是" + getName() + "喵喵喵");
    }

    //行為——捉老鼠
    public void catchMouse() {
        System.out.println("捉到一隻老鼠");
    }
}
程序3-6
/**
 * 兔子類繼承父類
 * @author Xing Xiaoguan
 */

public class Rabbit extends Animal {

    private String home;

    public String getHome() {
        return home;
    }

    public void setHome(String home) {
        this.home = home;
    }

    //行為——叫
    public void say() {
        System.out.println("我是" + getName() + "咕咕咕");
    }

    //行為——搗葯
    public void makeMedicine() {
        System.out.println("在" + home + "搗葯");
    }
}

觀察上面的子類和父類,可以發現:

  1. 子類不用再重複父類中已有的屬性和方法,能通過繼承獲取到。
  2. 子類比父類的功能更加豐富,子類可以擁有自己的屬性和方法
  3. 子類如果感覺繼承自父類的方法不合適,可以重寫父類的方法的實現過程,注意返回值和形參不能改變。

使用繼承的好處:

  • 提高代碼的復用性,不用再寫那麼多重複代碼了。
  • 使代碼便於維護,當我們需要修改某個公用方法時,不需要一個個類去修改,只需修改父類的該方法即可。

3.3. 多態

看下面一段代碼,我們來直觀體驗什麼是多態。

程序3-7
public static void main(String[] args) {
    Animal dog = new Dog();
    dog.setName("哮天犬");
    dog.say(dog.getName());
    
    Animal cat = new Cat();
    cat.setName("加菲貓");
    cat.say();

    Animal rabbit = new Rabbit();
    rabbit.setName("玉兔");
    rabbit.say();
}

運行,輸出:
我是哮天犬汪汪汪
我是加菲貓喵喵喵
我是玉兔咕咕咕
  1. Dog、Cat和Rabbit都是繼承了Animal父類。
  2. Dog、Cat和Rabbit都重寫了Animal的say(String name)方法。
  3. 在創建實例對象時,我們使用父類引用指向子類的對象。

使用多態應當注意一下幾點:Animal dog = new Dog()

  • 在多態中,子類對象只能調用父類中定義的方法,不能調用子類中獨有的方法。

    比如dog不能調用watchDoor()方法。

  • 在多態中,子類可以調用父類的所有方法。

  • 在多態中,子類如果重寫了父類的方法,那麼子類調用該方法時,調用的是子類重寫的方法。

在上面的代碼中,狗、貓、兔子對象都運行了say方法,但是輸出不同。

由此看出,不同的對象的同一行為具有不同的表現形式,這就是多態。

在實際的本例中,我們可以理解為:動物Animal,他們都會叫出聲。如果是狗,則叫的是汪汪汪;如果是貓,則叫的是喵喵喵;如果是兔子,則叫的是咕咕咕。

4. 總結

面向對象思想使我們在編程更加貼近現實世界,類是現實世界的抽象,對象則是一個個具體的事物。

封裝使一個個具體的事物更加獨立,繼承則使一些類似的事物之間具有聯繫,而多態則使事物的行為更加靈活多樣。

面向對象編程提高了軟件的重用性、靈活性、擴展性。

如有錯誤,還請指正

參考資料:

  • The Java Tutorials

  • 維基百科

  • 百度百科

  • Java核心技術 卷1

文章首發於公眾號「行人觀學」

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

【其他文章推薦】

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

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

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

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

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

接口調用超時的實現原理

  平常開發過程中,如果涉及到RPC調用,對於服務調用方和服務提供方,都是可以設置接口超時時間的。以調用方為例,調用方需要調用遠程的一個接口,為了保證服務的質量,一般會設置調用接口的超時時間,比如將調用接口的超時時間設置為1秒,當調用遠程接口后,經過1秒還未拿到結果,那麼就認為是超時了,調用方就不會繼續等待服務提供方返回結果,而是直接拋出一個SocketTimeOutException。

  其實不僅僅是RPC接口調用需要設置超時時間,數據庫、緩存這些一樣的,一般都會設置超時時間,不能讓程序無休止的等待下去。

  那麼問題來了!!應用或者說框架,是如何設置超時時間的呢?我們設置超時時間為1秒,那麼程序是怎麼保證1秒后就停止調用了呢?這也就是本文要介紹的,接口調用設置超時時間的原理。

  本文將以設置調用數據庫超時為例,介紹設置超時時間是怎麼實現的,明白這個原理后,其他比如RPC調用、緩存調用的超時原理也就明白。

 

CallUtils-帶有超時的執行工具類

  下面是一個CallUtils,包含一個線程池和execute方法,execute方法中默認超時時間為1秒。

package cn.ganlixin.util;
import java.util.concurrent.*;

/**
 * 帶有超時的任務執行器
 */
public class CallUtils {

    /**
     * 用於執行任務的線程池
     */
    private static ExecutorService executorService = Executors.newFixedThreadPool(5);

    /**
     * 執行任務,並且超時時間為1秒
     *
     * @param callable 需要執行的任務
     * @param <T>      任務執行完畢后返回值類型
     * @return 任務結果
     */
    public static <T> T execute(Callable<T> callable) {

        // 提交任務
        Future<T> future = executorService.submit(callable);

        try {
            return future.get(1, TimeUnit.SECONDS);
        } catch (InterruptedException | ExecutionException | TimeoutException e) {
            e.printStackTrace();
        }

        return null;
    }
}

  

UserService-將調用數據庫

  UserService使用CallUtils來執行數據庫操作,因為CallUtils設置有調用超時:

package cn.ganlixin.service;

import cn.ganlixin.dao.UserDAO;
import cn.ganlixin.model.User;
import cn.ganlixin.util.CallUtils;

import java.util.List;

public class UserService {

    private UserDAO userDAO = new UserDAO();

    public List<User> getAllUser() {
        return CallUtils.execute(() -> userDAO.getAllUserFromDB());
    }
}

  

UserDAO-耗時的數據庫操作

  userDAO中,作為測試,只提供了一個getAllUserFromDB方法,休眠2秒來模擬數據庫操作,因為UserService使用CallUtils來調用該方法,CallUtils的超時時間為1秒,所以該數據庫操作必定會超時:

package cn.ganlixin.dao;
import cn.ganlixin.model.User;
import java.util.List;
import java.util.concurrent.TimeUnit;

public class UserDAO {

    /**
     * 從DB獲取全量user列表
     */
    public List<User> getAllUserFromDB() {
        try {
            // 模擬數據庫操作,耗時2秒
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return null;
    }
}

  

進行測試

  創建測試類,測試UserService的getAllUser方法,因為getAllUser方法中使用CallUtils來調用數據庫操作,CallUtils的超時時間為1秒,而數據庫操作需要2秒,所以getAllUser方法必然會超時。

package cn.ganlixin.test;
import cn.ganlixin.service.UserService;
import org.junit.Test;

public class TestUserService {

    public UserService userService = new UserService();

    @Test
    public void testGetAllUser() {
        userService.getAllUser();
    }
}

  執行測試,輸出如下:

  

 

 

總結

  本文演示了接口超時調用的原理,實現接口調用超時,無非是通過將任務提交到線程池后,使用future.get,設置超時時間即可。

  上面的代碼很多細節都不太規範,比如涉及到數據庫的超時,應該是數據庫連接池的超時配置,而我在演示時是直接使用CallUtils來替代了,但是明白這個原理就OK。

 

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

【其他文章推薦】

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

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

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

※南投搬家前需注意的眉眉角角,別等搬了再說!

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

IDEA版本彩虹屁插件idea-rainbow-fart,一個在你編程時瘋狂稱讚你的 IDEA擴展插件

緣起

是否聽說過程序員鼓勵師,不久前出了一款vscode的插件rainbow-fart,可以在寫代碼的時候,匹配到特定關鍵詞就瘋狂的拍你馬屁。

vscode的下載嘗試過,但是作為日常將IDEA作為主力生產工具的同學來說,如何體驗呢? 於是假期花了一點時間,寫了一個idea版本的插件idea-rainbow-fart。

使用說明

默認使用中文語音包,可以在setting里設置

打開設置:

選擇第三方語音包:

可以到 https://github.com/topics/vscode-rainbow-fart 查找語音包。

點擊確定生效:

原理

沒啥原理,就是一款簡單的idea插件,對沒寫過插件的我來說,需要先看下官方文檔,基本上看下面這一篇就OK:

https://www.jetbrains.org/intellij/sdk/docs/basics/getting_started.html

讀取語音包

先來看下語音包的設計:


{
  "name": "KugimiyaRie",
  "display-name": "KugimiyaRie 釘宮理惠 (Japanese)",
  "avatar": "louise.png",
  "avatar-dark": "shana.png",
  "version": "0.0.1",
  "description": "傲嬌釘宮,鞭寫鞭罵",
  "languages": [
    "javascript"
  ],
  "author": "zthxxx",
  "gender": "female",
  "locale": "jp",
  "contributes": [
    {
      "keywords": [
        "function",
        "=>"
      ],
      "voices": [
        "function_01.mp3",
        "function_02.mp3",
        "function_03.mp3"
      ]
    },
	...
	]
}

對Java來說,定義兩個bean類,解析json即可:

/**
     * 加載配置
     */
    public static void loadConfig() {
        try {
            //
            FartSettings settings = FartSettings.getInstance();
            if (!settings.isEnable()) {
                return;
            }
            String json = readVoicePackageJson("manifest.json");
            Gson gson = new Gson();
            Manifest manifest = gson.fromJson(json, Manifest.class);
            // load contributes.json
            if (manifest.getContributes() == null) {
                String contributesText = readVoicePackageJson("contributes.json");
                Manifest contributes = gson.fromJson(contributesText, Manifest.class);
                if (contributes.getContributes() != null) {
                    manifest.setContributes(contributes.getContributes());
                }
            }
            Context.init(manifest);

        } catch (IOException e) {
        }
    }

監控用戶輸入

自定義一個Handler類繼承TypedActionHandlerBase即可,需要實現的方法原型是:

public void execute(@NotNull Editor editor, char charTyped, @NotNull DataContext dataContext)

chartTyped就是輸入的字符,我們可以簡單粗暴的將這些組合到一起即可,用一個list緩存,然後將拼接后的字符串匹配關鍵詞。

     private List<String> candidates = new ArrayList<>();

    @Override
    public void execute(@NotNull Editor editor, char charTyped, @NotNull DataContext dataContext) {
        candidates.add(String.valueOf(charTyped));
        String str = StringUtils.join(candidates, "");
        try {
            List<String> voices = Context.getCandidate(str);
            if (!voices.isEmpty()) {
                Context.play(voices);
                candidates.clear();
            }
        }catch (Exception e){
            // TODO
            candidates.clear();
        }

        if (this.myOriginalHandler != null) {
            this.myOriginalHandler.execute(editor, charTyped, dataContext);
        }
    }

匹配關鍵詞更簡單,將讀取出來的json,放到hashmap中,然後遍歷map,如果包含關鍵詞就作為語音候選:

    public static List<String> getCandidate(String inputHistory) {


        final List<String> candidate = new ArrayList<>();

        FartSettings settings = FartSettings.getInstance();
        if (!settings.isEnable()) {
            return candidate;
        }
        if (keyword2Voices != null) {
            keyword2Voices.forEach((keyword, voices) -> {
                if (inputHistory.contains(keyword)) {
                    candidate.addAll(voices);
                }
            });
        }
        if (candidate.isEmpty()) {
            candidate.addAll(findSpecialKeyword(inputHistory));
        }
        return candidate;
    }

如果找到候選,就播放。

播放

為了防止同時播放多個語音,我們用一個單線程線程池來搞定。播放器使用javazoom.jl.player.Player

  /**
     * play in a single thread pool
     */
    static ExecutorService playerTheadPool;
	static {
        ThreadFactory playerFactory = new ThreadFactoryBuilder()
                .setNameFormat("player-pool-%d").build();
        playerTheadPool = new ThreadPoolExecutor(1, 1,
                0L, TimeUnit.MILLISECONDS,
                new LinkedBlockingQueue<>(1024), playerFactory, new ThreadPoolExecutor.AbortPolicy());
    }
	
	public static void play(List<String> voices) {

        FartSettings settings = FartSettings.getInstance();
        if (!settings.isEnable()) {
            return;
        }
        // play in single thread
        playerTheadPool.submit(() -> {
            String file = voices.get(new Random().nextInt() % voices.size());
            try {
                InputStream inputStream = null;
                if (StringUtils.isEmpty(settings.getCustomVoicePackage())) {
                    inputStream = Context.class.getResourceAsStream("/build-in-voice-chinese/" + file);
                } else {
                    File mp3File = Paths.get(settings.getCustomVoicePackage(), file).toFile();
                    if (mp3File.exists()) {
                        try {
                            inputStream = new FileInputStream(mp3File);
                        } catch (FileNotFoundException e) {
                        }
                    } else {
                        return;
                    }
                }
                if (inputStream != null) {
                    Player player = new Player(inputStream);
                    player.play();
                    player.close();
                }
            } catch (JavaLayerException e) {
            }
        });
    }

end

開源地址: https://github.com/jadepeng/idea-rainbow-fart

歡迎大家點贊!

作者:Jadepeng
出處:jqpeng的技術記事本–http://www.cnblogs.com/xiaoqi
您的支持是對博主最大的鼓勵,感謝您的認真閱讀。
本文版權歸作者所有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接,否則保留追究法律責任的權利。

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

【其他文章推薦】

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

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

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

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

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

BMW與日產將在南非建充電站 可望採再生能源供電

據路透社報導,BMW集團和日產汽車計畫在南非建設快速充電站網路,以推動當地電動車(含純電動車和插電式混動車)的銷量增長。   兩家公司表示,充電站的建設工作將從今年開始,並將持續至2017年,屆時BMW和日產旗下的電動車均可以在充電站進行充電,另外兩家公司還將在部分地區的充電站配備使用交流電的充電器。   目前南非正面臨電力供應短缺的困擾,不過BMW和日產稱充電站建設計畫不會受到影響。日產發言人Veralda Schmidt稱,未來兩家公司還可能採用可再生能源解決充電站供電問題。

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

【其他文章推薦】

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

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

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

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

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

比亞迪與英國ADL聯手打造歐洲最大電動公車隊

比亞迪於倫敦時間7月29日上午宣佈將同亞歷山大•鄧尼斯有限公司(ADL)合作,將投資1900萬英鎊(約合人民幣1.8億元)共同打造由51輛單層電動公車組成的車隊,規模為全歐最大。

根據雙方合作框架協定,這個由51台零排放單層電動公車組成的車隊將採12公尺大巴車型,車體配有比亞迪的底盤和動力系統、車身由ADL生產,造型融合熱銷車型Enviro200的設計。這將是歐洲最大規模的全尺寸電動公車隊。比亞迪也將負責車隊的現場維修和保養服務。

倫敦交通局(TfL)在本月初宣佈,作為2020年前實現倫敦市所有單層公車的零排放目標的一部分,將會率先實現倫敦市中心連接滑鐵盧和維多利亞的兩條公交路線(507路和521路)電動化。負責統一售後服務倫敦公交運營商Go-Ahead確認,將把507和521公交路線上引進51台零排放的純電動大巴。Go-Ahead表示,希望這個革命性的車隊在2016年8月27日前全部投入運營。

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

【其他文章推薦】

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

※評比前十大台北網頁設計、台北網站設計公司知名案例作品心得分享

※智慧手機時代的來臨,RWD網頁設計已成為網頁設計推薦首選

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

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