Java并發編程 活鎖

1. 什么是活鎖

活鎖是另一個并發問題,它和死鎖很相似。在活鎖中,兩個或多個線程彼此間一直在轉移狀態,而不像我們上個例子中互相等待。結果就是所有線程都不能執行它們各自的任務。

雖然線程并沒有阻塞,也始終在運行(所以叫做“活鎖”,線程時“活”的),但是程序卻得不到進展,因為線程始終重復做同樣的事

形象比喻
活鎖:馬路中間有條小橋,只能容納一輛車經過,橋兩頭開來兩輛車A和B,A比較禮貌,示意B先過,B也比較禮貌,示意A先過,結果兩人一直謙讓誰也過不去。

2. 演示活鎖

2.1. 相互協作的線程彼此響應從而修改自己狀態,導致無法執行下去。

比如兩個很有禮貌的人在同一條路上相遇,彼此給對方讓路,但是又在同一條路上遇到了。互相之間反復的避讓下去

public class LiveLock {

    static class Spoon {

        private Diner owner;

        public Spoon(Diner owner) {
            this.owner = owner;
        }

        public Diner getOwner() {
            return owner;
        }

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

        public synchronized void use() {
            System.out.printf("%s吃完了!", owner.name);


        }
    }

    static class Diner {

        private String name;
        private boolean isHungry;

        public Diner(String name) {
            this.name = name;
            isHungry = true;
        }

        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHungry) {
                if (spoon.owner != this) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }

                if (spouse.isHungry ) {
                    System.out.println(name + ": 親愛的" + spouse.name + "你先吃吧");
                    spoon.setOwner(spouse);
                    continue;
                }

                spoon.use();
                isHungry = false;
                System.out.println(name + ": 我吃完了");
                spoon.setOwner(spouse);

            }
        }
    }


    public static void main(String[] args) {
        Diner husband = new Diner("老公");
        Diner wife = new Diner("老婆");

        Spoon spoon = new Spoon(husband);

        new Thread(new Runnable() {
            @Override
            public void run() {
                husband.eatWith(spoon, wife);
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                wife.eatWith(spoon, husband);
            }
        }).start();
    }
}

老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
老公: 親愛的老婆你先吃吧
老婆: 親愛的老公你先吃吧
...省略
2.2 消息隊列發生異常重試策略

消息隊列當發生異常的時候,消息消費者回滾事務并把消息放到隊列頭中,然后相同的消息又從隊列頭中被讀到,又會造成異常并再次放入到隊列頭中。如此循壞往復,消費者永遠讀不到隊列中其他的消息。

3.如何解決活鎖問題

1. 選擇一個隨機退讓,使得具備一定的隨機性
public class LiveLockFix {

    static class Spoon {

        private Diner owner;

        public Spoon(Diner owner) {
            this.owner = owner;
        }

        public Diner getOwner() {
            return owner;
        }

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

        public synchronized void use() {
            System.out.printf("%s吃完了!", owner.name);


        }
    }

    static class Diner {

        private String name;
        private boolean isHungry;

        public Diner(String name) {
            this.name = name;
            isHungry = true;
        }

        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHungry) {
                if (spoon.owner != this) {
                    try {
                        Thread.sleep(1);
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                    continue;
                }
                Random random = new Random();
                if (spouse.isHungry && random.nextInt(10) < 9) {
                    System.out.println(name + ": 親愛的" + spouse.name + "你先吃吧");
                    spoon.setOwner(spouse);
                    continue;
                }

                spoon.use();
                isHungry = false;
                System.out.println(name + ": 我吃完了");
                spoon.setOwner(spouse);

            }
        }
    }


    public static void main(String[] args) {
        Diner husband = new Diner("老公");
        Diner wife = new Diner("老婆");

        Spoon spoon = new Spoon(husband);

        new Thread(new Runnable() {
            @Override
            public void run() {
                husband.eatWith(spoon, wife);
            }
        }).start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                wife.eatWith(spoon, husband);
            }
        }).start();
    }
}
image.png
2. 消息隊列重試策略修復
  • 重試次數限定
  • 將不可修復的錯誤不要重試(放在死信隊列或者數據中,跑腳本或人工解決)
  • 將消息放到隊列尾部

特別感謝:

悟空

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容