如果人生也能存檔——C#中的備忘錄模式

大家好,老胡又和大家見面了。首先承認今天的博客有點標題黨了,人生是沒有存檔,也沒有後悔葯的。有存檔和後悔葯的,那是遊戲,不知道這是不是遊戲讓人格外放鬆的原因之一。

今天恰逢端午放假,就讓我們來試着做一個小遊戲吧,順帶看看備忘錄模式是如何在這種情況下面工作的。

遊戲背景

這是一個簡單的打怪遊戲,有玩家,有怪獸,玩家作為主角光環,有如下三個特殊能力

  • 攻擊怪獸有暴擊幾率
  • 有幾率迴避怪獸攻擊
  • 可以自己治療一定生命值

遊戲實現

角色類
角色基類

首先是角色類,角色類提供玩家和怪獸最基本的抽象,比如血量、攻擊力、攻擊和治療。(對於怪獸來說,治療是沒有提供實現的,壞人肯定不能再治療了)

class Character
{
    public int HealthPoint { get; set; }
    public int AttackPoint { get; set; }        

    public virtual void AttackChracter(Character opponent)
    {
        opponent.HealthPoint -= this.AttackPoint;
        if (opponent.HealthPoint < 0)
        {
            opponent.HealthPoint = 0;
        }
    }

    public virtual void Cure()
    {
		//故意留空給子類實現
    }
}
玩家類

玩家實現了治療功能並且有暴擊幾率。

class Player : Character
{
    private float playerCriticalPossible;
    public Player(float critical)
    {
        playerCriticalPossible = critical;
    }

    public override void AttackChracter(Character opponent)
    {
        base.AttackChracter(opponent);
        Console.WriteLine("Player Attacked Monster");

        Random r = new Random();
        bool critical = r.Next(0, 100) < playerCriticalPossible * 100;
        if (critical)
        {
            base.AttackChracter(opponent);
            Console.WriteLine("Player Attacked Monster again");
        }
    }

    public override void Cure()
    {
        Random r = new Random();
        HealthPoint += r.Next(5, 10);
        Console.WriteLine("Player cured himself");
    }
}
怪獸類

怪獸沒有治療能力但是有一定的幾率丟失攻擊目標。

class Monster : Character
{
    private float monsterMissingPossible;
    public Monster(float missing)
    {
        monsterMissingPossible = missing;
    }

    public override void AttackChracter(Character opponent)
    {
        Random r = new Random();
        bool missing = r.Next(0, 100) < monsterMissingPossible * 100;
        if (missing)
        {
            Console.WriteLine("Monster missed it");
        }
        else
        {
            base.AttackChracter(opponent);
            Console.WriteLine("Monster Attacked player");
        }
    }
}
遊戲類

遊戲類負責實例化玩家和怪獸、記錄回合數、判斷遊戲是否結束,暴露可調用的公共方法給遊戲操作類。

class Game
{
    private Character m_player;
    private Character m_monster;
    private int m_round;
    private float playerCriticalPossible = 0.6f;
    private float monsterMissingPossible = 0.2f;
    
    public Game()
    {
        m_player = new Player(playerCriticalPossible)
        {
            HealthPoint = 15,
            AttackPoint = 2
        };
        m_monster = new Monster(monsterMissingPossible)
        {
            HealthPoint = 20,
            AttackPoint = 6
        };
    }

    public bool IsGameOver => m_monster.HealthPoint == 0 || m_player.HealthPoint == 0;

    public void AttackMonster()
    {            
        m_player.AttackChracter(m_monster);
    }

    public void AttackPlayer()
    {
        m_monster.AttackChracter(m_player);
    }

    public void CurePlayer()
    {
        m_player.Cure();
    }

    public void BeginNewRound()
    {
        m_round++;
    }

    public void ShowGameState()
    {
        Console.WriteLine("".PadLeft(20, '-'));
        Console.WriteLine("Round:{0}", m_round);
        Console.WriteLine("player health:{0}", "".PadLeft(m_player.HealthPoint, '*'));
        Console.WriteLine("monster health:{0}", "".PadLeft(m_monster.HealthPoint, '*'));
    }
}
遊戲操作類

在我們這個簡易遊戲中,沒有UI代碼,遊戲操作類負責在用戶輸入和遊戲中搭建一個橋樑,解釋用戶的輸入。

class GameRunner
{
    private Game m_game;
    public GameRunner(Game game)
    {
        m_game = game;
    }

    public void Run()
    {
        while (!m_game.IsGameOver)
        {
            m_game.BeginNewRound();
            bool validSelection = false;
            while (!validSelection)
            {
            	m_game.ShowGameState();
                Console.WriteLine("Make your choice: 1. attack 2. Cure");
                var str = Console.ReadLine();
                if (str.Length != 1)
                {
                    continue;
                }
                switch (str[0])
                {
                    case '1':
                        {
                            validSelection = true;
                            m_game.AttackMonster();
                            break;
                        }
                    case '2':
                        {
                            validSelection = true;
                            m_game.CurePlayer();
                            break;
                        }
                    default:
                        break;
                }
            }
            if(!m_game.IsGameOver)
            {
                m_game.AttackPlayer();
            }
        }            
    }
}
客戶端

客戶端的代碼就非常簡單了,只需要實例化一個遊戲操作類,然後讓其運行就可以了。

class Program
{
    static void Main(string[] args)
    {
        Game game = new Game();
        GameRunner runner = new GameRunner(game);
        runner.Run();
    }
}

試着運行一下,

看起來一切都好。

 

加上存檔

雖然遊戲可以正常運行,但是總感覺還是少了點什麼。嗯,存檔功能,一個遊戲沒有存檔是不健全的,畢竟,人生雖然沒有存檔,但是遊戲可是有的!讓我們加上存檔功能吧,首先想想怎麼設計。
 

需要存檔的數據

首先我們要明確,有哪些數據是需要存檔的,在這個遊戲中,玩家的生命值、攻擊力、暴擊率;怪獸的生命值、攻擊力和丟失率,遊戲的回合數,都是需要存儲的對象。
 

存檔定義

這是一個需要仔細思考的地方,一般來說,需要考慮以下幾個地方:

  • 存檔需要訪問一些遊戲中的私有字段,比如暴擊率,需要在不破壞遊戲封裝的情況下實現這個功能
  • 存檔自身需要實現信息隱藏,即除了遊戲,其他類不應該訪問存檔的詳細信息
  • 存檔不應該和遊戲存放在一起,以防不經意間遊戲破壞了存檔數據,應該有專門的類存放存檔

 

備忘錄模式出場

這個時候應該是主角出場的時候了。看看備忘錄模式的定義

在不破壞封閉的前提下,捕獲一個對象的內部狀態,並在該對象之外保存這個狀態。這樣以後就可將該對象恢復到原先保存的狀態

再看看UML,

看起來完全符合我們的需求啊,Originator就是遊戲類,知道如何創造存檔和從存檔中恢復狀態,Memento類就是存檔類,Caretaker是一個新類,負責保存存檔。

經過思考,我們決定採取備忘錄模式,同時加入以下措施:

  • 將存檔定義為遊戲中的私有嵌套類,這樣存檔可以毫無壓力的訪問遊戲中的私有字段,同時外界永遠沒有辦法去實例化或者嘗試通過轉型來獲得這個類,完美的保護了存檔類
  • 存檔類是一個簡單的數據集合,不包含任何其他邏輯
  • 添加一個存檔管理器,可以放在遊戲操作類中,可以通過它看到我們當前有沒有存檔
  • 存檔放在存檔管理器中
  • 存檔實現一個空接口,在存檔管理器中以空接口形式出現,這樣外部類在訪問存檔的時候,僅能看到這個空接口。而在遊戲類內部,我們在使用存檔之前先通過向下轉型實現類型轉換(是的,向下轉型不怎麼好,但是偶爾可以用一下)
代碼實現
空接口
interface IGameSave
{

}
私有嵌套存檔類

該類存放在game裏面,無壓力地在不破壞封裝的情況下訪問game私有字段

private class GameSave : IGameSave
{
    public int PlayerHealth { get; set; }
    public int PlayerAttack { get; set; }
    public float PlayerCritialAttackPossible { get; set; }
    public int MonsterHealth { get; set; }
    public int MonsterAttack { get; set; }
    public float MonsterMissingPossible { get; set; }
    public int GameRound { get; set; }
}
創建存檔和從存檔恢復

game中添加創建存檔和從存檔恢復的代碼,在從存檔恢復的時候,使用了向下轉型,因為從存檔管理器讀出來的只是空接口而已

public IGameSave CreateSave()
{
    var save = new GameSave()
    {
        PlayerHealth = m_player.HealthPoint,
        PlayerAttack = m_player.AttackPoint,
        PlayerCritialAttackPossible = playerCriticalPossible,
        MonsterAttack = m_monster.AttackPoint,
        MonsterHealth = m_monster.HealthPoint,
        MonsterMissingPossible = monsterMissingPossible,
        GameRound = m_round
    };
    Console.WriteLine("game saved");
    return save;
}

public void RestoreFromGameSave(IGameSave gamesave)
{
    GameSave save = gamesave as GameSave;
    if(save != null)
    {
        m_player = new Player(save.PlayerCritialAttackPossible) { HealthPoint = save.PlayerHealth, AttackPoint = save.PlayerAttack };
        m_monster = new Player(save.MonsterMissingPossible) { HealthPoint = save.MonsterHealth, AttackPoint = save.MonsterAttack };
        m_round = save.GameRound;
    }
    Console.WriteLine("game restored");
}	
存檔管理器類

添加一個類專門管理存檔,此類非常簡單,只有一個存檔,要支持多存檔可以考慮使用List

    class GameSaveStore
    {
        public IGameSave GameSave { get; set; }
    }
在遊戲操作類添加玩家選項

首先在遊戲操作類中添加一個存檔管理器

private GameSaveStore m_gameSaveStore = new GameSaveStore();

接着修改Run方法添加用戶操作

public void Run()
{
    while (!m_game.IsGameOver)
    {
        m_game.BeginNewRound();
        bool validSelection = false;
        while (!validSelection)
        {
            m_game.ShowGameState();
            Console.WriteLine("Make your choice: 1. attack 2. Cure 3. Save 4. Load");
            var str = Console.ReadLine();
            if (str.Length != 1)
            {
                continue;
            }
            switch (str[0])
            {
                case '1':
                    {
                        validSelection = true;
                        m_game.AttackMonster();
                        break;
                    }
                case '2':
                    {
                        validSelection = true;
                        m_game.CurePlayer();
                        break;
                    }
                case '3':
                    {
                        validSelection = false;
                        m_gameSaveStore.GameSave = m_game.CreateSave();
                        break;
                    }
                case '4':
                    {
                        validSelection = false;
                        if(m_gameSaveStore.GameSave == null)
                        {
                            Console.WriteLine("no save to load");
                        }
                        else
                        {
                            m_game.RestoreFromGameSave(m_gameSaveStore.GameSave);
                        }
                        break;
                    }
                default:
                    break;
            }
        }
        if(!m_game.IsGameOver)
        {
            m_game.AttackPlayer();
        }
    }            
}

注意,上面的3和4是新添加的存檔相關的操作。試着運行一下。

看起來一切正常,這樣我們就使用備忘錄模式,完成了存檔讀檔的功能。

 

結語

這就是備忘錄模式的使用,如果大家以後遇到這種場景

  • 想要保存狀態,又不想破壞封裝
  • 需要把狀態保存到其他地方

那麼就可以考慮使用這個模式。

遊戲有存檔,人生沒存檔,願我們把握當下,天天努力。
祝大家端午安康,下次見。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

JAVA設計模式 3【創建型】理解工廠模式與抽象工廠模式

上一節我們已經學習了原型模式,稍微複習一下:通過重寫Object 類的clone() 方法實現淺克隆,淺克隆也要實現Cloneable 標記接口。而深克隆則是將對象通過序列化和反序列化 的方式進行創建和還原。

本小結將學習的是工廠模式,這個模式在平時是特別常用的,還需好好理解。我也將舉一些例子給大家

從生活出發

假設我們今天不想做飯,想出去吃飯、肯定選定一家好吃的川菜館,然後我們找好位置坐下,給廚師說,我想吃糖醋排骨。 稍微等待幾分鐘、菜就可以做好,然後給你呈上來。

這是一個很經典的例子,我們平時也經常有這樣的實際問題,你發現了么?

  • 我只需要告訴廚師菜名,我要吃啥就行了
  • 我不關注這個菜是怎麼生產的 new()

開始擼代碼

/**
 * 抽象產品 菜
 */
public interface FoodProduct {
    void show();
}
------------
/**
 * 具體產品
 */
public class HongShao implements FoodProduct {
    @Override
    public void show() {
        System.out.println("紅燒排骨");
    }
}
------------
public class TangCu implements FoodProduct {
    @Override
    public void show() {
        System.out.println("糖醋魚");
    }
}

創建廚房工廠

創建一個廚房類,廚房可以用來生產食物產品,我們只需要告訴廚房,這裡是通過id 編號的形式告訴廚房的。我們只需要告訴廚房所需要的食物 而不關心這個食物產品是如何創建出來的。

public class KitchenFactory {
    /**
     * id=1 上菜紅燒肉 id=2 糖醋魚
     * @param id
     */
    public FoodProduct cooking(int id) {
        if (1 == id) {
            return new HongShao();
        } else {
            return new TangCu();
        }
    }
}
KitchenFactory kitchen = new KitchenFactory();
FoodProduct food = kitchen.cooking(1);
food.show();
--------
紅燒排骨

理解工廠模式

通過這個簡單的例子,我們可以學習到:

  • 無需關注對象是如何創建的。只需通過指定的關鍵字 就能拿到我需要的產品,這就是簡單工廠模式。

抽象工廠

http://c.biancheng.net/view/1351.html

抽象工廠,就是簡單工廠的抽象版、如何理解呢?我們上面的工廠(廚房)它已經是一個確定的對象 了。而抽象工廠,則是在廚房 的基礎上,再次衍生出一個接口,我們的廚房 則是這個抽象類的一個具體實例化。

代碼源於生活

我又要開始舉栗子了。請細細品

我們都知道小米 小米既可以生產手機 也可以生產電器用品 那麼這就是一個很好的例子。

小米抽象工廠 是一個巨大的工廠,它裏面有小米手機工廠 以及小米電器工廠 而不同的工廠,則生產不同的產品

代碼教學開始

首先,我們得需要一個抽象工廠,這個工廠可以包含手機工廠和電器工廠。

public interface AbstractFactory {
    /**
     * 創建手機工廠
     * @return
     */
    PhoneFactory phoneFactory();
    /**
     * 創建電器工廠
     * @return
     */
    ElectricalFactory electricalFactory();
}

當然,手機工廠不知道是具體哪個工廠,反正它可以做一些事情,比如創建手機。

public interface PhoneFactory {
    /**
     * 手機工廠可以做的事情
     */
    void show();
}
-----------
public interface ElectricalFactory {
    /**
     * 電器工廠可以生產電器
     */
    void show();
}

上手實際創建一個小米工廠


public class XiaoMiFactory implements AbstractFactory {
    
    @Override
    public PhoneFactory phoneFactory() {
        return new XiaoMiPhoneFactory();
    }
    @Override
    public ElectricalFactory electricalFactory() {
        return new XiaoMiElectricalFactory();
    }
}
-------------
public class XiaoMiElectricalFactory implements ElectricalFactory {
    @Override
    public void show() {
        System.out.println("小米電器工廠可以生產電器。。比如小米掃地機器人");
    }
}
-------------
public class XiaoMiPhoneFactory implements PhoneFactory {
    @Override
    public void show() {
        System.out.println("小米手機工廠可以生產小米手機。。。");
    }
}

當然,小米工廠實現抽象工廠,那小米工廠就必須要包含兩個子工廠,手機工廠和電器工廠了。我們也可以創建一個華為工廠,其實是一樣的道理。

AbstractFactory factory = new XiaoMiFactory();

PhoneFactory phoneFactory = factory.phoneFactory();
phoneFactory.show();
--------
小米手機工廠可以生產小米手機。。。

我們從創建的小米工廠中拿出小米手機工廠 然後再執行手機工廠可以做事情,抽象工廠,就是在上面的簡單工廠的層次上進行了再次的抽象,將具體的工廠進行抽象。

假設我們按照上面的邏輯。對於一個工廠,我想要一部手機 我給工廠說一聲就行了。我不關心這個手機 是如何生產出來的。我該怎麼操作?

我稍微將之前的幾個接口作為稍微的改造。

public interface PhoneFactory {
    /**
     * 手機工廠可以做的事情
     */
    PhoneProduct show();
}
---------------
public class XiaoMiPhoneFactory implements PhoneFactory {
    @Override
    public PhoneProduct show() {
        return new PhoneProduct(1, "小米10 Pro");
    }
}
----------
//手機對象
public class PhoneProduct {

    private int id;

    private String name;
}

我們可以創建這樣一個訪問器,通過訪問器對象,將我們需要的對象名稱傳入就好比下單 它能自動匹配工廠,並且調用工廠創建產品 的方法,將我們需要的產品進行創建。

public class AbstractFactoryClient {

    public PhoneProduct createPhone(String name) {

        AbstractFactory factory = null;

        if ("xiaomi" == name) {
            factory = new XiaoMiFactory();
        } else {
            factory = new HuaweiFactory();
        }
        PhoneFactory phoneFactory = factory.phoneFactory();
        
        return phoneFactory.show();
    }
}

測試一下

AbstractFactoryClient factoryClient = new AbstractFactoryClient();
PhoneProduct product = factoryClient.createPhone("xiaomi");
System.out.println(product);
-----------
手機銘牌 編號:1,型號:小米10 Pro

小結

學習完本節,是否對於工廠模式和抽象工廠有了一個深入的了解呢?工廠模式其實在平時的代碼中,還是比較常用的。所以還是需要更加努力學習和使用!

代碼示例

https://gitee.com/mrc1999/Dev-Examples

參考

http://c.biancheng.net/view/1351.html

歡迎關注

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

kubernetes資源均衡器Descheduler

背景

Kubernetes中的調度是將待處理的pod綁定到節點的過程,由Kubernetes的一個名為kube-scheduler的組件執行。調度程序的決定,無論是否可以或不能調度容器,都由其可配置策略指導,該策略包括一組規則,稱為謂詞和優先級。調度程序的決定受到其在第一次調度時出現新pod時的Kubernetes集群視圖的影響。由於Kubernetes集群非常動態且狀態隨時間而變化,因此可能需要將已經運行的pod移動到其他節點,原因如下:

  • 一些節點不足或過度使用。
  • 原始調度決策不再適用,因為在節點中添加或刪除了污點或標籤,不再滿足pod / node親和性要求。
  • 某些節點發生故障,其pod已移至其他節點。
  • 新節點將添加到群集中。

因此,可能會在群集中不太理想的節點上安排多個pod。Descheduler根據其政策,發現可以移動並移除它們的pod。請注意,在當前的實現中,descheduler不會安排更換被驅逐的pod,而是依賴於默認的調度程序。

Descheduler二次調度

GitHub地址:https://github.com/kubernetes-sigs/descheduler

下面是重要的配置

  • configmap.yaml
---
apiVersion: v1
kind: ConfigMap
metadata:
  name: descheduler-policy-configmap
  namespace: kube-system
data:
  policy.yaml: |
    apiVersion: "descheduler/v1alpha1"
    kind: "DeschedulerPolicy"
    strategies:
      "RemoveDuplicates":
         enabled: true
      "RemovePodsViolatingInterPodAntiAffinity":
         enabled: true
      "LowNodeUtilization":
         enabled: true
         params:
           nodeResourceUtilizationThresholds:
             thresholds:
               "cpu" : 30
               "memory": 40
               "pods": 50
             targetThresholds:
               "cpu" : 20
               "memory": 25
               "pods": 15

RemoveDuplicates策略

該策略發現未充分利用的節點,並且如果可能的話,從其他節點驅逐pod,希望在這些未充分利用的節點上安排被驅逐的pod的重新創建。此策略的參數配置在nodeResourceUtilizationThresholds

節點的利用率低是由可配置的閾值決定的thresholdsthresholds可以按百分比為cpu,內存和pod數量配置閾值 。如果節點的使用率低於所有(cpu,內存和pod數)的閾值,則該節點被視為未充分利用。目前,pods的請求資源需求被考慮用於計算節點資源利用率。

還有另一個可配置的閾值,targetThresholds用於計算可以驅逐pod的潛在節點。任何節點,所述閾值之間,thresholds並且targetThresholds被視為適當地利用,並且不考慮驅逐。閾值targetThresholds也可以按百分比配置為cpu,內存和pod數量。

簡單的說:thresholds是沒有達到資源使用的node視為資源使用率低可以分配做為預選節點, targetThresholds是已經滿足這個條件的node資源緊張要把上面的pod遷移。

  • cronjob.yaml
---
apiVersion: batch/v1beta1
kind: CronJob
metadata:
  name: descheduler-cronjob
  namespace: kube-system
spec:
  #定時任務時間可調 schedule:
"*/10 * * * *" concurrencyPolicy: "Forbid" jobTemplate: spec: template: metadata: name: descheduler-pod spec: priorityClassName: system-cluster-critical containers: - name: descheduler image: aveshagarwal/descheduler #image: us.gcr.io/k8s-artifacts-prod/descheduler:v0.10.0 volumeMounts: - mountPath: /policy-dir name: policy-volume command: - "/bin/descheduler" args: - "--policy-config-file" - "/policy-dir/policy.yaml" - "--v" - "3" restartPolicy: "Never" serviceAccountName: descheduler-sa volumes: - name: policy-volume configMap: name: descheduler-policy-configmap
  • rbac.yaml
---
kind: ClusterRole
apiVersion: rbac.authorization.k8s.io/v1
metadata:
  name: descheduler-cluster-role
  namespace: kube-system
rules:
- apiGroups: [""]
  resources: ["events"]
  verbs: ["create", "update"]
- apiGroups: [""]
  resources: ["nodes"]
  verbs: ["get", "watch", "list"]
- apiGroups: [""]
  resources: ["pods"]
  verbs: ["get", "watch", "list", "delete"]
- apiGroups: [""]
  resources: ["pods/eviction"]
  verbs: ["create"]
---
apiVersion: v1
kind: ServiceAccount
metadata:
  name: descheduler-sa
  namespace: kube-system
---
apiVersion: rbac.authorization.k8s.io/v1
kind: ClusterRoleBinding
metadata:
  name: descheduler-cluster-role-binding
  namespace: kube-system
roleRef:
  apiGroup: rbac.authorization.k8s.io
  kind: ClusterRole
  name: descheduler-cluster-role
subjects:
  - name: descheduler-sa
    kind: ServiceAccount
    namespace: kube-system

kubectl apply -f 執行上面三個文件,查看日誌如有滿足再次調度條件的 會重新發起二次調度均衡node資源。

 

 

 

 

 

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

【其他文章推薦】

※超省錢租車方案

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

※回頭車貨運收費標準

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

綠色認證棕櫚油需要支持 監督機構訂新規 企業採購量不足將受罰

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

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

防空污 印度排燈節只准燃放綠色環保鞭炮

摘錄自2019年10月22日中央通訊社印度報導

印度國家首都區這幾天空氣污染持續在「不佳」(poor)狀態,為避免排燈節燃放鞭炮惡化空氣品質,印度最高法院今天(22日)宣布,只有兩種綠色環保、被稱為anar和phuljhari的無聲鞭炮,可以在27日排燈節(Diwali)燃放,其他吵雜的鞭炮和煙火都被禁止。

被允許在印度宗教節日排燈節燃放的鞭炮將有印度政府認證標章和快速響應矩陣圖碼(QR code),警方呼籲消費者在購買鞭炮時要認明標章和QR code。新德里電視台(NDTV)引述德里警察局發言人藍大瓦(MS Randhawa)說,警方已組成檢查小組,如果有商家販售其他類型的鞭炮和煙火,警方將會採取法律行動。

主管環保的印度中央政府部長瓦德漢(Harsh Vardhan)表示,綠色環保鞭炮的懸浮粒子含量減少25%到30%,二氧化碳排放減少50%,有助降低鞭炮燃放對空氣品質的影響。

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案

「人造葉」設備啟蒙自光合作用 可生產乾淨燃料「合成氣」

摘錄自2019年10月22日科技新報報導

科學家開發出太陽能燃料領域一種新設備,可以利用陽光將水、二氧化碳轉化成用來發電的燃料「合成氣」,因轉化過程有如植物光合作用,而被暱稱為「人造葉」。

合成氣是一種利用氣化技術(gasification technology)將煤炭、石油、生質物等含碳原料轉化成一氧化碳與氫氣、然後混合而成的產物,本身可充當燃料氣體,主要用途為發電,也可用來生產藥品、塑膠、肥料等。

而英國劍橋大學團隊花費多年時間設計出人造葉,包含二種先進的鈣鈦礦光吸收劑和一種由鈷製成的分子催化劑,前者作用類似植物中吸收陽光的分子,鈣鈦礦可提供更高的電壓與電流驅動化學反應;後者則是代替鉑或銀,不僅成本較低,催生一氧化碳的表現也比其他催化劑好。

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

【其他文章推薦】

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

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

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

※超省錢租車方案

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

瑞典智庫:氣候變遷加劇衝突 阻礙和平建設

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

瑞典智庫斯德哥爾摩國際和平研究所(SIPRI)23日公布報告指出,氣候變遷對當前及未來的和平建設構成嚴峻挑戰,並且可能加劇衝突。該研究所氣候變遷計畫高級研究員科蘭普(Florian Krampe)指出,報告顯示安全形勢正隨著氣候變遷改變,這次許多發現也適用於其他衝突。

索馬利亞被形容為「世界氣候變遷脆弱度最高的國家之一」。報告顯示,索國數十年來的衝突,因為一系列嚴重乾旱而加劇,加深國家建設進展的壓力,在多個層面對聯合國駐索馬利亞援助團的工作構成更多挑戰。科蘭普並未斷言氣候變遷本身可能造成衝突,但他認為證據明確顯示「氣候變化增加衝突及暴力的可能性」。

根據聯合國難民事務高級專員公署(UNHCR),由於武裝衝突及重複不斷的乾旱,索馬利亞境內現今約有260萬人流離失所,逾80萬人仍離鄉背井滯留鄰國。

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

【其他文章推薦】

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

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

※回頭車貨運收費標準

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

※超省錢租車方案

Java併發包JUC核心原理解析

CS-LogN思維導圖:記錄CS基礎 面試題
開源地址:https://github.com/FISHers6/CS-LogN

JUC

分類

線程管理

  • 線程池相關類

    • Executor、Executors、ExecutorService
    • 常用的線程池:FixedThreadPool、CachedThreadPool、ScheduledThreadPool、SingleThreadExecutor
  • 能獲取子線程的運行結果

    • Callable、Future、FutureTask

併發流程管理

  • CountDwonLatch、CyclicBarrier、Semaphore、Condition

實現線程安全

  • 互斥同步(鎖)

    • Synchronzied、及工具類Vector、Collections
    • Lock接口的相關類:ReentrantLock、讀寫鎖
  • 非互斥同(原子類)

    • 原子基本類型、引用類型、原子升級、累加器
  • 併發容器

    • ConcurrentHashMap、CopyOnWriteArrayList、BlockingQueue
  • 無同步與不可變方案

    • final關鍵字、ThreadLocal棧封閉

線程池

使用線程池的作用好處

  • 降低資源消耗

    • 重複利用已創建的線程降低線程創建和銷毀造成的消耗
  • 提高響應速度

    • 任務到達,可以不需要等到線程創建就能立即執行
  • 提高線程的可管理性

    • 使用線程池可以進行統一的分配,調優和監控

線程池的參數

  • corePoolSize、maximumPoolSize、keepAliveTime、workQueue、threadFactory、handler

  • 圖示

常用線程池的創建與規則

  • 線程添加規則

    • 1.如果線程數量小於corePoolSize,即使工作線程處於空閑狀態,也會創建一個新線程來運行新任務,創建方法是使用threadFactory

    • 2.如果線程數量大於corePoolSize但小於maximumPoolSize,則將任務放入隊列

    • 3.如果workQueue隊列已滿,並且線程數量小於maxPoolSize,則開闢一個非核心新線程來運行任務

    • 4.如果隊列已滿,並且線程數大於或等於maxPoolSize,則拒絕該任務,執行handler

    • 圖示(分別與3個參數比較)

  • 常用線程池

    • newFixedThreadPool

      • 創建固定大小的線程池,使用無界隊列會發生OOM
    • newSingleThreadExecutor

      • 創建一個單線程的線程池,線程數為1
    • newCachedThreadPool

      • 創建一個可緩存的線程池,60s會回收部分空閑的線程。採用直接交付的隊列 SynchronousQueue ,隊列容量為0,來一個創建一個線程
    • newScheduledThreadPool

      • 創建一個大小無限的線程池。此線程池支持定時以及周期性執行任務的需求
  • 如何設置初始化線程池的大小?

    • 可根據線程池中的線程
      處理任務的不同進行分別估計

      • CPU 密集型任務

        • 大量的運算,無阻塞
          通常 CPU 利用率很高
          應配置盡可能少的線程數量
          設置為 CPU 核數 + 1
      • IO 密集型任務

        • 這類任務有大量 IO 操作
          伴隨着大量線程被阻塞
          有利於并行提高CPU利用率
          配置更多數量: CPU 核心數 * 2
  • 使用線程池的注意事項

    • 1.避免任務堆積(無界隊列會OOM)、2.避免線程數過多(cachePool直接交付隊列)、3.排查線程泄露

線程池的狀態和常用方法

  • 線程池的狀態

    • RUNNING(接受並處理任務中)、
      SHUTDOWN(不接受新任務但處理排隊任務)、
      STOP(不接受新任務 也不處理排隊任務 並中斷正在進行的任務)、
      TIDYING、TEMINATED(運行完成)
  • 線程池停止

    • shutdown

      • 通知有序停止,先前提交的任務務會執行
    • shutdownNow

      • 嘗試立即停止,忽略隊列里等待的任務

線程池的源碼解析

  • 線程池的組成

    • 1.線程池管理器
      2.工作線程
      3.任務隊列:無界、有界、直接交付隊列
      4.任務接口Task

    • 圖示

  • Executor家族

    • Executor頂層接口,只有一個execute方法

    • ExecutorService繼承了Executor,增加了一些新的方法,比如shutdown擁有了初步管理線程池的功能方法

    • Executors工具類,來創建,類似Collections

    • 圖示

  • 線程池實現任務復用的原理

    • 線程池對線程作了包裝,不需要啟動線程,不需要重複start線程,只是調用已有線程固定數量的線程來跑傳進來的任務run方法

    • 添加工作線程

      • 4步:1. 獲取線程池狀態、4.判斷是否進入任務隊列 3.根據狀態檢測是否增加工作線程4.執行拒絕handler
    • 重複利用線程執行不同的任務

面試題

  • 為什麼要使用線程池?
  • 如何使用線程池?
  • 線程池有哪些核心參數?
  • 初始化線程池的大小的如何算?
  • shutdown 和 shutdownNow 有什麼區別?

ThreadLocal

ThreadLocal的作用好處

  • 為每個線程提供存儲自身獨立的局部變量,實現線程間隔離
  • 即:達到線程安全,不需要加鎖節省開銷,減少參數傳遞

ThreadLocal的使用場景

  • 1.每個線程需要一個獨享的對象,如 線程不安全的工具類,(線程隔離)
  • 2.每個線程內需要保存全局變量,如 攔截器中的用戶信息參數,讓不同方法直接使用,避免參數傳遞過多,(局部變量安全,參數傳遞)

ThreadLocal的實現原理

  • 每個 Thread 維護着一個 ThreadLocalMap 的引用;ThreadLocalMap 是 ThreadLocal 的內部類,用 Entry 來進行存儲;key就對應一個個ThreadLocal

  • get方法:取出當前線程的ThreadLocalMap,然後調用map.getEntry方法,把ThreadLocal作為key參數傳入,取出對應的value

  • set方法:往 ThreadLocalMap 設置ThreadLocal對應值
    initalValue方法:延遲加載,get的時候設置初始化

  • 圖示

缺陷注意

  • value內存泄漏

    • 原因:ThreadLocal 被 ThreadLocalMap 中的 entry 的 key 弱引用。如果 ThreadLocal 沒有被強引用, 那麼 GC 時 Entry 的 key 就會被回收,但是對應的 value 卻不會回收,就會造成內存泄漏

    • 解決方案:每次使用完 ThreadLocal,都調用它的 remove () 方法,清除value數據。

    • 源碼圖示

面試題

  • ThreadLocal 的作用是什麼?
  • 講一講ThreadLocal的實現原理(組成結構)
  • ThreadLocal有什麼風險?

Callable與Future

Callable

  • 引入目的

    • 解決Runnable的缺陷

      • 1.沒有返回值,因為返回類型為void
      • 2.不能拋出異常,因為沒有繼承Execption接口
  • 是什麼如何使用

    • Callable是類似於Runnable的接口,實現Callable接口的類和實現Runnable的類都是可被其它線程執行的任務。
    • 實現Call方法,可以有返回值

Future

  • 引入目的

    • Future的核心思想是:一個方法的計算過程可能非常耗時,一直在原地等待方法返回,顯然不明智。可以把該計算過程放到子線程去執行,並通過Future去控制方法的計算過程,在計算出結果后直接獲取該結果。
  • 常用方法

    • get方法:獲取結果,在沒有計算出結果前,會進入阻塞態
  • 使用場景

    • 用法1:線程池的submit方法返回Future對象
    • 用法2:用FutureTask來創建Future
  • 注意點

    • 當for循環批量獲取future的結果時,容易block,get方法調用時應使用timeout限制
    • Future和Callable的生命周期不能後退
  • Callable和Future的關係

    • Future相當於一個存儲器,它存儲未來call()任務方法的返回值結果

    • 可以用Future.get方法來獲取Callable接口的執行結果,在call()未執行完畢之前沒調用get的線程會被阻塞

    • 線程池傳入Callable,submit返回Future,get獲取值

  • FutureTask

    • FutureTask是一種包裝器,可以把Callable轉化成Future和Runnable,它同時實現了二者的接口。所以既可以作為Runnable任務被線程執行,又可以作為Future得到Callable的返回值

    • 圖示

final與不變性

什麼是不變性(Immutable)

  • 如果對象在被創建后,狀態就不能被修改,那麼它就是不可變的。
  • 具有不變性的對象一定是線程安全的,我們不需要對其採取任何額外的安全措施,也能保證線程安全。

final的作用

  • 類防止被繼承、方法防止被重寫、變量防止被修改
  • 天生是線程安全的(因為不能修改),而不需要額外的同步開銷

final的3種用法:修飾變量、方法、類

  • final修飾變量

    • 被final修飾的變量,意味着值不能被修改。
      如果變量是對象,那麼對象的引用不能變,但是對象自身的內容依然可以變化。

    • 賦值時機

      • 屬性被聲明為final后,該變量則只能被賦值一次。且一旦被賦值,final的變量就不能再被改變,如論如何也不會變。

      • 區分為3種

        • final instance variable(類中的final屬性)

          • 等號右側、構造函數、初始化代碼塊
        • final static variable(類中的static final屬性)

          • 等號右側、靜態初始化代碼塊
        • final local variable(方法中的final變量)

          • 使用前複製即可
      • 為什麼規定時機

        • 根據JVM對類和成員變量、靜態成員變量的加載規則來看:如果初始化不賦值,後續賦值,就是從null變成新的賦值,這就違反final不變的原則了!
  • final修飾方法(構造方法除外)

    • 不可被重寫,也就是不能被override,即便是子類有同樣名字的方法,那也不是override,與static類似*
  • final修飾類

    • 不可被繼承,例如典型的String類就是final的

棧封閉 實現線程安全

  • 在方法里新建的局部便咯,實際上是存儲在每個線程私有的棧空間,線程棧不能被其它線程訪問,所以不會有線程安全問題,如ThreadLocal

面試題

CAS

什麼是CAS

  • 我認為V的值應該是A,如果是的話那我就把它改成B,如果不是A(說明被別人修改過了),那我就不修改了,避免多人同時修改導致出錯。
  • CAS有三個操作數:內存值V、預期值A、要修改的值B,當且僅當預期值A和內存值V相同時,才將內存值修改為B,否則什麼都不做。最後返回現在的V值。
  • 最終執行CPU處理機提供的的原子指令

缺點

  • ABA問題

    • 我認為 V的值為A,有其它線程在這期間修改了值為B,但它又修改成了A,那麼CAS只是對比最終結果和預期值,就檢測不出是否修改過
  • CAS+自旋,導致自旋時間過長

  • 改進:通過版本號的機制來解決。每次變量更新的時候,版本號加 1,如AtomicStampedReference的compareAndSet ()

應用場景

  • 1 樂觀鎖:數據庫、git版本號; 自旋 2 concurrentHashMap:CAS+自旋
    3 原子類

CAS底層實現

  • 通過Unsafe獲取待修改變量的內存遞增,
    比較預期值與結果,調用彙編cmpxchg指令

以AtomicInteger為例,分析在Java中是如何利用CAS實現原子操作的?

  • 1.使用Unsafe類拿到value的內存遞增,通過偏移量 直接操作內存數據
  • 2.Unsafe的getAndAddInt方法,使用CAS+自旋嘗試修改數據
  • CAS的參數通過 預期值 與 實際拿到的值進行比較,相同就修改,不相同就自旋
  • Unsafe提供硬件級別的原子操作,最終調用原子彙編指令的cmpxchg指令

鎖的分類

Lock鎖接口

  • 簡介

    • Lock鎖是一種工具,用於控制對共享資源的訪問
    • 如:ReentrantLock
  • Lock和Synchronized的異同點

    • 相同點

      • 都能達到線程安全的目的
    • 不同點

      • Lock 有比 synchronized 更精確的線程語義和更好的性能;高級功能

      • 1 實現原理不同

        • Synchronized 是關鍵字,屬於 JVM 層面,底層是通過 monitorenter 和 monitorexit 完成,依賴於 monitor 對象來完成;
        • Lock 是 java.util.concurrent.locks.lock 包下的,底層是AQS
      • 2 靈活性不同

        • Synchronized 代碼完成之後系統自動讓線程釋放鎖;ReentrantLock 需要用戶手動釋放鎖,加鎖解鎖靈活
      • 3 等待時是否可以中斷

        • Synchronized 不可中斷,除非拋出異常或者正常運行完成;ReentrantLock 可以中斷。一種是通過 tryLock,另一種是 lockInterruptibly () 放代碼塊中,調用 interrupt () 方法進行中斷;
  • 可見性

    • happens-before規則約定;Lock與Synchronized一致都可以保證可見性
    • 即下一個線程加鎖時可以看到上一個釋放鎖的線程發生的所有操作

樂觀鎖與悲觀鎖

  • 悲觀鎖(互斥同步鎖)

    • 思想

      • 鎖住數據,讓別人無法訪問,確保數據萬無一失
    • 實例

      • Synchronized、Lock相關類
      • 應用實例:select 把庫鎖住,屬於悲觀鎖,更新期間其它人不能修改
    • 缺點

      • 在阻塞和喚醒性能開銷大(用戶態核心態切換、上下文切換、檢查是否有線程被喚醒)
      • 持有鎖的線程被阻塞時無法釋放,有可能造成永久阻塞
  • 樂觀鎖

    • 思想

      • 認為自己在操作數據時不會有其它線程干擾,所以不需要鎖住被操作對象
      • 在更新數據的時候,去對比修改期間有沒有被其它人改變過,沒改過就正常修改(類似CAS思想)
      • 樂觀鎖一般由CAS實現:CAS在一個原子操作內把數據對比且交換,在此期間不能被打斷的
    • 實例

      • 原子類、併發容器
      • 應用實例:數據庫版本號控制、git版本號
    • 優缺點對比

      • 悲觀鎖一旦切換就不用再考慮切換CPU等操作了,一勞永逸,開銷固定
      • 樂觀鎖,會一步步嘗試自旋來獲取鎖,自旋開銷
  • 對比

可重入鎖與非可重入鎖

  • 什麼是可重入

    • 拿到鎖的線程又請求這把鎖,允許通過
  • 可重入的好處

    • 避免死鎖(拿到鎖的線程內部又請求該鎖)
    • 提升封裝性,避免一次次加鎖
  • 可重入鎖ReentrantLock與非可重入鎖ThreadPoolExecutor的Worker類對比

公平鎖和非公平鎖

  • 公平鎖

    • 介紹

      • 公平鎖是指多個線程按照申請鎖的順序來獲取鎖,線程直接進入隊列中排隊,隊列中的第一個線程才能獲得鎖
    • 優點

      • 公平鎖的優點是公平執行,等待鎖的線程不會餓死
    • 缺點

      • 缺點是整體吞吐效率相對非公平鎖要低,等待隊列中除第一個線程以外的所有線程都會阻塞,CPU喚醒阻塞線程的開銷比非公平鎖大
  • 非公平鎖

    • 介紹

      • 多個線程加鎖時直接嘗試獲取鎖,獲取不到才會到等待隊列的隊尾等待。但如果此時鎖剛好可用,那麼這個線程可以無需阻塞直接獲取到鎖,所以非公平鎖有可能出現后申請鎖的線程先獲取鎖的場景
    • 優點

      • 減少喚起線程的開銷,整體的吞吐效率高,因為線程有幾率不阻塞直接獲得鎖,CPU不必喚醒所有線程
    • 缺點

      • 處於等待隊列中的線程可能會餓死,或者等很久才會獲得鎖
  • 優缺點對比

  • 源碼分析

共享鎖和排他鎖

  • 排他鎖

    • 介紹

      • 排他鎖,獲取鎖后,既能讀又能寫,但是此時其它線程不能獲取這個鎖了,只能由當前線程修改數據獨享鎖,保證了線程安全,synchronized
      • 又稱為 獨佔鎖,寫鎖
  • 共享鎖

    • 介紹

      • 獲取共享鎖后,其它線程也可以獲取共享鎖完成讀操作,但都不能修改刪除數據
      • 又成為 讀鎖
  • ReentrantReadWriteLock

    • 讀寫鎖的作用

      • 共享鎖減少了多個讀都加鎖的開銷,線程也安全
      • 在讀的地方使用讀鎖,在寫的地方寫鎖;在沒有寫鎖的情況下,讀操作無阻塞,提高程序效率
    • 讀寫鎖的規則

      • 要麼可以多讀,要麼只能一寫
      • 讀寫鎖只是一把鎖,可以通過兩個方式鎖定:讀鎖定 或 寫鎖定
    • 一把鎖兩種方式鎖定

      • readLock() 讀鎖
      • writeLock() 寫鎖
    • 讀線程插隊策略(非公平下)

      • 寫鎖可以隨時插隊,參与競爭
      • 讀鎖僅在等待隊列頭節點為寫的時候不允許插隊;當隊頭為讀的時候可以去插隊。
    • 鎖升級

      • 引入場景

        • 假如一開始持有寫鎖,但我寫需求完了,後面都是讀的需求了,如果還佔用寫鎖就浪費資源開銷
      • 策略

        • 只允許降級,不允許升級
    • 適合場景

      • 讀多寫少,提高併發效率

自旋鎖和阻塞鎖

  • 阻塞鎖

    • 思想

      • 沒拿到鎖之前,會直接把線程阻塞,直到被喚醒
    • 開銷缺陷

      • 阻塞或喚醒一個線程需要操作系統切換CPU狀態來完成,恢復現場等需要消耗處理機時間;如果同步代碼塊的內容過於簡單,狀態轉換消耗的時間有可能比用戶代碼執行的時間還要長,得不償失
  • 自旋鎖

    • 思想

      • 讓當前搶鎖失敗的線程進行自旋,如果在自旋完成后前面鎖定同步資源的線程已經釋放了鎖,那麼當前線程就可以不必阻塞而是直接獲取同步資源,從而避免切換線程的開銷
    • 開銷缺陷

      • 自旋佔用時間長,起始開銷低,但消耗CPU資源開銷會線性增長
  • 源碼分析

    • atomic包下的類基本都是自旋鎖的實現

    • AtomicInteger的實現:自旋鎖實現原理是CAS,Atomic調用Unsafe進行自增add的源碼中的do-while循環就是一個自旋操作,使用CAS如果修改過程中遇到其它線程修改導致沒有秀嘎四成功,就在while里死循環,直至修改成功

    • 圖示

  • 適用場景

    • 多核、臨界區短小

可中斷鎖

  • 介紹

    • 線程B等待線程A釋放鎖時,線程B不想等待了,想處理其它事情,我們可以中斷它
  • 使用場景

    • synchronized是不可中斷鎖,Lock是可中斷鎖(tryLock(time) 和 lockInterruptibly)響應中斷

鎖優化

  • JDK1.6 后對synchronized鎖的優化

    • JDK1.6 對鎖的實現引入了大量的優化,如偏向鎖、輕量級鎖、自旋鎖、適應性自旋鎖、鎖消除、鎖粗化等技術來減少鎖操作的開銷。

    • 偏向鎖

      • 無競爭條件下,消除整個同步互斥,連CAS都不操作;即這個鎖會偏向於第一個獲得它的線程
    • 輕量級鎖

      • 無競爭條件下,通過CAS消除同步互斥,減少傳統的重量級鎖使用操作系統互斥量產生的性能消耗。
    • 重量級鎖

      • 互斥同步鎖
    • 自旋鎖

      • 為了減少線程狀態改變帶來的消耗,不停地執行當前線程
    • 自適應自旋鎖

      • 自旋的時間不固定了,如設置自旋次數
    • 鎖消除

      • 不可能存在共享數據競爭的鎖進行消除;
    • 鎖粗化

      • 鎖粗化就是增大鎖的作用域;如解決加鎖操作在循環體內的頻開銷
  • 寫代碼時的優化

    • 縮小同步代碼塊、如不要鎖住方法
    • 減少鎖的請求次數, 如一批一批請求
    • 參考LongAdder的思想,每個段有自己的計數器,最後才合併

面試題

  • 什麼是公平鎖?什麼是非公平鎖?
  • 自旋鎖解決什麼問題?自旋鎖的原理是什麼?自旋的缺點?
  • 說說 JDK1.6 之後的synchronized 關鍵字底層做了哪些優化,可以詳細介紹一下這些優化嗎?
  • 說說 synchronized 和 java.util.concurrent.locks.Lock 的異同?

原子類atomic包

原子類的作用

  • 原子類的作用和鎖類似,都是為了保證併發下線程安全
  • 粒度更細,變量級別
  • 效率更高,除了高度競爭外

原子類的種類

  • Atomic*基本類型原子類:AtomicInteger、AtomicLong、AtomicBoolean
  • Atomic*Array數組類型原子類:AtomicIntegerArray、AtomicLongArray、AtomicReferenceArray
  • Atomic*Reference 引用類型原子類:AtomicReference等
  • AtomicIntegerFiledUpdate等升級類型原子類
  • Adder累加器、Accumlator累加器

AtomicInteger

  • 常用方法

    • get、getAndSet、getAndIncrement、compareAndSet(int expect,int update)
  • 實現原理

    • AtomicInteger 內部使用 CAS 原子語義來處理加減等操作。CAS通過判斷內存某個位置的值是否與預期值相等,如果相等則進行值更新
    • CAS 是內部是通過 Unsafe 類實現,而 Unsafe 類的方法都是 native 的,在 JNI 里是藉助於一個 CPU 指令完成的,屬於原子操作。
  • 缺點

    • 循環開銷大。如果 CAS 失敗,會一直嘗試
    • 只能保證單個共享變量的原子操作,對於多個共享變量,CAS 無法保證,引出原子引用類
    • 用CAS存在 ABA 問題

Adder累加器

  • 引入目的/改進思想

    • AtomicLong在每一次加法都要flush和refresh主存,與JMM內存模型有關。工作線程之間不能直接通信,需要通過主內存間接通信
  • 設計思想

    • Java8引入,高併發下LongAdder比AtomicLong效率高,本質是空間換時間
    • 競爭激烈時,LongAdder把不同線程對應到不同的Cell單元上進行修改,降低了衝突的概率,是多段鎖的理念,提高了併發性
    • 每個線程都有自己的一個計數器,不存在競爭
    • sum源碼分析:最終把每一個Cell的計數器與base主變量相加

面試題

  • AtomicInteger 怎麼實現原子操作的?
  • AtomicInteger 有哪些缺點?

併發容器

ConcurrentHashMap

  • 集合類歷史

    • Vector的方法被synchronizd修飾,同步鎖;不允許多個線程同時執行。併發量大的時候性能不好
    • Hashtable是線程安全的HashMap,方法也是被synchronized修飾,同步但併發性能差
    • Collections工具類,提高的有synchronizedList和synchronizedMap,代碼內使用sync互斥變量加鎖
  • 為什麼需要

    • 為什麼不用HashMap

      • 1.多線程下同時put碰撞導致數據丟失
      • 2.多線程下同時put擴容導致數據丟失
      • 3.死循環造成的CPU100%
    • 為什麼不用Collection.synchronizedMap

      • 同步鎖併發性能低
  • 數據結構與併發策略

    • JDK1.7

      • 數組+鏈表,拉鏈法解決衝突
      • 採用分段鎖,每個數組結點是一個獨立的ReentrantLock鎖,可以支持同時併發寫
    • JDK1.8

      • 數組+鏈表+紅黑樹,拉鏈法和樹化解決衝突
      • 採用CAS+synchronized鎖細化
    • 1.7到1.8改變後有哪些優點

      • 1.數據結構由鏈表變為紅黑樹,樹查詢效率更高
      • 2.減少了Hash碰撞,1.7拉鏈法
      • 3.保證了併發安全和性能,分段鎖改成CAS+synchronized
      • 為什麼超過8要轉為紅黑樹,因為紅黑樹存儲空間是結點的兩倍,經過泊松分佈,8衝突概率低
  • 注意事項

    • 組合操作線程不安全,應使用putIfAbsent提供的原子性操作

CopyOnWriteArrayList

  • 引入目的

    • Vector和SynchronizedList鎖的粒度太大併發效率低,並且迭代時無法編輯exceptMod!=Count
  • 適合場景

    • 讀多寫少,如黑名單管理每日更新
  • 讀寫規則

    • 是對讀寫鎖的升級:讀取完全不用加鎖,讀時寫入也不會阻塞。只有寫入和寫入之間需要同步
  • 實現原理

    • 創建數據的新副本,實現讀寫分離,修改時整個副本進行一次複製,完成后最後再替換回去;由於讀寫分離,舊容器不變,所以線程安全無需鎖
    • 在計算機內存中修改不直接修改主內存,而是修改緩存(cache、對拷貝的副本進行修改),再進行同步(指針指向新數據)。
  • 缺點

    • 1.數據一致性問題,拷貝不能保證數據實時一致,只能保證數據最終一致性
    • 2.內存佔用問題,寫複製機制,寫操作時內存會同時駐紮兩個對象的內存

併發隊列

  • 為什麼使用隊列

    • 用隊列可以在線程間傳遞數據,緩存數據
    • 考慮鎖等線程安全問題的重任轉移到了“隊列”上
  • 併發隊列關係圖示

  • BlockingQueue阻塞隊列

    • 阻塞隊列是局由自動阻塞功能的隊列,線程安全;take方法移除隊頭,若隊列無數據則阻塞直到有數據;put方法插入元素,如果隊列已滿就無法繼續插入則阻塞直到隊列里有了空閑空間

    • ArrayBlockQueue

      • 有界可指定容量、可公平
      • Put源碼加鎖,可中斷的上鎖方法。沒滿才可以入隊,否則一直await等待。
    • LinkedBlockingQueue

      • 無界容量為MAX_VALUE,內部結構Node
      • 使用了兩把鎖take鎖和put鎖互補干擾
    • PriorityBlockingQueue

      • 支持優先級,無界隊列
    • SynchronousQueue

      • 直接傳遞的隊列,容量0,效率高線程池的CacheExecutorPool使用其作為工作隊列
    • DelayQueue

      • 無界隊列,根據延遲時間排序
  • 非阻塞隊列

    • ConcurrentLinkedQueue

      • 使用鏈表作為隊列存儲結構
      • 使用Unsafe的CAS非阻塞方法來實現線程安全,無需阻塞,適合對性能要求較高的併發場景
  • 選擇合適的隊列

    • 邊界上看

      • ArrayBlockQueue有界;LinkedBlockQueue無界適合容量大容量激增
    • 內存上看

      • ArrayBlockQueue內部結構是array,從內存存儲上看,連續存儲更加整齊。而LinkedBlockQueue採用鏈表結點,可以非連續存儲。
    • 吞吐量上看

      • 從性能上看LinkedBlockQueue的put鎖和鎖分開,鎖粒度更細,所以優於ArrayBlockQueue

總結併發容器對比

  • 分為3類:Concurrent、CopyOnWrite、Blocking*
  • Concurrent*的特定是大部分使用CAS併發;而CopyOnWrite通過複製一份元數據寫加鎖實現;Blocking通過ReentLock鎖底層AQS實現

併發流程控制工具類

控制併發流程工具類的作用

  • 控制併發流程的工具類,作用是幫助程序員更容易讓線程之間相互配合,來滿足業務邏輯

  • 併發工具類圖示

CountDownLatch倒計時門閂

  • 作用(事件)

    • 一個線程等多個線程、或多個線程等一個線程完成到達,才能繼續執行
  • 常用方法

    • 構造函數中傳入倒數值、await、countDown

Semaphore信號量

  • 作用

    • 用來限制管理數量有限的資源的使用情況,相當於一定數量的“許可證”
  • 常用方法

    • 構造函數中傳入數量、acquire、release

Condition條件對象

  • 作用

    • 等待條件滿足才放行,否則阻塞;一個鎖可以對應多個條件
  • 常用方法

    • lock.newCondition、await、signal

CyclicBarrier循環柵欄

  • 作用(線程)

    • 多個線程互相等待,直到達到同一個同步點(屏障),再繼續一起執行
  • 常用方法

    • 構造函數中傳入個數、await

AQS

AQS的作用

  • AQS是一個用於構建鎖、同步器、協作工具類的框架,有了AQS后,更多的協作工具類都可以很方便的寫出來

AQS的應用場景

  • Exclusive(獨佔)

    • ReentrantLock 公平和非公平鎖
  • Share(共享)

    • Semaphore/CountDownLatch/CyclicBarrier

AQS原理解析

  • 核心三要素

    • 1.sate

      • 使用一個 int 成員變量來表示同步狀態 state,被volatile修飾,會被併發修改,各方法如getState、setState等使用CAS保證線程安全
      • 在ReentrantLock中,表示可重入的次數
      • 在Semaphore中,表示剩餘許可證信號的數量
      • 在CountDownLatch中,表示還需要倒數的個數
    • 2.控制線程搶鎖和配合的FIFO隊列

      • 獲取資源線程的排隊工作
    • 3.期望協作工具類去實現的“獲取/釋放”等喚醒分配的方法策略

  • AQS的用法

    • 第一步:寫一個類,想好協作的邏輯,實現獲取/釋放方法
    • 第二步:內部寫一個Sync類繼承AbstractQueueSynchronizer
    • 第三步:Sync類根據獨佔還是共享重寫tryAcquire/tryRelease或tryAcquireShared和tryReleaseShared等方法,在之前寫的獲取/釋放方法中調用AQS的acquire/release或則Shared方法

AQS應用實例源碼解析

  • AQS在CountDownLatch的應用

    • 內部類Sync繼承AQS

    • 1.state表示門閂倒數的count數量,對應getCount方法獲取

    • 2.釋放方法,countDown方法會讓state減1,直到減為0時就喚醒所有線程。countDown方法調用releaseShared,它調用sync實現的tryReleaseShared,其使用CAS+自旋鎖,來實現安全的計數-1

    • 3.阻塞方法,await會調用sync提供的aquireSharedInterruptly方法,當state不等於0時,最終調用LockUpport的park,它利用Unsafe的park,native方法,把線程加入阻塞隊列

    • 總結

  • AQS在Semphore的應用

    • state表示信號量允許的剩餘許可數量

    • tryAcquire方法,判斷信號量大於0就成功獲取,使用CAS+自旋改變state狀態。如果信號量小於0了,再請求時tryAcquireShared返回負數,調用aquireSharedInterruptly方法就進入阻塞隊列

    • release方法,調用sync實現的releaseShared,會利用AQS去阻塞隊列喚醒一個線程

    • 總結

  • AQS在ReentrantLock的應用

    • state表示已重入的次數,獨佔鎖權保存在AQS的Thread類型的exclusiveOwnerThread變量中
    • 釋放鎖: unlock方法調用sync實現的release方法,會調用tryRelease,使用setState而不是CAS來修改重入次數state,當state減到0完全釋放鎖
    • 加鎖lock方法:調用sync實現的lock方法。CAS嘗試修改鎖的所有權為當前線程,如果修改失敗就要調用acquire方法再次嘗試獲取,acquire方法調用了AQS的tryAcquire,這個實現在ReentantLock的裏面,獲取失敗加入到阻塞隊列

通過AQS自定義同步器

  • 自定義同步器在實現時只需要根據業務邏輯需求,實現共享資源 state 的獲取與釋放方式策略即可
  • 至於具體線程等待隊列的維護(如獲取資源失敗入隊 / 喚醒出隊等),AQS 已經在頂層實現好了

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

【其他文章推薦】

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

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

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

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

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

※超省錢租車方案

程序員不能說自己不行啊

二哥,最近我剛進了一家公司,之前跟你說過,培訓出身剛剛畢業,打算在北京打拚。最近進公司,給安排了工作,今天第一次沒人帶,自己上手搞代碼,搞不出,明明挺簡單的功能,自己還是做不出,不知道從哪裡學習,想趕快熟悉工作,可是自己的能力不行,在地鐵上常看二哥原創的作品,平常积極在看,超級希望能自己學到本事,但自己的能力真的有點問題,工作搞不完,害怕被問,害怕任務完不成被辭退。

以上是讀者西瓜向我提的一個問題,我覺得挺具有代表性的,所以決定拉出來單獨寫一篇文章答疑解惑一下。

可以肯定的一點是,任何時候都要說自己不行啊,尤其是男性同胞,可以認慫,但是“不行”這個兩個字千萬不要輕易說出口,為什麼?你懂吧?

人的能力各有不同,但如果你自己都不自信,那又能做好什麼事情呢?心理建設非常重要。

記得之前看一個短片,一個小男孩跳了無數次,都無法越過障礙物,但是呢,他身邊的同學一直為他加油吶喊,小男孩呢,也從來沒有放棄的打算,最後的結果我都快看哭了,他真的跳過去了,他出色地完成了自我挑戰。

他的成功,離不開同學們的鼓勵,但更重要的是他鍥而不舍的精神,心裏素質比一般的成年人都要強大。

我現在已經為人父了,雖然我一直標榜自己只有 18 歲,但叫二叔的讀者真的越來越多,我已經逆來順受了。在我的教育觀念里,我覺得我家女兒最優秀的一點品質,就是,如果她喜歡一件事,她就會主動去鑽研,去摸索,在沒有任何外人的幫助下。

你比如說,現在比較流行的平衡車,就是不帶腳踏板和鏈條的自行車。一開始,我想給她報個班,至少有個老師教教,對吧?

但是,她的表現完全出乎我的意料,我只要把車買回來,放到她的面前,怎麼騎,完全靠她自己去體驗。一開始小心翼翼,很保本,但她不滿足於現狀,就找一些小坡騎,然後是再大一點的坡,就這樣,挑戰一次又一次,自己就完全掌握了騎行的技術。

對於我來說,我沒有騎平衡車的經驗,小時候也沒有這玩意。我能做的除了買車,就是給她鼓勵,摔倒了沒事,哭了也沒事,有些事情,痛苦的同時,伴隨着挑戰和突破。

對於我們成年人來說,其實道理都懂的,但人與人之間的差距之所以拉開,除了選擇的正確有否,最大的因素我想就是,你有沒有自己主動去做

西瓜說自己明明很簡單的功能,就是做不出來。這種感覺我也有過,即便是現在有了十年多的編程經驗,仍然在某些時刻感到举手無措,無從下手。

對,這就是為什麼人要終身學習的原因啊。我們做不出來,除了思維上、認知上的局限性,另外一個重要的點就在於,你有沒有經驗。

對於新人來說,經驗肯定是欠缺的,這點毫無疑問,對吧?但是只要公司招你進去了,無論是不是培訓班出身,負責任的公司都會給你充足的時間和空間去進步,就看你自己有沒有主動。

我大三出去實習的時候,公司要求我做一個計算器,那時候覺得好難啊,因為加減乘除,再帶上小括號,運算是有優先級的,還要考慮到小括號的自動補齊,對於那時候菜得一筆的我來說,真特么難啊。

但能怎麼辦?做不出來就意味着要被辭退,那只有一個辦法,就是上網搜,找別人的例子模仿,拆分,融化,把它變成是自己的。

那時候,我還不會玩 GitHub、碼雲和開源中國,私下里主動學習的地方只有一個,好像是叫編程入門網,現在已經沒有了。我就是照着上面的例子,一個個手敲,當你例子敲多了,很多編程知識就融會貫通了。

現在好了,優秀的案例數不勝數。我的兩個好朋友,macrozheng 開源了他的電商平台 mall,江南一點雨開源了他的微人事系統 vhr,這兩個開源項目我一直強烈推薦新手去下載到本地,去學習。

很多時候,對於編程天賦一般的我們來說,不需要主動去造輪子,我們只需要去發現輪子,對吧?

我在一開始做 Web 管理系統的時候,找了一個企業級的開源系統,叫做 DWZ,不知道有沒有讀者朋友用過,當年非常火,我們公司的後台管理系統現在還在用,雖然說界面已經很古董了,但對於我們公司來說,足夠用了。

這套 DWZ 就封裝了很多前端組件,對於我一個 Java 程序員來說,非常友好,直接可以上手操作,如果一些組件不滿足,我就去改造。改造的過程中,就積攢了大把解決問題的實戰經驗,這是彌足珍貴的。

我在《Web全棧開發進階之路》這本書里,就借鑒了不少 DWZ 的優秀思想。不要覺得不會造輪子是可恥的,會用輪子也是真本領啊。

就西瓜來說,平常喜歡看我的原創文,那我文章涉及到的例子有沒有去敲呢?如果你敲了,你就會發現,文章里涉及到的例子能解決大部分新人在工作中遇到的問題,直接把這些作為自己的工具包,下次遇到拿來即用就可以了。

對於 Java 程序員來說,JDK 的原生 API 不能滿足需求的話,還有很多第三方的類庫,比如說 Apache 的,封裝了大量常用的工具類和方法。前提條件是,你必須得知道有這些東西,如果不知道的話,那就無從下手了,對吧?

那怎麼見多識廣呢?這就回到了之前所說的,你得去練,動手去練,無論是書本里的,還是文章里的,還是開源項目里的,你得去手操一遍,不要眼高手低,敲多了,自然就形成了自己解決問題的思路和方法。

擔心自己被辭退是一件好事,這會督促我們前進,對吧?有的人,有自驅力,不需要外力的干預就能奮發圖強,有的人,就需要一條看不見的鞭子抽打着,才會有前進的動力。

別懷疑自己,真的,人嘛,總是有能力強弱之分的,要學會接納自己,像二哥一樣自信點,腳踏實地,一點一點去進步,當你取得一點成績的時候就把這些當做是里程碑,隨着時間的推移,你就會發現,自己變禿了,不不不,變強了。

加油,西瓜!

如果覺得文章對你有點幫助,請微信搜索「 沉默王二 」第一時間閱讀。

本文已收錄 GitHub,傳送門~ ,裏面更有大廠面試完整考點,歡迎 Star。

我是沉默王二,一枚有顏值卻靠才華苟且的程序員。關注即可提升學習效率,別忘了三連啊,點贊、收藏、留言,我不挑,嘻嘻

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

【其他文章推薦】

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

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

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

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

※超省錢租車方案