標籤: 台北網頁設計

  • Jmeter系列(26)- 詳解 JSON 提取器

    Jmeter系列(26)- 詳解 JSON 提取器

    如果你想從頭學習Jmeter,可以看看這個系列的文章哦

    https://www.cnblogs.com/poloyy/category/1746599.html

     

    為什麼要用 JSON 提取器

    • JSON 是目前大多數接口響應內容的數據格式
    • 在接口測試中,不同接口之間可能會有數據依賴,在 Jmeter 中可以通過後置處理器來提取接口的響應內容
    • JSON 提取器是其中一個可以用來提取響應內容的元件

     

    JSON 提取器的應用場景

    1. 提取某個特定的值
    2. 提取多個值
    3. 按條件取值
    4. 提取值組成的列表

     

    JSON 提取器

    我們通過實際栗子去講述理論知識點

     

    JSON 提取器界面介紹

     

    字段含義

     

    字段 結果
    Apply to 應用範圍,選默認的 main sample only 就行了
    Names of created variables
    • 接收提取值的變量名
    • 多個變量用 ; 分隔
    • 必傳
    JSON Path expression
    • json path 表達式,用來提取某個值
    • 多個表達式用 ; 分隔
    • 必傳
    Match No.(0 for Random)
    • 取第幾個值,多個值用 ; 分隔
    • 0:隨機,默認
    • -1:所有
    • 1:第一個值
    • 非必傳
    Compute concatenation var(suffix_ALL)
    • 如果匹配到多個值,則將它們都連接起來,不同值之間用 , 分隔
    • 變量會自動命名為 <variable name>_ALL 
    Default Values
    • 缺省值,匹配不到值的時候取該值,可寫error
    • 多個值用 ; 分隔
    • 非必傳

     

    入門栗子 

    栗子的前提

    這個栗子,我都會以這個地址的接口來完成 JSON 提取器的實戰慄子,大家可以註冊個賬號玩一玩哦

    http://api.yesapi.cn/docs.php?keyword=%E4%BC%9A%E5%91%98&channel=api

     

    測試計劃樹結構

    下面多個栗子都以這個測試計劃為基礎哦

     

    提取某個特定的值的栗子

    登錄接口響應

    登錄是執行其他接口的前置接口,所以要獲取用戶登錄后的 token、uuid

     

    提取 token

    相對路徑的方式

     

    提取 uuid

    絕對路徑的方式

     

    其他接口調用 token、uuid

     

    知識點

    • 提取某個特定值的方式有兩種:絕對路徑、相對路徑
    • 提其他接口可以通過 ${var} 這種格式,來獲取提取到的值

     

    綜合栗子

    • 上面講的是使用 JSON 提取器時的一個流程
    • 在實際項目中,接口的響應內容肯定是非常複雜的,而我們需要提取的值也是多樣化的,需要通過各種實戰慄子來講述清晰

     

    JSON 字符串

    這也是某個接口返回的響應內容,後面的栗子也是以這個 JSON 字符串為基礎來提取各種值

    感興趣也可以自己玩一玩:http://api.yesapi.cn/docs-api-App.User.GetList.html

    {
        "ret": 200,
        "msg": "V2.5.1 YesApi App.User.GetList",
        "data": {
            "total": 3,
            "err_msg": "",
            "err_code": 0,
            "users": [
                {
                    "role": "user",
                    "status_desc": "正常",
                    "reg_time": "2020-06-22 15:19:51",
                    "role_desc": "普通會員",
                    "ext_info": {
                        "yesapi_nickname": "",
                        "yesapi_points": 0
                    },
                    "uuid": "6D5EDCB459F0917A98106E07D5438C58",
                    "username": "fangjieyaossb",
                    "status": 0
                },
                {
                    "role": "user",
                    "status_desc": "正常",
                    "reg_time": "2020-06-22 14:27:17",
                    "role_desc": "普通會員",
                    "ext_info": {
                        "yesapi_nickname": "",
                        "yesapi_points": 0
                    },
                    "uuid": "0164DC0680F84DCE40D3DD4A36640ECA",
                    "username": "fangjieyaossa",
                    "status": 0
                },
                {
                    "role": "admin",
                    "status_desc": "正常",
                    "reg_time": "2020-03-23 22:48:32",
                    "role_desc": "管理員",
                    "ext_info": {
                        "yesapi_nickname": "",
                        "yesapi_points": 0
                    },
                    "uuid": "079BF6BB82AFCFC7084F96AECAF0519F",
                    "username": "fangjieyaoss",
                    "status": 0
                }
            ]
        }
    }

     

    提取單個值

    Jsonpath 結果
    $.data.total 2
    $..total 2
    $..users[0].role user
    $..uuid 079BF6BB82AFCFC7084F96AECAF0519F
    $.data.users[0].ext_info.yesapi_points 0

     

    重點

    • 如果匹配到多個值(像 $..uuid ),也只能提取到一個值
    • 如果想提取匹配到的所有 uuid,可以設置為 -1,結果如下圖

    還會告訴你匹配了多少個值 ${uuid_matchNr} ,記住,調用變量時,不再是 ${uuid} 而是 ${uuid_1} 、 ${uuid_2} 

     

    利用切片提取單個值

    和 Python  切片一樣的原理

    Jsonpath 結果
    $..users[2] 第三個 users
    $..users[-2] 倒數第二個users
    $..users[0,1] 前面兩個users
    $..users[:2] 第一、二個users
    $..users[1:2] 第二個users
    $..users[-2:] 倒數兩個users
    $..users[1:] 第二個開始的所有users

     

    提取多個值

    • 四種寫法類似,選一種方法自己熟記即可
    • 重點:提取多個值,提取器的 Match No. 必須填 -1

     

    $.data.users[*].role

    提取所有 role 字段值

    [*] 表示取數組的所有元素

     

    $..users..role_desc

    提取所有 role_desc 字段值

     

    $..reg_time

    提取所有 reg_time 字段值

     

    $..[*].username

    提取所有 username 字段值

     

    按條件提取值

    有時候只需要提取某個特定條件下的參數值

     

    語法格式

    [?(expression)]

     

    栗子

    Jsonpath 結果
    $..users[?(@.uuid)] 提取 users 裡面包含 uuid 字段的記錄
    $..users[?(@.reg_time > ‘2020-06-01’)] 提取 reg_time 字段大於 2020-06-01 的記錄
    $..users[?(@.role_desc =~ /.*會員.*?/i)] 提取 role_desc 字段包含會員的記錄
    $..users[?(@.status == 0)] 提取 status 字段等於 0 的記錄

     

    @

    代表當前節點,像上面的四個栗子,@代表 users 這個列表字段

     

    =~

    • 後面跟正則表達式,如果想提取包含指定字符的值,可以使用此正則: /.*指定字符串.*?/i 
    •  i  代表大小寫不敏感

     

    提取數據指定字段的值的栗子

    提取 users 第一條記錄的 uuid、username 字段的值

    $..users[0].['uuid','username']

     

    測試結果

    new_1={"uuid":"6D5EDCB459F0917A98106E07D5438C58","username":"luojunjiessb"}

     

    勾選 Compute concatenation var 的栗子

    JSON 提取器

     

    測試結果

    uuid_1=6D5EDCB459F0917A98106E07D5438C58
    uuid_2=0164DC0680F84DCE40D3DD4A36640ECA
    uuid_3=079BF6BB82AFCFC7084F96AECAF0519F
    uuid_ALL=6D5EDCB459F0917A98106E07D5438C58,0164DC0680F84DCE40D3DD4A36640ECA,079BF6BB82AFCFC7084F96AECAF0519F
    uuid_matchNr=3

     

    一個 JSON 提取器有多個 Jsonpath 的栗子

    JSON 提取器

     

    測試結果

    uuid1_1=6D5EDCB459F0917A98106E07D5438C58
    uuid1_2=0164DC0680F84DCE40D3DD4A36640ECA
    uuid1_3=079BF6BB82AFCFC7084F96AECAF0519F
    uuid1_matchNr=3
    uuid2_1=6D5EDCB459F0917A98106E07D5438C58
    uuid2_2=0164DC0680F84DCE40D3DD4A36640ECA
    uuid2_3=079BF6BB82AFCFC7084F96AECAF0519F
    uuid2_matchNr=3

     

    知識點

    • 如果有多個 Jsonpath 的時候,每個字段都必填值,且字段值的數量要一致(像上圖,每個字段都填了兩個值)
    • 勾不勾 Compute concatenation var 都行
    • 字段值數量不一致則無法提取值

     

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

    【其他文章推薦】

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

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

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

    ※超省錢租車方案

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

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

  • Flutter學習筆記(36)–常用內置動畫,Flutter學習筆記(36)–常用內置動畫

    Flutter學習筆記(36)–常用內置動畫,Flutter學習筆記(36)–常用內置動畫

    如需轉載,請註明出處:Flutter學習筆記(36)–常用內置動畫

    Flutter給我們提供了很多而且很好用的內置動畫,這些動畫僅僅需要簡單的幾行代碼就可以實現一些不錯的效果,Flutter的動畫分為補間動畫和基於物理的動畫,基於物理的動畫我們先不說。

    補間動畫很簡單,Android裏面也有補間動畫,就是給UI設置初始的狀態和結束狀態,經過我們定義的一段時間,系統去幫助我們實現開始到結束的過渡變化,這就是補間動畫。

    今天我們要看的Flutter的內置動畫就是補間動畫,根據Flutter提供的動畫組件,我們去設置初始、結尾的狀態,並且定義一下這個變化過程所需要的時間,再經過系統的處理(其實就是setState())來達到動畫的效果。

    接下來我們會寫一下常用的內置動畫組件,並且提供一下動畫效果的gif,方便大家更直觀的去理解。

    • AnimatedContainer

    看到Container我們就會知道這是一個帶有動畫屬性的容器組件,這個組件可以定義大小、顏色等屬性,那麼我們是不是就可以給這個組件設置初始和結束的大小及顏色的屬性值,然後通過系統來幫助我們來補足中間過程的動畫呢?

    答案是可以的,下面看一下demo和動畫效果:

    class _MyHomePageState extends State<MyHomePage> {
      double _width = 100.0;
      double _height = 100.0;
      Color _color = Colors.red;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: AnimatedContainer(
            width: _width,
            height: _height,
            duration: Duration(seconds: 2),
            color: _color,
            curve: Curves.bounceInOut,
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(() {
                _width = 300.0;
                _height = 300.0;
                _color = Colors.green;
              });
            },
            tooltip: 'Increment',
            child: Icon(Icons.adjust),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }

     

    demo很簡單,就是先定義好組件初始的大小和顏色,點擊按鈕,在按鈕事件裏面去更改大小和顏色的屬性值。這裏唯一需要特別說一下就是curve這個屬性。

    curve指的是動畫曲線?我開始的時候不理解這個動畫曲線是什麼意思,後來看了一組圖之後,豁然開朗。demo裏面curve我們用的是Curves.bounceInOut。如下:

     

    它其實就是一個非線性的動畫的變化形式(變化過程)也可以理解為就是一種函數,也不知道這麼說大家能不能理解。

    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_in_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_bounce_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_decelerate.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_sine.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quad.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_cubic.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quart.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_quint.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_expo.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_circ.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_back.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_sine.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quad.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_cubic.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quart.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_quint.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_expo.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_circ.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_in_out_back.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_sine.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quad.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_cubic.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quart.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_quint.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_expo.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_circ.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_ease_out_back.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_in_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_elastic_out.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_fast_out_slow_in.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_slow_middle.mp4}
    /// {@animation 464 192 https://flutter.github.io/assets-for-api-docs/assets/animation/curve_linear.mp4}

     

    這裡是每一種curve曲線的表現形式,大家可以看看,也可以在demo裏面多嘗試,或者可以看另一篇博客,有動畫曲線Curves 效果。

    • AnimatedCrossFade

    Flutter中文網:一個widget,在兩個孩子之間交叉淡入,並同時調整他們的尺寸。

    個人說明:CrossFade,故名思意,淡入淡出,AnimatedCrossFade組件包含兩個子widget,一個firstChild一個secondChild,這兩個組件根據狀態(我們自己定義的一個標識)改變狀態,

    一個淡入,一個淡出,同時改變大小或顏色等屬性。

    class _MyHomePageState extends State<MyHomePage> {
      bool _showFirst = true;
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: AnimatedCrossFade(
            firstChild: Container(
              width: 100,
              height: 100,
              color: Colors.red,
              alignment: Alignment.center,
              child: Text('firstChild'),
            ),
            secondChild: Container(
              width: 200,
              height: 200,
              color: Colors.green,
              alignment: Alignment.center,
              child: Text('secondChild'),
            ),
            duration: Duration(seconds: 3),
            crossFadeState:
                _showFirst ? CrossFadeState.showFirst : CrossFadeState.showSecond,
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(() {
                _showFirst = false;
              });
            },
            tooltip: 'Increment',
            child: Icon(Icons.adjust),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }

     

    • Hero

    Hero常用於頁面跳轉的過長動畫,比如電商App有一個商品列表,列表的每個item都有一張縮略圖,點擊會跳轉到詳情頁面,在Flutter中將圖片從一個路由飛到另一個路由稱為hero動畫,儘管相同的動作有時也稱為 共享元素轉換

    class _MyHomePageState extends State<MyHomePage> {
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(
            title: Text(widget.title),
          ),
          body: Hero(
            tag: 'heroTag',
            child: ClipOval(
              child: Image.asset(
                'images/banner.png',
                width: 60,
                height: 60,
                fit: BoxFit.cover,
              ),
            ),
          ),
          floatingActionButton: FloatingActionButton(
            onPressed: () {
              setState(() {
                Navigator.push(context, MaterialPageRoute(builder: (_) {
                  return new HeroPage();
                }));
              });
            },
            tooltip: 'Increment',
            child: Icon(Icons.adjust),
          ), // This trailing comma makes auto-formatting nicer for build methods.
        );
      }
    }

     

    詳情頁面:

    import 'package:flutter/material.dart';
    
    class HeroPage extends StatelessWidget {
      @override
      Widget build(BuildContext context) {
        return MaterialApp(
          title: 'HeroPage',
          home: Scaffold(
            appBar: AppBar(
              title: Text('HeroPage'),
            ),
            body: Center(
              child: GestureDetector(
                child: Hero(
                  tag: 'heroTag',
                  child: ClipOval(
                    child: Image.asset(
                      'images/banner.png',
                      width: 300,
                      height: 300,
                      fit: BoxFit.cover,
                    ),
                  ),
                ),
                onTap: () {
                  Navigator.pop(context);
                },
              ),
            ),
          ),
        );
      }
    }

     

    注:特彆強調一下,為了將兩個頁面的元素關聯起來,hero有個tag標識,前後兩個頁面的tag標識必須一樣,不然的話元素是關聯不起來的,也就意味着不會產生hero動畫。

    1.同級tag不允許相同。

    2.前後頁面想要有hero動畫,tag必須相同。

    3.前後關聯起來的hero組件,其各自內部的child組件不是必須一樣的,就是說前面的hero的子組件可以是image,後面的hero的子組件可以是image以外的其他組件。

    • AnimatedBuilder

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 3), vsync: this);
        _animation =
            new Tween(begin: 0.0, end: 200.0).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
            appBar: AppBar(
              title: Text(widget.title),
            ),
            body: AnimatedBuilder(
              animation: _animation,
              builder: (BuildContext context, Widget child) {
                return Center(
                  child: Container(
                    color: Colors.red,
                    width: _animation.value,
                    height: _animation.value,
                    child: child,
                  ),
                );
              },
            ));
      }
    }

    AnimationController:動畫控制器(定義動畫過程時長)。

    Animation:動畫變化區間值(也可以說是開始和結束的關鍵幀值),demo里定義的值為初始0,結束200。

    _animation.value:關鍵幀值是0和200,_animation.value的值為0–200之間連續變化的值(0-1-2-3-…-198-199-200)。

    • DecoratedBoxTransition

    Decortated可以給容器添加各種外觀裝飾,比如增加圓角、陰影等裝飾。DecoratedBox的動畫版本,可以給它的Decoration不同屬性使用動畫

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<Decoration> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 3), vsync: this);
        _animation = DecorationTween(
                begin: BoxDecoration(
                    borderRadius: BorderRadius.all(Radius.circular(0.0)),
                    color: Colors.red),
                end: BoxDecoration(
                    borderRadius: BorderRadius.all(Radius.circular(30.0)),
                    color: Colors.green))
            .animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: DecoratedBoxTransition(
              decoration: _animation,
              child: Container(
                width: 100,
                height: 100,
              ),
            ),
          ),
        );
      }
    }

    • FadeTransition

    透明度變化動畫,因為透明度也是在0-1之間變化的,所以animation就還繼續用double類型的就可以了。

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        _animation = Tween(begin: 1.0, end: 0.0).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: FadeTransition(
              opacity: _animation,
              child: Container(
                width: 100,
                height: 100,
                decoration: BoxDecoration(
                  color: Colors.red,
                ),
              ),
            ),
          ),
        );
      }
    }

    • RotationTransition

    旋轉動畫,對widget使用旋轉動畫 1~360°(Tween(begin: 0.0, end: 1.0))這裏的0-1指的是0°-360°

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        _animation = Tween(begin: 0.0, end: 1.0).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: RotationTransition(
              turns: _animation,
              child: Container(
                width: 100,
                height: 100,
                decoration: BoxDecoration(
                  color: Colors.red,
                ),
                child: Center(child: Text('data')),
              ),
            ),
          ),
        );
      }
    }

    • ScaleTransition

    縮放動畫,Tween(begin: 1.0, end: 0.2)指的是原大小的倍數,demo里是由原大小縮小到原來的0.2倍。

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        _animation = Tween(begin: 1.0, end: 0.2).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: ScaleTransition(
              scale: _animation,
              child: Container(
                width: 200,
                height: 200,
                decoration: BoxDecoration(
                  color: Colors.red,
                ),
                child: Center(child: Text('data')),
              ),
            ),
          ),
        );
      }
    }

    • SizeTransition

    僅一個方向進行縮放

    class _MyHomePageState extends State<MyHomePage>
        with SingleTickerProviderStateMixin {
      Animation<double> _animation;
      AnimationController _animationController;
    
      @override
      void initState() {
        _animationController =
            AnimationController(duration: Duration(seconds: 2), vsync: this);
        _animation = Tween(begin: 1.0, end: 0.2).animate(_animationController);
        _animationController.forward();
        super.initState();
      }
    
      @override
      void dispose() {
        _animationController.dispose();
        super.dispose();
      }
    
      @override
      Widget build(BuildContext context) {
        return Scaffold(
          appBar: AppBar(title: Text(widget.title)),
          body: Center(
            child: SizeTransition(
              axis: Axis.horizontal,
              sizeFactor: _animation,
              child: Center(
                child: Container(
                  width: 200,
                  height: 200,
                  decoration: BoxDecoration(
                    color: Colors.red,
                  ),
                  child: Center(child: Text('data')),
                ),
              ),
            ),
          ),
        );
      }
    }

     

    以上!有任何疑問歡迎留言!

     

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

    【其他文章推薦】

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

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

    ※回頭車貨運收費標準

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

    ※超省錢租車方案

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

  • Java多線程之volatile詳解

    Java多線程之volatile詳解

    本文目錄

    • 從多線程交替打印A和B開始
    • Java 內存模型中的可見性、原子性和有序性
    • Volatile原理
      • volatile的特性
      • volatile happens-before規則
      • volatile 內存語義
      • volatile 內存語義的實現
    • CPU對於Volatile的支持
      • 緩存一致性協議
    • 工作內存(本地內存)並不存在
    • 總結
    • 參考資料

    從多線程交替打印A和B開始

    面試中經常會有一道多線程交替打印A和B的問題,可以通過使用Lock和一個共享變量來完成這一操作,代碼如下,其中使用num來決定當前線程是否打印

    public class ABTread {
    
        private static int num=0;
        private static Lock lock=new ReentrantLock();
    
        public static void main(String[] args) throws InterruptedException {
    
            Thread A=new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        lock.lock();
                        if (num==0){
                            System.out.println("A");
                            num=1;
                        }
                        lock.unlock();
                    }
                }
            },"A");
            Thread B=new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        lock.lock();
                        if (num==1){
                            System.out.println("B");
                            num=0;
                        }
                        lock.unlock();
                    }
                }
            },"B");
            A.start();
            B.start();
        }
    }
    

    這一過程使用了一個可重入鎖,在以前可重入鎖的獲取流程中有分析到,當鎖被一個線程持有時,後繼的線程想要再獲取鎖就需要進入同步隊列還有可能會被阻塞。
    現在假設當A線程獲取了鎖,B線程再來獲取鎖且B線程獲取失敗則會調用LockSupport.park()導致線程B阻塞,線程A釋放鎖時再還行線程B
    是否會經常存在阻塞線程和還行線程的操作呢,阻塞和喚醒的操作是比較費時間的。是否存在一個線程剛釋放鎖之後這一個線程又再一次獲取鎖,由於共享變量的存在,
    則獲取鎖的線程一直在做着毫無意義的事情。

    可以使用volatile關鍵字來修飾共享變量來解決,代碼如下:

    public class ABTread {
    
        private static volatile  int num=0;
        public static void main(String[] args) throws InterruptedException {
    
            Thread A=new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        if (num==0){        //讀取num過程記作1
                            System.out.println("A");
                            num=1;          //寫入num記位2
                        }
                    }
                }
            },"A");
            Thread B=new Thread(new Runnable() {
                @Override
                public void run() {
                    while (true){
                        if (num==1){        //讀取num過程記作3
                            System.out.println("B");
                            num=0;          ////寫入num記位4
                        }
                    }
                }
            },"B");
            A.start();
            B.start();
        }
    }
    

    Lock可以通過阻止同時訪問來完成對共享變量的同時訪問和修改,必要的時候阻塞其他嘗試獲取鎖的線程,那麼volatile關鍵字又是如何工作,
    在這個例子中,是否效果會優於Lock呢。

    Java 內存模型中的可見性、原子性和有序性

    • 可見性:指線程之間的可見性,一個線程對於狀態的修改對另一個線程是可見的,也就是說一個線程修改的結果對於其他線程是實時可見的。
      可見性是一個複雜的屬性,因為可見性中的錯誤總是會違背我們的直覺(JMM決定),通常情況下,我們無法保證執行讀操作的線程能實時的看到其他線程的寫入的值。
      為了保證線程的可見性必須使用同步機制。退一步說,最少應該保證當一個線程修改某個狀態時,而這個修改時程序員希望能被其他線程實時可見的,
      那麼應該保證這個狀態實時可見,而不需要保證所有狀態的可見。在 Javavolatilesynchronizedfinal 實現可見性。

    • 原子性:如果一個操作是不可以再被分割的,那麼我們說這個操作是一個原子操作,即具有原子性。但是例如i++實際上是i=i+1這個操作是可分割的,他不是一個原子操作。
      非原子操作在多線程的情況下會存在線程安全性問題,需要是我們使用同步技術將其變為一個原子操作。javaconcurrent包下提供了一些原子類,
      我們可以通過閱讀API來了解這些原子類的用法。比如:AtomicIntegerAtomicLongAtomicReference等。在 Javasynchronized 和在 lockunlock 中操作保證原子性

    • 有序性:一系列操作是按照規定的順序發生的。如果在本線程之內觀察,所有的操作都是有序的,如果在其他線程觀察,所有的操作都是無序的;前半句指“線程內表現為串行語義”後半句指“指令重排序”和“工作內存和主存同步延遲”
      Java 語言提供了 volatilesynchronized 兩個關鍵字來保證線程之間操作的有序性。volatile 是因為其本身包含“禁止指令重排序”的語義,
      synchronized 是由“一個變量在同一個時刻只允許一條線程對其進行 lock 操作”這條規則獲得的,此規則決定了持有同一個對象鎖的兩個同步塊只能串行執行。

    Volatile原理

    volatile定義:Java編程語言允許線程訪問共享變量,為了確保共享變量能被準確和一致的更新,線程應該通過獲取排他鎖單獨獲取這個變量;
    java提供了volatile關鍵字在某些情況下比鎖更好用。

    • Java語言提供了volatile了關鍵字來提供一種稍弱的同步機制,他能保證操作的可見性和有序性。當把變量聲明為volatile類型后,
      編譯器與運行時都會注意到這個變量是一個共享變量,並且這個變量的操作禁止與其他的變量的操作重排序。

    • 訪問volatile變量時不會執行加鎖操作。因此也不會存在阻塞競爭的線程,因此volatile變量是一種比sychronized關鍵字更輕量級的同步機制。

    volatile的特性

    volatile具有以下特性:

    • 可見性:對於一個volatile的讀總能看到最後一次對於這個volatile變量的寫
    • 原子性:對任意單個volatile變量的讀/寫具有原子性,但對於類似於i++這種複合操作不具有原子性。
    • 有序性:

    volatile happens-before規則

    根據JMM要求,共享變量存儲在共享內存當中,工作內存存儲一個共享變量的副本,
    線程對於共享變量的修改其實是對於工作內存中變量的修改,如下圖所示:

    從多線程交替打印A和B開始章節中使用volatile關鍵字的實現為例來研究volatile關鍵字實現了什麼:
    假設線程A在執行num=1之後B線程讀取num指,則存在以下happens-before關係

    1)  1 happens-before 2,3 happens-before 4
    2)  根據volatile規則有:2 happens-before 3
    3)  根據heppens-before傳遞規則有: 1 happens-before 4
    

    至此線程的執行順序是符合我們的期望的,那麼volatile是如何保證一個線程對於共享變量的修改對於其他線程可見的呢?

    volatile 內存語義

    根據JMM要求,對於一個變量的獨寫存在8個原子操作。對於一個共享變量的獨寫過程如下圖所示:

    對於一個沒有進行同步的共享變量,對其的使用過程分為readloaduseassign以及不確定的storewrite過程。
    整個過程的語言描述如下:

    - 第一步:從共享內存中讀取變量放入工作內存中(`read`、`load`)
    - 第二步:當執行引擎需要使用這個共享變量時從本地內存中加載至**CPU**中(`use`)
    - 第三步:值被更改后使用(`assign`)寫回工作內存。
    - 第四步:若之後執行引擎還需要這個值,那麼就會直接從工作內存中讀取這個值,不會再去共享內存讀取,除非工作內存中的值出於某些原因丟失。
    - 第五步:在不確定的某個時間使用`store`、`write`將工作內存中的值回寫至共享內存。
    

    由於沒有使用鎖操作,兩個線程可能同時讀取或者向共享內存中寫入同一個變量。或者在一個線程使用這個變量的過程中另一個線程讀取或者寫入變量。
    上圖中1和6兩個操作可能會同時執行,或者在線程1使用num過程中6過程執行,那麼就會有很嚴重的線程安全問題,
    一個線程可能會讀取到一個並不是我們期望的值。

    那麼如果希望一個線程的修改對後續線程的讀立刻可見,那麼只需要將修改后存儲在本地內存中的值回寫到共享內存
    並且在另一個線程讀的時候從共享內存重新讀取而不是從本地內存中直接讀取即可;事實上
    當寫一個volatile變量時,JMM會把該線程對應的本地內存中共享變量值刷新會共享內存;
    而當讀取一個volatile變量時,JMM會從主存中讀取共享變量
    ,這也就是volatile的寫-讀內存語義。

    volatile的寫-讀內存語義:

    • volatile寫的內存語義:當寫一個volatile變量時,JMM會把該線程對應的本地內存中共享變量值刷新會共享內存
    • volatile讀的內存語義:當讀一個volatile變量時,JMM會把該線程對應的本地內存置為無效,線程接下來將從主內存中讀取共享變量。

    如果將這兩個步驟綜合起來,那麼線程3讀取一個volatile變量后,寫線程1在寫這個volatile變量之前所有可見的共享變量的值都將樂客變得對線程3可見。

    volatile變量的讀寫過程如下圖:

    需要注意的是:在各個線程的工作內存中是存在volatile變量的值不一致的情況的,只是每次使用都會從共享內存讀取並刷新,執行引擎看不到不一致的情況,
    所以認為volatile變量在本地內存中不存在不一致問題。

    volatile 內存語義的實現

    在前文Java內存模型中有提到重排序。為了實現volatile的內存語義,JMM會限制重排序的行為,具體限制如下錶:

    是否可以重排序 第二個操作 第二個操作 第二個操作
    第一個操作 普通讀/寫 volatile volatile
    普通讀/寫 NO
    volatile NO NO NO
    volatile NO NO

    說明:

    - 若第一個操作時普通變量的讀寫,第二個操作時volatile變量的寫操作,則編譯器不能重排序這兩個操作
    - 若第一個操作是volatile變量的讀操作,不論第二個變量是什麼操作不餓能重排序這兩個操作
    - 若第一個操作時volatile變量的寫操作,除非第二個操作是普通變量的獨寫,否則不能重排序這兩個操作
    

    為了實現volatile變量的內存語義,編譯器生成字節碼文件時會在指令序列中插入內存屏障來禁止特定類型的處理器排序。
    為了實現volatile變量的內存語義,插入了以下內存屏障,並且在實際執行過程中,只要不改變volatile的內存語義,
    編譯器可以根據實際情況省略部分不必要的內存屏障

    - 在每個volatile寫操作前面插入StoreStore屏障
    - 在每個volatile寫操作後面插入StoreLoad屏障
    - 在每個volatile讀操作後面插入LoadLoad屏障
    - 在每個volatile讀操作後面插入LoadStore屏障
    

    插入內存屏障后volatile寫操作過程如下圖:

    插入內存屏障后volatile讀操作過程如下圖:

    至此在共享內存和工作內存中的volatile的寫-讀的工作過程全部完成

    但是現在的CPU中存在一個緩存,CPU讀取或者修改數據的時候是從緩存中獲取並修改數據,那麼如何保證CPU緩存中的數據與共享內存中的一致,並且修改后寫回共享內存呢?

    CPU對於Volatile的支持

    緩存行:cpu緩存存儲數據的基本單位,cpu不能使數據失效,但是可以使緩存行失效。

    對於CPU來說,CPU直接操作的內存時高速緩存,而每一個CPU都有自己L1、L2以及共享的L3級緩存,如下圖:

    那麼當CPU修改自身緩存中的被volatile修飾的共享變量時,如何保證對其他CPU的可見性。

    緩存一致性協議

    在多處理器的情況下,每個處理器總是嗅探總線上傳播的數據來檢查自己的緩存是否過期,當處理器發現自己對應的緩存對應的地址被修改,
    就會將當前處理器的緩存行設置為無效狀態,當處理器對這個數據進行操作的時候,會重新從系統中把數據督導處理器的緩存里。這個協議被稱之為緩存一致性協議。

    緩存一致性協議的實現又MEIMESIMOSI等等。

    MESI協議緩存狀態

    狀態 描述
    M(modified)修改 該緩存指被緩存在該CPU的緩存中並且是被修改過的,即與主存中的數據不一致,該緩存行中的數據需要在未來的某個時間點寫回主存,當寫回註冊年之後,該緩存行的狀態會變成E(獨享)
    E(exclusive)獨享 該緩存行只被緩存在該CPU的緩存中,他是未被修改過的,與主存中數據一致,該狀態可以在任何時候,當其他的CPU讀取該內存時編程共享狀態,同樣的,當CPU修改該緩存行中的內容時,該狀態可以變為M(修改)
    S(share)共享 該狀態意味着該緩存行可能被多個CPU緩存,並且各個緩存中的數據與主存中的數據一致,當有一個CPU修改自身對應的緩存的數據,其它CPU中該數據對應的緩存行被作廢
    I(Invalid)無效 該緩存行無效

    MESI協議可以防止緩存不一致的情況,但是當一個CPU修改了緩存中的數據,但是沒有寫入主存,也會存在問題,那麼如何保證CPU修改共享被volatile修飾的共享變量后立刻寫回主存呢。

    在有volatile修飾的共享變量進行寫操作的時候會多出一條帶有lock前綴的彙編代碼,而這個lock操作會做兩件事:

    1. 將當前處理器的緩存行的數據協會到系統內存。lock信號確保聲言該信號期間CPU可以獨佔共享內存。在之前通過鎖總線的方式,現在採用鎖緩存的方式。
    2. 這個寫回操作會使其他處理器的緩存中緩存了該地址的緩存行無效。在下一次這些CPU需要使用這些地址的值時,強制要求去共享內存中讀取。

    如果對聲明了volatile的共享變量進行寫,JVM會向CPU發送一條lock指令,使得將這個變量所在的緩存行緩存的數據寫回到內存中。而其他CPU通過嗅探總線上傳播的數據,
    使得自身緩存行失效,下一次使用時會從主存中獲取對應的變量。

    工作內存(本地內存)並不存在

    根據JAVA內存模型描述,各個線程使用自身的工作內存來保存共享變量,那麼是不是每個CPU緩存的數據就是從工作內存中獲取的。這樣的話,在CPU緩存寫回主存時,
    協會的是自己的工作內存地址,而各個線程的工作內存地址並不一樣。CPU嗅探總線時就嗅探不到自身的緩存中緩存有對應的共享變量,從而導致錯誤?

    事實上,工作內存並不真實存在,只是JMM為了便於理解抽象出來的概念,它涵蓋了緩存,寫緩衝區、寄存器及其他的硬件編譯器優化。所以緩存是直接和共享內存交互的。
    每個CPU緩存的共享數據的地址是一致的。

    總結

    • volatile提供了一種輕量級同步機制來完成同步,它可以保操作的可見性、有序性以及對於單個volatile變量的讀/寫具有原子性,對於符合操作等非原子操作不具有原子性。

    • volatile通過添加內存屏障及緩存一致性協議來完成對可見性的保證。

    最後Lock#lock()是如何保證可見性的呢??

    Lock#lock()使用了AQSstate來標識鎖狀態,而statevolatile標記的,由於對於volatile的獨寫操作時添加了內存屏障的,所以在修改鎖狀態之前,
    一定會將之前的修改寫回共享內存。

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

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

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • mysql定時備份任務

    mysql定時備份任務

    簡介

    在生產環境上,為了避免數據的丟失,通常情況下都會定時的對數據庫進行備份。而Linux的crontab指令則可以幫助我們實現對數據庫定時進行備份。首先我們來簡單了解crontab指令,如果你會了請跳到下一個內容mysql備份
    本文章的mysql數據庫是安裝在docker容器當中,以此為例進行講解。沒有安裝到docker容器當中也可以參照參照。

    contab定時任務

    使用crontab -e來編寫我們的定時任務。

    0 5 * * 1 [command]
    

    前面的5個数字分別代表分、時、日、月、周,後面的 command為你的執行命令。
    假如你需要在每天晚上8點整執行定時任務,那麼可以這麼寫

    0 8 * * * [command]
    

    擴展:
    crontab -l 可以查看自己的定時任務
    crontab -r 刪除當前用戶的所有定時任務

    mysql備份

    快速上手

    這裏我的mysql數據庫是docker容器。假如你需要在每天晚上8點整執行定時任務,那麼可以這麼寫。
    首先執行命令crontab -e

    0 8 * * * docker exec mysql_container mysqldump -uroot -proot_password database_name > /var/backups/mysql/$(date +%Y%m%d_%H%M%S).sql
    

    mysql_container 為你的數據庫容器名
    mysqldump 是mysql數據庫導出數據的指令
    -u 填寫root賬號
    -p 填寫root密碼
    database_name 需要備份的數據庫名
    /var/backups/mysql/$(date +%Y%m%d_%H%M%S).sql 備份文件,後面是文件名的格式

    如果你沒什麼要求,單純的只是想要備份,那麼上面那個命令就可以幫你進行定時備份。

    小坑: mysql備份的時候我使用了docker exec -it mysqldump ... 這樣的命令去做bash腳本,因為-i參數是有互動的意思,導致在crontab中執行定時任務的時候,沒有輸出數據到sql文件當中。所以使用crontab定時的對docker容器進行備份命令的時候不要添加-i參數。

    crontab優化

    我不建議直接在crontab -e裏面寫要執行的命令,任務多了就把這個文件寫的亂七八招了。
    建議把數據庫備份的命令寫成一個bash腳本。在crontab這裏調用就好了
    如:建立一個/var/backups/mysql/mysqldump.sh文件,內容如下

    docker exec mysql_container mysqldump -uroot -pmypassword database_name > /var/backups/mysql/$(date +%Y%m%d_%H%M%S).sql
    

    然後把文件改為當前用戶可執行的:

    chmod 711 /var/backups/mysql/mysqldump.sh
    

    執行crontab -e 命令修改成如下:

    0 20 * * * /var/backups/mysql/mysqldump.sh
    

    那麼這樣就比較規範了。

    mysql備份優化

    因為sql文件比較大,所以一般情況下都會對sql文件進行壓縮,不然的話磁盤佔用就太大了。
    假設你做了上面這一步 crontab優化,我們可以把mysqldump.sh腳本改成下面這樣:

    export mysqldump_date=$(date +%Y%m%d_%H%M%S) && \
    docker exec mysql_container mysqldump -uroot -pmypassword database_name> /var/backups/mysql/$mysqldump_date.sql && \
    gzip /var/backups/mysql/$mysqldump_date.sql
    find /var/backups/mysql/ -name "*.sql" -mtime +15 -exec rm -f {} \;
    

    export 在系統中自定義了個變量mysqldump_date,給備份和壓縮命令使用
    gzip 為壓縮命令,默認壓縮了之後會把源文件刪除,壓縮成.gz文件
    find ... 這行命令的意思為,查詢 /var/backups/mysql/目錄下,創建時間15天之前(-mtime +15),文件名後綴為.sql的所有文件 執行刪除命令-exec rm -f {} \;。總的意思就是:mysql的備份文件只保留15天之內的。15天之前的都刪除掉。

    數據恢復

    若一不小心你執行drop database,穩住,淡定。我們首先要創建數據庫被刪除的數據庫。

    >mysql create database database_name;
    

    然後恢復最近備份的數據。恢復備份的命令:

    docker exec -i mysql_container mysql -uroot -proot_password database_name < /var/backups/mysql/20200619_120012.sql
    

    雖然恢復了備份文件的數據,但是備份時間點之後的數據我們卻沒有恢復回來。
    如:晚上8點進行定時備份,但是卻在晚上9點drop database,那麼晚上8點到晚上9點這一個小時之內的數據卻沒有備份到。這時候就要使用binlog日誌了。

    binlog日誌

    binlog 是mysql的一個歸檔日誌,記錄的數據修改的邏輯,如:給 ID = 3 的這一行的 money 字段 + 1。
    首先登錄mysql后查詢當前有多少個binlog文件:

    > mysql show binary logs;
    +---------------+-----------+-----------+
    | Log_name      | File_size | Encrypted |
    +---------------+-----------+-----------+
    | binlog.000001 |       729 | No        |
    | binlog.000002 |      1749 | No        |
    | binlog.000003 |      1087 | No        |
    +---------------+-----------+-----------+
    

    查看當前正在寫入的binlog

    mysql> show master status\G;
    

    生成新的binlog文件,mysql的後續操作都會寫入到新的binlog文件當中,一般在恢複數據都時候都會先執行這個命令。

    mysql> flush logs
    

    查看binlog日誌

    mysql> show binlog events in 'binlog.000003';
    

    小知識點:初始化mysql容器時,添加參數--binlog-rows-query-log-events=ON。或者到容器當中修改/etc/mysql/my.cnf文件,添加參數binlog_rows_query_log_events=ON,然後重啟mysql容器。這樣可以把原始的SQL添加到binlog文件當中。

    恢複數據

    拿回上面例子的這段話。

    晚上8點進行定時備份,但是卻在晚上9點drop database,那麼晚上8點到晚上9點這一個小時之內的數據卻沒有備份到。。

    首先進入到mysql容器后,切換到/var/lib/mysql目錄下,查看binlog文件的創建日期

    cd /var/lib/mysql
    ls -l
    ...
    -rw-r----- 1 mysql mysql      729 Jun 19 15:54  binlog.000001
    -rw-r----- 1 mysql mysql     1749 Jun 19 18:45  binlog.000002
    -rw-r----- 1 mysql mysql     1087 Jun 19 20:58  binlog.000003
    ...
    

    從文件日期可以看出:當天時間為2020-06-21,binlog.000002文件的最後更新時間是 18:45 分,那麼晚上8點的備份肯定包含了binlog.000002的數據;
    binlog.000003的最後更新日期為 20:58 分,那麼我們需要恢復的數據 = 晚上8點的全量備份 + binlog.000003的 20:00 – 執行drop database命令時間前的數據。

    恢復命令格式:

    mysqlbinlog [options] file | mysql -uroot -proot_password database_name
    

    mysqlbinlog常用參數:

    –start-datetime 開始時間,格式 2020-06-19 18:00:00
    –stop-datetime 結束時間,格式同上
    –start-positon 開始位置,(需要查看binlog文件)
    –stop-position 結束位置,同上

    恢復備份數據和binlog數據前建議先登錄mysql后執行flush logs生成新的binlog日誌,這樣可以專註需要恢複數據的binlog文件。
    首先我們需要查看binlog日誌,在哪個位置進行了drop database操作:

    mysql> show binlog events in 'binlog.000003';
    +---------------+-----+----------------+-----------+-------------+---------------------------------------------------------------------------------------------------------------------------------------------+
    | Log_name      | Pos | Event_type     | Server_id | End_log_pos | Info                                                                                                                                        |
    +---------------+-----+----------------+-----------+-------------+---------------------------------------------------------------------------------------------------------------------------------------------+
    | binlog.000003 |   4 | Format_desc    |         1 |         125 | Server ver: 8.0.20, Binlog ver: 4                                                                                                           |
    | binlog.000003 | 125 | Previous_gtids |         1 |         156 |                                                                                                                                             |
    | binlog.000003 | 156 | Anonymous_Gtid |         1 |         235 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                                                                                        |
    | binlog.000003 | 235 | Query          |         1 |         318 | BEGIN                                                                                                                                       |
    | binlog.000003 | 318 | Rows_query     |         1 |         479 | # INSERT INTO `product_category` SET `name` = '床上用品' , `create_time` = 1592707634 , `update_time` = 1592707634 , `lock_version` = 0      |
    | binlog.000003 | 479 | Table_map      |         1 |         559 | table_id: 139 (hotel_server.product_category)                                                                                               |
    | binlog.000003 | 559 | Write_rows     |         1 |         629 | table_id: 139 flags: STMT_END_F                                                                                                             |
    | binlog.000003 | 629 | Xid            |         1 |         660 | COMMIT /* xid=2021 */                                                                                                                       |
    | binlog.000004 | 660 | Anonymous_Gtid |         1 |         739 | SET @@SESSION.GTID_NEXT= 'ANONYMOUS'                                                                                                        |
    | binlog.000004 | 739 | Query          |         1 |         822 | drop database hotel_server /* xid=26 */                                                                                                     |
    +---------------+-----+----------------+-----------+-------------+---------------------------------------------------------------------------------------------------------------------------------------------+
    

    根據上面的日誌,我們可以看到,在End_log_pos = 822 的位置執行了drop database操作,那麼使用binlog恢復的範圍就在2020-06-19 20:00:00 – 660 的位置。為什麼是660?因為drop database的上一個事務的提交是660的位置,命令如下:

    mysqlbinlog --start-datetime=2020-06-19 20:00:00 --stop-position=660 /var/lib/mysql/binlog.000003 | mysql -uroot -proot_password datbase_name
    

    如果你的範圍包括了822的位置,那麼就會幫你執行drop database命令了。不信你試試?
    執行完上面的命令,你的數據就會恢復到drop database前啦!開不開心,激不激動!

    總結

    因為mysql定時備份是在生產環境上必須的任務。是很常用的。所以我就迫不及待的寫博客。當然也很感謝我同事的幫助。這篇文章已經寫了三天了,因為我也是在不斷地試錯,不斷的更新文章。避免把錯誤的知識點寫出來。如果幫到你了,關注我一波唄!謝謝。

    個人博客網址: https://colablog.cn/

    如果我的文章幫助到您,可以關注我的微信公眾號,第一時間分享文章給您

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

    【其他文章推薦】

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

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

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

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

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

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

  • 快速打造屬於你的接口自動化測試框架

    快速打造屬於你的接口自動化測試框架

    1 接口測試

    接口測試是對系統或組件之間的接口進行測試,主要是校驗數據的交換,傳遞和控制管理過程,以及相互邏輯依賴關係。
    接口自動化相對於UI自動化來說,屬於更底層的測試,這樣帶來的好處就是測試收益更大,且維護成本相對來說較低,是我們進行自動化測試的首選

    2 框架選型

    目前接口自動化的框架比較多,比如jmeter,就可以集接口自動化和性能測試於一體,該工具編寫用例效率不高;還有我們常用的postman,結合newman也可以實現接口自動化;Python+unittest+requests+HTMLTestRunner 是目前比較主流的測試框架,對python有一定的編碼要求;
    本期我們選擇robotframework(文中後續統一簡稱為RF)這一個比較老牌的測試框架進行介紹,RF是一個完全基於 關鍵字 測試驅動的框架,它即能夠基於它的一定規則,導入你需要的測試庫(例如:其集成了selenium的測試庫,即可以理解為操作控件的測試底層庫),然後基於這些測試庫,你能應用TXT形式編寫自己的關鍵字(支持python和java語言,這些關鍵字即你的庫組成),之後,再編寫(測試用例由測試關鍵字組成)進行測試;他支持移動端、UI自動化和接口自動化的測試

    3 環境搭建

    • python的安裝:目前選取的python3以上的版本,RF的運行依賴python
    • robotframework:參考https://www.jianshu.com/p/9dcb4242b8f2
    • jenkins:用於調度RF的用例執行環境
    • gitlab:代碼倉庫

    4 需求

    4.1 需求內容
    接口內容:實現一個下單,並檢查訂單狀態是否正常的場景;該需求涉及到如下三個接口

    • 下單接口
    • 訂單結果查詢接口
    • 下單必須帶上認證標識,生成token的接口

    環境覆蓋:需要支持能在多套環境運行,比如測試和預發布環境
    系統集成:需要能夠集成在CICD中,實現版本更新后的自動檢測

    4.2 用例設計
    4.2.1 用例設計,根據業務場景設計測試用例,方便後續實現

    4.2.2 測試數據構造,預置不同環境的測試數據,供實現調用

    5 整體實現架構

    接口測試實現層:在RF,通過引用默認關鍵字 RequestsLibrary (實現http請求)和通過python自定義關鍵字來完成用例實現的需求;
    jenkins調度:在jenkins上配置一個job,設置好RF用例執行的服務器和發送給服務器相關的RF執行的指令,並且在jenkins中配置好測試報告模板,這樣用例便可以通過jenkins完成執行併發送測試結果給項目干係人;
    生成用例執行的API:上圖中藍色部分,就是為了將jenkins的job生成一個可訪問api接口,方便被測項目的CICD集成;
    集成到被測系統CICD流程:將上面步驟中封裝的API配置在被測應用的__gitlab-ci.yml__中,完成整個接口自動化的閉環

    6 RF用例實現

    6.1 引用的內置關鍵字

    • RequestsLibrary 構造http的請求,get|post等請求
    getRequests
    # get請求的入參
        [Arguments]    ${url_domain}    ${getbody}    ${geturl}    ${getToken}
        Create session    postmain    ${url_domain}
    # 定義header的內容
        ${head}    createdictionary    content-type=application/json    Authorization=${getToken}    MerchantId=${s_merchant_id}
    # get請求
        ${addr}    getRequest    postmain    ${geturl}    params=${getbody}    headers=${head}
    # 請求狀態碼斷言
        Should Be Equal As Strings    ${addr.status_code}    200
        ${response_get_data}    To Json    ${addr.content}
    # 返回http_get請求結果
        Set Test Variable    ${response_get_data}	 
        Delete All Sessions
    

    6.2 自定義關鍵字

    • getEnvDomain 用於從自定義的configs.ini文件獲取對應環境的微服務的請求域名
      configs.ini的內容
    # 獲取configs.ini的內容
    import configparser
    def getEnv(path,env):
        config = configparser.ConfigParser()
        config.read(path)
        passport = config[env]['passport']
        stock=config[env]['stock']
        finance=config[env]['finance']
        SUP = config[env]['SUP']
        publicApi = config[env]['publicApi']
        publicOrder = config[env]['publicOrder']
        data_dict={'passport':passport,'stock':stock,'finance':finance,'SUP':SUP,'publicApi':publicApi,'publicOrder':publicOrder}
        return data_dict
    
    • excelTodict 用戶將excel中的內容作為字典返回
    import xlrd
    
    '''
    通用獲取excel數據
    @:param path excel文件路徑
    @:param sheet_name excel文件裏面sheet的名稱 如:Sheet1
    @:env 環境,是IT還是PRE
    '''
    def getExcelDate(path, sheet_name,env):
        bk = xlrd.open_workbook(path)
        sh = bk.sheet_by_name(sheet_name)
        row_num = sh.nrows
        data_list = []
        for i in range(1, row_num):
            row_data = sh.row_values(i)
            data = {}
            for index, key in enumerate(sh.row_values(0)):
                data[key] = row_data[index]
            data_list.append(data)
        data_list1 = []
        for x in data_list:
            #print('這是'+str(x))
            if(x.get('env')==env):
                data_list1.append(x)
        return data_list1
    
    • getToken 提供接口下單的授權token
    *** Keywords ***
    # 根據傳入的clientid、secret生成對應的token
    getToken
        [Arguments]    ${client_id}    ${client_secret}    ${url_domain}
        Create session    postmain    ${url_domain}
        ${auth}    createdictionary    grant_type=client_credentials    client_id=${client_id}    client_secret=${client_secret}
        ${header}    createdictionary    content-type=application/x-www-form-urlencoded
        ${addr}    postRequest    postmain    /oauth/token    data=${auth}    headers=${header}
        Should Be Equal As Strings    ${addr.status_code}    200
        ${responsedata}    To Json    ${addr.content}
        ${access}    Get From Dictionary    ${responsedata}    access_token
        ${token}    set variable    bearer ${access}
        Set Test Variable    ${token}
        Delete All Sessions
    
    • getAllDate 獲取該用例下的所有數據
    getAllData
        [Arguments]    ${row_no}
        getEnvDomain
        getBalance    ${row_no}
        getStockNum    ${row_no}
        getSupProPrice    ${row_no}
        getProPrice    ${row_no}
        Set Test Variable    ${publicOrderUrl}
        Set Test Variable    ${FPbalance}
        Set Test Variable    ${Pbalance}
        Set Test Variable    ${Sbalance}
        Set Test Variable    ${Jbalance}
        Set Test Variable    ${Cardnum}
        Set Test Variable    ${sprice}
        Set Test Variable    ${price}
        Set Test Variable    ${j_merchant_id}
        Set Test Variable    ${s_merchant_id}
        Set Test Variable    ${stock_id}
        Set Test Variable    ${p_product_id}
        Set Test Variable    ${s_product_id}
    
    
    • 實現demo
    *** Settings ***
    Test Template
    Resource          引用所有資源.txt
    
    *** Test Cases ***
    *** Settings ***
    Test Template
    Resource          引用所有資源.txt
    
    *** Test Cases ***
    01 下單卡密直儲商品
        [Tags]    order
        LOG    ---------------------獲取下單前的數量、餘額------------------------------------------
        getAllData    0
        ${Cardnum1}    set variable    ${Cardnum}
        ${FPbalance1}    set variable    ${FPbalance}
        ${Pbalance1}    set variable    ${Pbalance}
        ${Sbalance1}    set variable    ${Sbalance}
        ${Jbalance1}    set variable    ${Jbalance}
        ${CustomerOrderNo1}    Evaluate    random.randint(1000000, 9999999)    random
        ${Time}    Get Time
        log    ------------------------下單操作-------------------------------------------------------
        getToken    100xxxx    295dab07a9xxxx9780be0eb95xxxx   ${casUrl}
        ${input_cs}    create dictionary    memberId=${j_merchant_id}    clientId=1xxx079    userId=string    shopType=string    customerOrderNo=${CustomerOrderNo1}
        ...    productId=${p_product_id}    buyNum=1    chargeAccount=otest888888    notifyUrl=string    chargeIp=string    chargePassword=string
        ...    chargeGameName=string    chargeGameRole=string    chargeGameRegion=string    chargeGameSrv=string    chargeType=string    remainingNumber=0
        ...    contactTel=string    contactQQ=string    customerPrice=0    poundage=0    batchNumber=    originalOrderId=string
        ...    shopName=string    appointSupProductId=0    stemFromSubOrderId=123456    externalBizId=456789
        postRequests    ${publicOrderUrl}    ${input_cs}    /api/Order    ${token}
        ${data}    get from dictionary    ${responsedata}    data
        ${orderid}    get from dictionary    ${data}    id
        sleep    6
        ${getdata}    create dictionary    Id=${orderid}    PageIndex=1    PageSize=1
        getRequests    ${publicOrderUrl}    ${getdata}    /api/Order/GetList    ${token}
        ${datalist}    get from dictionary    ${response_get_data}    data
        ${data}    get from dictionary    ${datalist}    list
        ${dict}    set variable    ${data}[0]
        ${orderOuterStatus}    get from dictionary    ${dict}    orderOuterStatus
        LOG    ---------------------獲取下單后的數量、餘額----------------------------------------------
        getAllData    0
        ${Cardnum2}    set variable    ${Cardnum}
        ${FPbalance2}    set variable    ${FPbalance}
        ${Pbalance2}    set variable    ${Pbalance}
        ${Sbalance2}    set variable    ${Sbalance}
        ${Jbalance2}    set variable    ${Jbalance}
        ${sprice}    set variable    ${sprice}
        ${price}    set variable    ${price}
        log    ------------------斷言-----------------------------------------------------------------
        ${Cardnum3}    Evaluate    ${Cardnum1}
        ${Jbalance3}    Evaluate    ${Jbalance1}
        ${Sbalance3}    Evaluate    ${Sbalance1}
        ${Pbalance3}    Evaluate    ${Pbalance1}
        should be true    ${orderOuterStatus}==90
        should be true    ${Cardnum3}==${Cardnum2}
        should be true    ${Jbalance3}==${Jbalance2}
        should be true    ${Sbalance3}==${Sbalance2}
        should be true    ${Pbalance3}==${Pbalance2}
    
    

    7 集成到CICD流程

    7.1 jenkins配置job
    通過jenkins的參數化構建,定義it和pre兩套環境

    jenkins發送RF執行的命令

    7.2 封裝的jenkins_job的執行接口地址
    通過python的flask框架,根據測試和pre兩套環境包一層jenkins的job執行接口

    __author__ = 'paul'
    
    # !/usr/bin/env python
    # -*- coding: utf-8 -*-
    from flask import Flask, abort, request, jsonify
    import jenkins
    
    server = jenkins.Jenkins('http://10.0.1.xxx:80', username='xxx', password='fuluxxxx')
    
    app = Flask(__name__)
    
    tasks = []
    
    # it的測試集合http請求接口
    @app.route('/test/it', methods=['get'])
    def robot_Test_It():
        server.build_job('CI_FuluOrder', {'environment': 'IT'})
        return jsonify({'result': 'success'})
    
    # pre的測試集合http請求接口
    @app.route('/test/pre', methods=['get'])
    def robot_Test_Pre():
        server.build_job('CI_FuluOrder', {'environment': 'PRE'})
        return jsonify({'result': 'success'})
    
    if __name__ == "__main__":
        # 將host設置為0.0.0.0,則外網用戶也可以訪問到這個服務
        app.run(host="0.0.0.0", port=80, debug=True)
    
    

    7.3 將上述flask封裝的接口打包成鏡像
    根據dockerfile生成鏡像

    FROM python:3.6
    WORKDIR /app
    EXPOSE 80
    COPY .	.
    RUN pip install -r requirements.txt 
    ENTRYPOINT ["python","robotTestApi.py"]
    
    

    7.4 將鏡像部署到kubernetes,對外提供服務
    供觸發測試執行的調用入口 ,這部分封裝的接口部署在本地的k8s集群下ordermiddle

    IT: http://ordermiddle.xxx.cn/test/it
    pre:http://ordermiddle.xxx.cn/test/pre

    7.5 被測項目的CICD集成接口自動化測試
    gitlab目前採取直接對CICD腳本加入測試步驟,在部署到容器30秒后(考慮到容器在K8S啟動時間)調用測試接口

    7.6 發送測試報告

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

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

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

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

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

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

    ※回頭車貨運收費標準

  • Java 多線程基礎(十)interrupt()和線程終止方式

    Java 多線程基礎(十)interrupt()和線程終止方式

    一、interrupt() 介紹

    interrupt() 定義在 Thread 類中,作用是中斷本線程

    本線程中斷自己是被允許的;其它線程調用本線程的 interrupt() 方法時,會通過 checkAccess() 檢查權限。這有可能拋出 SecurityException 異常。
    如果本線程是處於阻塞狀態:調用線程的 wait() , wait(long) 或 wait(long, int) 會讓它進入等待(阻塞)狀態,或者調用線程的 join(),join(long),join(long, int),sleep(long),sleep(long, int) 也會讓它進入阻塞狀態。若線程在阻塞狀態時,調用了它的 interrupt() 方法,那麼它的“中斷狀態”會被清除並且會收到一個 InterruptedException 異常。例如,線程通過 wait() 進入阻塞狀態,此時通過 interrupt() 中斷該線程;調用 interrupt() 會立即將線程的中斷標記設為 true,但是由於線程處於阻塞狀態,所以該“中斷標記”會立即被清除為 “false”,同時,會產生一個 InterruptedException 的異常
    如果線程被阻塞在一個 Selector 選擇器中,那麼通過 interrupt() 中斷它時;線程的中斷標記會被設置為 true,並且它會立即從選擇操作中返回。
    如果不屬於前面所說的情況,那麼通過 interrupt() 中斷線程時,它的中斷標記會被設置為 true。
    中斷一個“已終止的線程”不會產生任何操作。

    二、線程終止方式

    Thread中的 stop() 和 suspend() 方法,由於固有的不安全性,已經建議不再使用!
    下面,我先分別討論線程在“阻塞狀態”和“運行狀態”的終止方式,然後再總結出一個通用的方式。

    (一)、終止處於“阻塞狀態”的線程.

    通常,我們通過“中斷”方式終止處於“阻塞狀態”的線程
    當線程由於被調用了 sleep(),,wait(),join() 等方法而進入阻塞狀態;若此時調用線程的 interrupt() 將線程的中斷標記設為 true。由於處於阻塞狀態,中斷標記會被清除,同時產生一個InterruptedException 異常。將 InterruptedException 放在適當的位置就能終止線程,形式如下:

    public void run() {
        try {
            while (true) {
                // 執行業務
            }
        } catch (InterruptedException ie) {  
            // 由於產生InterruptedException異常,退出while(true)循環,線程終止!
        }
    }

    說明:

    在while(true)中不斷的執行業務代碼,當線程處於阻塞狀態時,調用線程的 interrupt() 產生 InterruptedException 中斷。中斷的捕獲在 while(true) 之外,這樣就退出了 while(true) 循環!

    注意:

    對 InterruptedException 的捕獲務一般放在 while(true) 循環體的外面,這樣,在產生異常時就退出了 while(true) 循環。否則,InterruptedException 在 while(true) 循環體之內,就需要額外的添加退出處理。形式如下: 

    public void run() {
        while (true) {
            try {
                // 執行任務...
            } catch (InterruptedException ie) {  
                // InterruptedException在while(true)循環體內。
                // 當線程產生了InterruptedException異常時,while(true)仍能繼續運行!需要手動退出
                break;
            }
        }
    }

    說明:

    上面的 InterruptedException 異常的捕獲在 whle(true) 之內。當產生 InterruptedException 異常時,被 catch 處理之外,仍然在 while(true) 循環體內;要退出 while(true) 循環體,需要額外的執行退出while(true) 的操作。

    (二)、終止處於“運行狀態”的線程

    通常,我們通過“標記”方式終止處於“運行狀態”的線程。其中,包括“中斷標記”和“額外添加標記”。

    1、通過“中斷標記”終止線程

    public void run() {
        while (!isInterrupted()) {
            // 執行任務...
        }
    }

    說明:

    isInterrupted() 是判斷線程的中斷標記是不是為 true。當線程處於運行狀態,並且我們需要終止它時;可以調用線程的 interrupt() 方法,使用線程的中斷標記為 true,即 isInterrupted() 會返回true。此時,就會退出while循環。
    注意:interrupt() 並不會終止處於“運行狀態”的線程!它會將線程的中斷標記設為 true。

    2、通過“額外添加標記”終止線程

    private volatile boolean flag= true;
    protected void stopTask() {
        flag = false;
    }
    public void run() {
        while (flag) {
            // 執行任務...
        }
    }

    說明:

    線程中有一個 flag 標記,它的默認值是 true;並且我們提供 stopTask() 來設置 flag 標記。當我們需要終止該線程時,調用該線程的 stopTask() 方法就可以讓線程退出 while 循環。
    注意:將 flag 定義為 volatile 類型,是為了保證 flag 的可見性。即其它線程通過 stopTask() 修改了 flag 之後,本線程能看到修改后的 flag 的值。

    (三)、通過方式

    綜合線程處於“阻塞狀態”和“運行狀態”的終止方式,比較通用的終止線程的形式如下:

    public void run() {
        try {
            // 1. isInterrupted()保證,只要中斷標記為true就終止線程。
            while (!isInterrupted()) {
                // 執行任務...
            }
        } catch (InterruptedException ie) {  
            // 2. InterruptedException異常保證,當InterruptedException異常產生時,線程被終止。
        }
    }
    1、isInterrupted()保證,只要中斷標記為 true 就終止線程。
    2、InterruptedException 異常保證,當 InterruptedException 異常產生時,線程被終止。

    三、示例

    public class InterruptTest {
        public static void main(String[] args) {
            try {
                Thread t1 = new MyThread("t1"); // 新建線程t1
                System.out.println(t1.getName() + "[" + t1.getState() + "] is new.");
                
                t1.start();// 啟動線程t1
                System.out.println(t1.getName() + "[" + t1.getState() + "] is started.");
                
                Thread.sleep(300);// 休眠300毫秒,然後主線程給t1發“中斷”指令,查看t1狀態
                t1.interrupt();
                System.out.println(t1.getName() + "[" + t1.getState() + "] is interrupted.");
                
                Thread.sleep(300);// 休眠300毫秒,然後查看t1狀態
                System.out.println(t1.getName() + "[" + t1.getState() + "] is interrupted now.");
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
    class MyThread extends Thread{
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            try {
                int i = 0;
                while(!isInterrupted()) {
                    Thread.sleep(100);// 休眠100毫秒
                    ++i;
                    System.out.println(Thread.currentThread().getName() + "[" + this.getState() + "] loop " + i);
                }
            }catch(InterruptedException e) {
                System.out.println(Thread.currentThread().getName() + "[" + this.getState() + "] catch InterruptedException");
            }
        }
    }
    // 運行結果
    t1 [ NEW ] is new.
    t1 [ RUNNABLE ] is started.
    t1 [ RUNNABLE ] loop 1
    t1 [ RUNNABLE ] loop 2
    t1 [ RUNNABLE ] loop 3
    t1 [ RUNNABLE ] catch InterruptedException
    t1 [ TERMINATED ] is interrupted.
    t1 [ TERMINATED ] is interrupted now.

    說明:

    ①、主線程 main 中通過 new MyThread(“t1”) 創建線程 t1,之後通過 t1.start() 啟動線程 t1。
    ②、t1 啟動之後,會不斷的檢查它的中斷標記,如果中斷標記為“false”;則休眠 100ms。
    ③、t1 休眠之後,會切換到主線程main;主線程再次運行時,會執行t1.interrupt()中斷線程t1。t1收到中斷指令之後,會將t1的中斷標記設置“false”,而且會拋出 InterruptedException 異常。在 t1 的 run() 方法中,是在循環體 while 之外捕獲的異常;因此循環被終止。

    我們對上面的結果進行小小的修改,將run()方法中捕獲InterruptedException異常的代碼塊移到while循環體內。

    public class InterruptTest {
        public static void main(String[] args) {
            try {
                Thread t1 = new MyThread("t1"); // 新建線程t1
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is new.");
                
                t1.start();// 啟動線程t1
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is started.");
                
                Thread.sleep(300);// 休眠300毫秒,然後主線程給t1發“中斷”指令,查看t1狀態
                t1.interrupt();
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted.");
                
                Thread.sleep(300);// 休眠300毫秒,然後查看t1狀態
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted now.");
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
            
        }
    }
    class MyThread extends Thread{
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            int i = 0;
            while(!isInterrupted()) {
                try {
                    Thread.sleep(100); // 休眠100ms
                } catch (InterruptedException ie) {  
                    System.out.println(Thread.currentThread().getName() +" [ "+this.getState()+" ] catch InterruptedException.");  
                }
                i++;
                System.out.println(Thread.currentThread().getName()+" [ "+this.getState()+" ] loop " + i);  
            }
        }
    }
    // 運行結果
    t1 [ NEW ] is new.
    t1 [ RUNNABLE ] is started.
    t1 [ RUNNABLE ] loop 1
    t1 [ RUNNABLE ] loop 2
    t1 [ TIMED_WAITING ] is interrupted.
    t1 [ RUNNABLE ] catch InterruptedException.
    t1 [ RUNNABLE ] loop 3
    t1 [ RUNNABLE ] loop 4
    t1 [ RUNNABLE ] loop 5
    t1 [ RUNNABLE ] loop 6
    t1 [ RUNNABLE ] is interrupted now.
    t1 [ RUNNABLE ] loop 7
    ...... // 無限循環

    說明:

    程序進入了死循環了。

    這是因為,t1在“等待(阻塞)狀態”時,被 interrupt() 中斷;此時,會清除中斷標記(即 isInterrupted() 會返回 false),而且會拋出 InterruptedException 異常(該異常在while循環體內被捕獲)。因此,t1理所當然的會進入死循環了。
    解決該問題,需要我們在捕獲異常時,額外的進行退出 while 循環的處理。例如,在 MyThread 的 catch(InterruptedException) 中添加 break 或 return 就能解決該問題。

    下面是通過“額外添加標記”的方式終止“狀態狀態”的線程的示例:

    public class InterruptTest {
        public static void main(String[] args) {
            try {
                MyThread t1 = new MyThread("t1"); // 新建線程t1
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is new.");
                
                t1.start();// 啟動線程t1
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is started.");
                
                Thread.sleep(300);// 休眠300毫秒,然後主線程給t1發“中斷”指令,查看t1狀態
                t1.stopTask();
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted.");
                
                Thread.sleep(300);// 休眠300毫秒,然後查看t1狀態
                System.out.println(t1.getName() + " [ " + t1.getState() + " ] is interrupted now.");
            }catch(InterruptedException e) {
                e.printStackTrace();
            }
            
        }
    }
    class MyThread extends Thread{
        private volatile boolean flag = true;
        public void stopTask() {
            flag = false;
        }
        public MyThread(String name) {
            super(name);
        }
        @Override
        public void run() {
            synchronized (this) {
                int i = 0;
                while(flag) {
                    try {
                        Thread.sleep(100); // 休眠100ms
                    } catch (InterruptedException ie) {  
                        System.out.println(Thread.currentThread().getName() +" [ "+this.getState()+" ] catch InterruptedException.");  
                        break;
                    }
                    i++;
                    System.out.println(Thread.currentThread().getName()+" [ "+this.getState()+" ] loop " + i);  
                }
            }
            
        }
    }
    // 運行結果
    t1 [ NEW ] is new.
    t1 [ RUNNABLE ] is started.
    t1 [ RUNNABLE ] loop 1
    t1 [ RUNNABLE ] loop 2
    t1 [ RUNNABLE ] loop 3
    t1 [ RUNNABLE ] is interrupted.
    t1 [ TERMINATED ] is interrupted now.

    四、interrupted() 和 isInterrupted()的區別

    interrupted() 和 isInterrupted()都能夠用於檢測對象的“中斷標記”。
    區別是,interrupted() 除了返回中斷標記之外,它還會清除中斷標記(即將中斷標記設為 false);而 isInterrupted() 僅僅返回中斷標記

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

    【其他文章推薦】

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

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

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

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

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

    ※超省錢租車方案

  • JVM 總結

    JVM 總結

    JVM GC 總結。

    周志明大大的《深入理解Java虛擬機》出第三版了,早早的買了這本書,卻一直沒有花時間看。近來抽空溫習了一下,感覺又有了新的收穫。這裏簡單總結下。

    GC的由來

    由於的動態性,操作系統將堆交由給了開發者自己管理,手動申請,手動釋放。對於C++,則是將這個權限繼續交給了開發者,而對於Java,則是將這個過程自動化了。為什麼要釋放內存呢?最簡單的原因就是操作系統一共給你了4G的內存空間,你需要的時候,就去借用。有借有還,再借不難,只借不還,最後4G內存空間被用完了,你就無法再申請新的內存了。內存泄漏,就是只借不還。

    JVM在操作系統與開發者之間又封裝了一層,間接的接管了內存的劃分。同時也將堆統一管理起來,使得開發者只管借用內存,由JVM負責回收,了解JVM的回收機制,明白它的原理,能讓開發者在不同的場景下,定製不同的回收規則,提高回收效率。

    關於GC的思考

    如果讓我設計一個能自動回收垃圾的虛擬機,我會怎麼設計呢?

    • 什麼時候開始回收?
    • 怎麼判斷這部分內存可以回收?
    • 怎麼回收這部分的垃圾?

    這3個問題,也是JVM開發者一直在思考的問題。之前簡單了解過JVM,就知道JVM會有Stop The World的問題,這對於用戶體驗來說非常不好,其根本原因便是因為在回收垃圾的時候,用戶線程可能會修改這部分內存,如果不暫停用戶線程,則可能會導致嚴重的問題,而如何減少Stop The World的時候,甚至讓其消失,是各個垃圾回收器一直追求的目標。

    哪些內存可以回收?

    對於一個對象來說,當不存在任何一個引用能夠訪問到這個對象的時候,則說明這個對象可以進行回收。因為沒有任何引用指向這個對象,那麼這個對象就不能被讀或寫。

    • 引用計數法

      前面說判斷一個對象可以被回收的標準就是是否還有引用指向這個對象,所以最容易想到的便是引用計數法,通過判斷一個對象的引用數量即可,可是這樣無法判斷兩個循環引用的對象。

    • 可達性分析

      可達性分析指的是從目前程序中正在使用的所有引用的對象出發,循環遍歷所有能找到的對象。

      作為出發的點的這些對象,被稱為GC Roots

      GC Roots主要包括以下幾種:

      • 在虛擬機棧(比如棧幀中的本地遍歷表)中引用的對象
      • 靜態屬性引用的對象
      • 常量池引用對象(比如
        String Table
      • 本地方法棧引用的對象
      • Java虛擬機內部的引用對應的對象
      • 所有被同步鎖持有的對象

      總體來說,就是當前程序中正在被使用的引用所指向的對象會被作為GC Roots

    GC Roots出發,依次查找,就能標記出當前存活的對象。但是標記這個過程,細節上依然存在問題:

    • STW : 標記是通過引用查找對象的,如果在標記過程中,用戶修改了引用的對象,那麼會導致不可預估的後果,因此一般標記過程中,是會STW

    • 跨代標記 : 現在的垃圾回收器,大多數都是分代,或者分區域回收的,也就是說,可能進行垃圾回收的時候,不是標記所有的垃圾,而是標記一部分,比如老年代或者新生代。此時就存在一個問題,跨代引用。比如一個新生代的對象,僅僅被一個老年代對象引用的話,對於Yong GC來說,是不會掃描老年代對象的,這個時候就會造成誤判。解決這個誤判的方法便是記憶集(Remembered Set),記憶集通過AOP技術生成寫屏障來維護。

      前面說了從GC Roots開始掃面,那分代收集的,怎麼知道哪些對象是新生代的,哪些對象是老年代的呢?因為GC Roots是包含了所有引用的。後面想想,其實對象的分代信息是存放在對象頭裡面的。在掃描GC Roots的時候,只保留新生代的對象即可。這樣基本能保證掃描到的是新生代對象,然後老年代對新生代引用交給記憶集實現就行(自己的猜測,沒有證據)

      JVM書中說道通過AOP生成的寫屏障會使得只要有更新操作,無論更新的是不是老年代對新生代對象的引用,都會使卡表變髒,不過這樣的代價相對來說是能接受的。

    • GC Roots 需要掃描的引用過多 :隨着現在Java應用越做越大,Java堆也越來越大,GC Roots的掃描是需要STW的,如果每次GC都逐個掃描,會非常的浪費時間。解決這個問題的辦法就是OopMap,使用OopMap記錄應用程序所存放的引用,每次需要GC的時候掃描這個OopMap即可生成對應的GC RootsOopMap通過安全點和安全區域來維護,只有在安全點或安全區域的時候,才更新OopMap和進行垃圾回收。

    • 併發標記過程可能丟失存活的對象 :從CMSG1,都將從GC Roots出發標記存活對象的過程修改成併發的,這樣會需要解決的問題就是標記過程中如果用戶修改了對象的引用,可能會導致本應該存活的對象”丟失“(可以通過三色標記分析),相應的解決方案便是破壞存活對象消失的必要條件,分別是增量更新(Incremental Upate)和原始快照(Snapshot At The Begin,SATB),增量更新破壞的是第一個條件,每插入一個引用,就都記錄下來,而原始快照破壞的是第二個條件,每刪除一個,都將其記錄下來。

      增量更新和併發快照也是通過前面所說的AOP技術生成寫屏障來維護

    通過以上分析以及解決方案,基本明白了怎麼標記那些內存可以回收,接下來需要分析的就是什麼時候開始回收

    什麼時候開始內存回收?

    對於內存回收來說,開始也需要有一定的講究,理論上來說,隨時隨地都可以開始內存回收,但是如果回收時使用的內存過多,會導致GC時間過程,進而STW時間也會很長,如果回收過於頻繁,又會導致吞吐量下降,畢竟每次掃描GC Roots都回STW的。

    同時,前面還說過,對於用戶線程來說,需要將用戶線程運行到安全點,更新對應的OopMap,才能開始垃圾回收。

    因此,對應何時GC,有以下幾點分析:

    • 對於新生代來說,一般新生代滿了(Eden + Survivor1)就會開始進行(Yong/Minor GC

    • 對於老年代來說,一般是老年代滿了了會開始Full/Major GC

      注意:這裏的滿了,需要根據具體的回收器不同,來衡量真正的滿,對於沒有併發過程的GC,老年代滿一般指的是真正到達100%,已經無法分配內存了,對於有併發過程的GC,則需要預留出來空間給用戶線程在併發過程中同時申請內存,如果預留內存過小,則會使用非併發垃圾回收器進行Full GC

      CMS: -XX:CMSInitiatingOccupancyFraction 設置,默認92% (JDK 8),表示當老年代垃圾佔用到92%就開始老年代回收, JDK 9后便無法使用CMS

      G1: -XX:G1ReservePercent設置,默認為10,表示當整個Java堆使用到達90%,就開始回收。同時配合的參數還有-XX:InitiatingHeapOccupancyPercent=n,默認值為45,表示使用率到達45%就啟動標記周期。這裏的GCMixed GC

      一般來說,只有CMS才有Major GC,其他老年代GC都會回收整個Java堆,也稱為Full GC

    • 統計得到的Minor GC晉陞到老年代的平均大小大於老年代剩餘的空間。(JDK 6 之後已經刪除了擔保規則)

    • GC併發失敗(concurrent mode failure): 情況如前面說的,併發標記過程中,又出現了新生代晉陞的情況,但是此時老年代剩下的內存不足夠放下晉陞的對象的時候,會生導致Full GC

      這裏的Full GC和情況1中說到達預留空間的GC不一樣,情況1是正常進行的GC,而這個併發失敗卻是GC過程中出現了異常,一般需要切換到非併發GC,此時性能會大大下降

    • 方法區區域被使用完畢:JDK 8之後將方法區從Perm Gen替換成了元空間,一般來說元空間大小理論上等於本地內存大小,不過元空間有一個默認初始值,到達默認初始值后,會通過Full GC擴大

      注意:G1只有Yong GCMixed GC。沒有Full GC的概念,也就是說如果需要回收方法區的話,只能退化為Serial GC進行Full GC

      CMS可以通過-XX:+CMSClassUnloadingEnabled設置併發回收方法區

    • 最大連續空間裝不下大對象:對於CMS,基於標記-清除算法來說,即使空間足夠,但是由於內存碎片,裝不下分配的大對象時,會進行一次Full GC,對於G1來說,當分配巨型對象的時候,如果在老年代無法找到連續的Humongous的時候,會進行Full GC

    • 用戶執行System.gc(),可以通過-XX:+DisableExplicitGC屏蔽

    怎麼回收這些內存

    最後一步便是怎麼回收這些內存。怎麼回收,書中介紹不多,總體來說有以下三種:

    • 標記-清除(
      Mark-Sweep):最原始的方法,實現簡單,不用移動對象,很容易做到不用
      Stop The Word,但是缺點也很致命,容易產生內存碎片。標記清除的速度一般,
      Mark階段與活對象的數量成正比,
      Sweep階段與整堆大小成正比。目前只有
      CMS使用這種回收方案
    • 標記-複製(
      Mark-Copying):基於標記-清除修改的垃圾回收算法,需要移動對象。 前期標記,然後複製活下來的對象到另一個區域,再總體回收整塊區域。標記複製算法對於新生代這種專門放朝生夕死的對象效率非常高,因為存活下來的對象少,所以
      Mark階段和
      Copying階段花費的時間都會比較少,幾乎所有的分代
      GC新生代都是使用的這種算法
    • 標記-整理(
      Mark-Compact): 基於標記-清除算法修改的垃圾回收器,需要移動對象。前期標記,然後將所有對象移動到一起,再對剩餘的區域進行回收,速度最慢,但是不會產生內存碎片。

    對於新生代使用標記-複製算法,是毋庸置疑的。但是對於老年代,使用標記清除還是標記整理,需要有一定的考量。因為使用標記-清除,不用移動對象,速度會相對來說比較快,但是由於存在內存碎片,無法使用指針碰撞的方式分配內存,而不得不使用“分區空閑分配鏈表”來解決內存分配的問題,這樣會對在內存分配帶來一定的效率影響,而標記-整理算法需要移動對象,特別是對於老年代這種大對象來說,移動這些對象將是一種極為負重的操作,但是標記-整理不會產生內存碎片。

    因此,基於以上考慮,對於CMS這種側重響應速度,致力於減少STW時間的回收器來說,選擇了標記-清除算法,但是由於內存分配是一個非常頻繁的操作,使用”分區空閑分配鏈表”會降低整個垃圾回收器的吞吐量,因此,對於Parllel Scavenge這種注重回收吞吐的垃圾回收器來說,選擇了標記-整理算法。當然,對於G1則是吞吐和響應速度都比較注重,權衡之下,選擇了標記-整理(全局)算法。

    GC的概念,到這裏基本總結完畢,但是,如果僅僅是理論,只是讓我們記着一些概念性的東西,接下來,我會結合CMSG1GC日誌以及《深入理解JVM》第四章的內容,聊一聊如何分析以及查看GC過程,簡單介紹如果進行GC調優。

    個人公眾號:

    不定期更新一些經典Java書籍總結。

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

    【其他文章推薦】

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

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

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

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

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

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

  • 聯合國示警:氣候變遷加劇阿拉伯地區衝突局勢

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

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

    【其他文章推薦】

    ※帶您來了解什麼是 USB CONNECTOR  ?

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

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

    ※綠能、環保無空污,成為電動車最新代名詞,目前市場使用率逐漸普及化

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

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

  • 美國迪士尼致力環保 將停用一次用塑膠吸管

    摘錄自2018年7月27日蘋果日報美國報導

    美國娛樂巨頭華特迪士尼公司( Walt Disney Company)昨天(26日)宣示,明年中期以前,迪士尼樂園等將停止使用一次性塑膠吸管。鑑於塑料垃圾造成的海洋污染日益嚴重,為保護地球環境,歐美正在推廣相同的措施。

    迪士尼指出,此舉將可每年減少1.75億根以上的吸管、1.3億根攪拌棒,強調本次嘗試是迪士尼履行環保責任的一環。

    共同社則報導,據與迪士尼方面簽訂許可協議的東方樂園公司稱,由於位於千葉縣浦安市的東京迪士尼度假區,運營母體不同,因此不受此次華特迪士尼公司決定的影響。然而該公司也指,「正在研究減少塑料廢棄物」。

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

    【其他文章推薦】

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

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

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

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

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

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

  • 葡西法三國簽署能源互聯協議 法國將關閉所有煤電廠

    摘錄自2018年7月28日新華社報導

    葡萄牙、西班牙和法國27日在葡萄牙首都里斯本舉行的第二屆能源互聯峰會上正式簽署了三國能源互聯協議。

    根據協議,西葡兩國同歐洲的能源互聯水平到2020年達到10%,2030年達到15%。此外,歐盟委員會將投資5.7億歐元在西班牙以北的比斯開灣建造一個用於連接西班牙、葡萄牙和法國的電力互聯項目。

    葡萄牙總理科斯塔、西班牙首相桑切斯和法國總統馬克宏在會後舉行了聯合記者會。馬克宏表示,最晚到2022年,法國將關閉所有煤電廠。

    科斯塔說,葡萄牙計劃到2020年使清潔能源占比超過60%,葡萄牙在逐步減少煤電行業投入的同時尋求清潔能源出口。

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

    【其他文章推薦】

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

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

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

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

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

    ※回頭車貨運收費標準