【问题标题】:Good example of livelock?活锁的好例子?
【发布时间】:2010-11-05 09:37:44
【问题描述】:

我了解什么是活锁,但我想知道是否有人有一个很好的基于代码的示例?而基于代码的,我确实不是的意思是“两个人试图在走廊里超越对方”。如果我再读一遍,我会失去午餐。

【问题讨论】:

标签: concurrency livelock


【解决方案1】:

这是一个非常简单的 Java 活锁示例,其中一对夫妻试图喝汤,但他们之间只有一个勺子。夫妻双方都太客气了,对方还没吃饭就递勺子。

public class Livelock {
    static class Spoon {
        private Diner owner;
        public Spoon(Diner d) { owner = d; }
        public Diner getOwner() { return owner; }
        public synchronized void setOwner(Diner d) { owner = d; }
        public synchronized void use() { 
            System.out.printf("%s has eaten!", owner.name); 
        }
    }
    
    static class Diner {
        private String name;
        private boolean isHungry;
        
        public Diner(String n) { name = n; isHungry = true; }       
        public String getName() { return name; }
        public boolean isHungry() { return isHungry; }
        
        public void eatWith(Spoon spoon, Diner spouse) {
            while (isHungry) {
                // Don't have the spoon, so wait patiently for spouse.
                if (spoon.owner != this) {
                    try { Thread.sleep(1); } 
                    catch(InterruptedException e) { continue; }
                    continue;
                }                       
            
                // If spouse is hungry, insist upon passing the spoon.
                if (spouse.isHungry()) {                    
                    System.out.printf(
                        "%s: You eat first my darling %s!%n", 
                        name, spouse.getName());
                    spoon.setOwner(spouse);
                    continue;
                }
                
                // Spouse wasn't hungry, so finally eat
                spoon.use();
                isHungry = false;               
                System.out.printf(
                    "%s: I am stuffed, my darling %s!%n", 
                    name, spouse.getName());                
                spoon.setOwner(spouse);
            }
        }
    }
    
    public static void main(String[] args) {
        final Diner husband = new Diner("Bob");
        final Diner wife = new Diner("Alice");
        
        final Spoon s = new Spoon(husband);
        
        new Thread(new Runnable() { 
            public void run() { husband.eatWith(s, wife); }   
        }).start();

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

运行程序,你会得到:

Bob: You eat first my darling Alice!
Alice: You eat first my darling Bob!
Bob: You eat first my darling Alice!
Alice: You eat first my darling Bob!
Bob: You eat first my darling Alice!
Alice: You eat first my darling Bob!
...

如果不间断,这将永远持续下去。这是一个活锁,因为 Alice 和 Bob 在无限循环中反复要求对方先走(因此 live)。在陷入僵局的情况下,Alice 和 Bob 都会被冻结,等待对方先走——除了等待(因此 dead),他们不会做任何事情。

【讨论】:

  • getOwner 方法不是也必须同步吗?来自 Effective Java “除非读写同步,否则同步无效”。
  • 他不应该用Thread.join()而不是Thread.sleep(),因为他想等配偶吃饭吗?
  • 我们应该怎么做才能克服这个特定示例中的活锁问题?
  • getOwner 方法必须同步,因为即使 setOwner 已同步,这并不能保证使用 getOwner 的线程(或直接访问字段 owner)会看到更改由执行setOwner 的另一个线程创建。该视频非常仔细地解释了这一点:youtube.com/watch?v=WTVooKLLVT8
  • setOwner 方法不需要使用synchronized 关键字,因为读取和写入是引用变量的原子操作。
【解决方案2】:

考虑一个有 50 个进程槽的 UNIX 系统。

十个程序正在运行,每个程序必须创建 6 个(子)进程。

每个进程创建了4个进程后,原来的10个进程和40个新进程都用完了表。 10 个原始进程中的每一个现在都处于无限循环中,分叉和失败——这恰如其分地出现了活锁的情况。这种情况发生的可能性很小,但有可能发生。

【讨论】:

    【解决方案3】:

    由于没有答案标记为已接受答案,我试图创建活锁示例;

    Original program 是我在 2012 年 4 月写的,用来学习多线程的各种概念。这次我修改了它以创建死锁、竞争条件、活锁等。

    那么我们先来了解一下问题陈述;

    Cookie 制作问题

    有一些配料容器:ChocoPowderContainerWheatPowderContainerCookieMaker 从配料容器中取出一定量的粉末来烘烤 Cookie。如果饼干制造商发现一个容器是空的,它会检查另一个容器以节省时间。并等待 Filler 填充所需的容器。有一个Filler定期检查容器并在容器需要时填充一些数量。

    请查看github上的完整代码;

    让我简要解释一下实现。

    • 我将 Filler 作为守护线程启动。所以它会定期填充容器。要先填充容器,它会锁定容器 -> 检查是否需要一些粉末 -> 填充它 -> 向所有等待它的制造商发出信号 -> 解锁容器。
    • 我创建了 CookieMaker 并设置它最多可以并行烘焙 8 个 cookie。我启动了 8 个线程来烘烤饼干。
    • 每个制造商线程创建 2 个可调用的子线程来从容器中取出粉末。
    • 子线程锁定容器并检查它是否有足够的粉末。如果没有,请等待一段时间。一旦 Filler 填满容器,它就会取出粉末并解锁容器。
    • 现在它完成了其他活动,例如:制作混合物和烘烤等。

    让我们看一下代码:

    CookieMaker.java

    private Integer getMaterial(final Ingredient ingredient) throws Exception{
            :
            container.lock();
            while (!container.getIngredient(quantity)) {
                container.empty.await(1000, TimeUnit.MILLISECONDS);
                //Thread.sleep(500); //For deadlock
            }
            container.unlock();
            :
    }
    

    IngredientContainer.java

    public boolean getIngredient(int n) throws Exception {
        :
        lock();
        if (quantityHeld >= n) {
            TimeUnit.SECONDS.sleep(2);
            quantityHeld -= n;
            unlock();
            return true;
        }
        unlock();
        return false;
    }
    

    Filler 填充容器之前一切正常。但是如果我忘记启动填充器,或者填充器意外离开,子线程会不断改变它们的状态以允许其他制造商去检查容器。

    我还创建了一个守护进程ThreadTracer,它监视线程状态和死锁。这是控制台的输出;

    2016-09-12 21:31:45.065 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:RUNNABLE, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
    2016-09-12 21:31:45.065 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
    WheatPowder Container has 0 only.
    2016-09-12 21:31:45.082 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:RUNNABLE]
    2016-09-12 21:31:45.082 :: [Maker_0:WAITING, Maker_1:WAITING, Maker_2:WAITING, Maker_3:WAITING, Maker_4:WAITING, Maker_5:WAITING, Maker_6:WAITING, Maker_7:WAITING, pool-7-thread-1:TIMED_WAITING, pool-7-thread-2:TIMED_WAITING, pool-8-thread-1:TIMED_WAITING, pool-8-thread-2:TIMED_WAITING, pool-6-thread-1:TIMED_WAITING, pool-6-thread-2:TIMED_WAITING, pool-5-thread-1:TIMED_WAITING, pool-5-thread-2:TIMED_WAITING, pool-1-thread-1:TIMED_WAITING, pool-3-thread-1:TIMED_WAITING, pool-2-thread-1:TIMED_WAITING, pool-1-thread-2:TIMED_WAITING, pool-4-thread-1:TIMED_WAITING, pool-4-thread-2:TIMED_WAITING, pool-3-thread-2:TIMED_WAITING, pool-2-thread-2:TIMED_WAITING]
    

    你会注意到子线程和改变它们的状态和等待。

    【讨论】:

      【解决方案4】:
      package concurrently.deadlock;
      
      import static java.lang.System.out;
      
      
      /* This is an example of livelock */
      public class Dinner {
      
          public static void main(String[] args) {
              Spoon spoon = new Spoon();
              Dish dish = new Dish();
      
              new Thread(new Husband(spoon, dish)).start();
              new Thread(new Wife(spoon, dish)).start();
          }
      }
      
      
      class Spoon {
          boolean isLocked;
      }
      
      class Dish {
          boolean isLocked;
      }
      
      class Husband implements Runnable {
      
          Spoon spoon;
          Dish dish;
      
          Husband(Spoon spoon, Dish dish) {
              this.spoon = spoon;
              this.dish = dish;
          }
      
          @Override
          public void run() {
      
              while (true) {
                  synchronized (spoon) {
                      spoon.isLocked = true;
                      out.println("husband get spoon");
                      try { Thread.sleep(2000); } catch (InterruptedException e) {}
      
                      if (dish.isLocked == true) {
                          spoon.isLocked = false; // give away spoon
                          out.println("husband pass away spoon");
                          continue;
                      }
                      synchronized (dish) {
                          dish.isLocked = true;
                          out.println("Husband is eating!");
      
                      }
                      dish.isLocked = false;
                  }
                  spoon.isLocked = false;
              }
          }
      }
      
      class Wife implements Runnable {
      
          Spoon spoon;
          Dish dish;
      
          Wife(Spoon spoon, Dish dish) {
              this.spoon = spoon;
              this.dish = dish;
          }
      
          @Override
          public void run() {
              while (true) {
                  synchronized (dish) {
                      dish.isLocked = true;
                      out.println("wife get dish");
                      try { Thread.sleep(2000); } catch (InterruptedException e) {}
      
                      if (spoon.isLocked == true) {
                          dish.isLocked = false; // give away dish
                          out.println("wife pass away dish");
                          continue;
                      }
                      synchronized (spoon) {
                          spoon.isLocked = true;
                          out.println("Wife is eating!");
      
                      }
                      spoon.isLocked = false;
                  }
                  dish.isLocked = false;
              }
          }
      }
      

      【讨论】:

        【解决方案5】:

        我修改了@jelbourn 的答案。 当其中一个注意到另一个饿了时,他(她)应该释放勺子并等待另一个通知,这样就会发生活锁。

        public class LiveLock {
            static class Spoon {
                Diner owner;
        
                public String getOwnerName() {
                    return owner.getName();
                }
        
                public void setOwner(Diner diner) {
                    this.owner = diner;
                }
        
                public Spoon(Diner diner) {
                    this.owner = diner;
                }
        
                public void use() {
                    System.out.println(owner.getName() + " use this spoon and finish eat.");
                }
            }
        
            static class Diner {
                public Diner(boolean isHungry, String name) {
                    this.isHungry = isHungry;
                    this.name = name;
                }
        
                private boolean isHungry;
                private String name;
        
        
                public String getName() {
                    return name;
                }
        
                public void eatWith(Diner spouse, Spoon sharedSpoon) {
                    try {
                        synchronized (sharedSpoon) {
                            while (isHungry) {
                                while (!sharedSpoon.getOwnerName().equals(name)) {
                                    sharedSpoon.wait();
                                    //System.out.println("sharedSpoon belongs to" + sharedSpoon.getOwnerName())
                                }
                                if (spouse.isHungry) {
                                    System.out.println(spouse.getName() + "is hungry,I should give it to him(her).");
                                    sharedSpoon.setOwner(spouse);
                                    sharedSpoon.notifyAll();
                                } else {
                                    sharedSpoon.use();
                                    sharedSpoon.setOwner(spouse);
                                    isHungry = false;
                                }
                                Thread.sleep(500);
                            }
                        }
                    } catch (InterruptedException e) {
                        System.out.println(name + " is interrupted.");
                    }
                }
            }
        
            public static void main(String[] args) {
                final Diner husband = new Diner(true, "husband");
                final Diner wife = new Diner(true, "wife");
                final Spoon sharedSpoon = new Spoon(wife);
        
                Thread h = new Thread() {
                    @Override
                    public void run() {
                        husband.eatWith(wife, sharedSpoon);
                    }
                };
                h.start();
        
                Thread w = new Thread() {
                    @Override
                    public void run() {
                        wife.eatWith(husband, sharedSpoon);
                    }
                };
                w.start();
        
                try {
                    Thread.sleep(10000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                h.interrupt();
                w.interrupt();
        
                try {
                    h.join();
                    w.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
        

        【讨论】:

          【解决方案6】:

          jelbourn 的 C# 版本代码:

          using System;
          using System.Runtime.CompilerServices;
          using System.Threading;
          using System.Threading.Tasks;
          
          namespace LiveLockExample
          {
              static class Program
              {
                  public static void Main(string[] args)
                  {
                      var husband = new Diner("Bob");
                      var wife = new Diner("Alice");
          
                      var s = new Spoon(husband);
          
                      Task.WaitAll(
                          Task.Run(() => husband.EatWith(s, wife)),
                          Task.Run(() => wife.EatWith(s, husband))
                          );
                  }
          
                  public class Spoon
                  {
                      public Spoon(Diner diner)
                      {
                          Owner = diner;
                      }
          
          
                      public Diner Owner { get; private set; }
          
                      [MethodImpl(MethodImplOptions.Synchronized)]
                      public void SetOwner(Diner d) { Owner = d; }
          
                      [MethodImpl(MethodImplOptions.Synchronized)]
                      public void Use()
                      {
                          Console.WriteLine("{0} has eaten!", Owner.Name);
                      }
                  }
          
                  public class Diner
                  {
                      public Diner(string n)
                      {
                          Name = n;
                          IsHungry = true;
                      }
          
                      public string Name { get; private set; }
          
                      private bool IsHungry { get; set; }
          
                      public void EatWith(Spoon spoon, Diner spouse)
                      {
                          while (IsHungry)
                          {
                              // Don't have the spoon, so wait patiently for spouse.
                              if (spoon.Owner != this)
                              {
                                  try
                                  {
                                      Thread.Sleep(1);
                                  }
                                  catch (ThreadInterruptedException e)
                                  {
                                  }
          
                                  continue;
                              }
          
                              // If spouse is hungry, insist upon passing the spoon.
                              if (spouse.IsHungry)
                              {
                                  Console.WriteLine("{0}: You eat first my darling {1}!", Name, spouse.Name);
                                  spoon.SetOwner(spouse);
                                  continue;
                              }
          
                              // Spouse wasn't hungry, so finally eat
                              spoon.Use();
                              IsHungry = false;
                              Console.WriteLine("{0}: I am stuffed, my darling {1}!", Name, spouse.Name);
                              spoon.SetOwner(spouse);
                          }
                      }
                  }
              }
          }
          

          【讨论】:

            【解决方案7】:

            我编写了 2 个人通过走廊的示例。两条线一旦意识到它们的方向相同就会相互避开。

            public class LiveLock {
                public static void main(String[] args) throws InterruptedException {
                    Object left = new Object();
                    Object right = new Object();
                    Pedestrian one = new Pedestrian(left, right, 0); //one's left is one's left
                    Pedestrian two = new Pedestrian(right, left, 1); //one's left is two's right, so have to swap order
                    one.setOther(two);
                    two.setOther(one);
                    one.start();
                    two.start();
                }
            }
            
            class Pedestrian extends Thread {
                private Object l;
                private Object r;
                private Pedestrian other;
                private Object current;
            
                Pedestrian (Object left, Object right, int firstDirection) {
                    l = left;
                    r = right;
                    if (firstDirection==0) {
                        current = l;
                    }
                    else {
                        current = r;
                    }
                }
            
                void setOther(Pedestrian otherP) {
                    other = otherP;
                }
            
                Object getDirection() {
                    return current;
                }
            
                Object getOppositeDirection() {
                    if (current.equals(l)) {
                        return r;
                    }
                    else {
                        return l;
                    }
                }
            
                void switchDirection() throws InterruptedException {
                    Thread.sleep(100);
                    current = getOppositeDirection();
                    System.out.println(Thread.currentThread().getName() + " is stepping aside.");
                }
            
                public void run() {
                    while (getDirection().equals(other.getDirection())) {
                        try {
                            switchDirection();
                            Thread.sleep(100);
                        } catch (InterruptedException e) {}
                    }
                }
            } 
            

            【讨论】:

              【解决方案8】:

              抛开轻率的做法不谈,一个已知的例子是尝试检测和处理死锁情况的代码。如果两个线程检测到死锁,并试图互相“让开”,那么他们将最终陷入循环,总是“让开”并且永远无法继续前进。

              “让步”我的意思是他们会释放锁并试图让另一个人获得它。我们可以想象两个线程这样做的情况(伪代码):

              // thread 1
              getLocks12(lock1, lock2)
              {
                lock1.lock();
                while (lock2.locked())
                {
                  // attempt to step aside for the other thread
                  lock1.unlock();
                  wait();
                  lock1.lock();
                }
                lock2.lock();
              }
              
              // thread 2
              getLocks21(lock1, lock2)
              {
                lock2.lock();
                while (lock1.locked())
                {
                  // attempt to step aside for the other thread
                  lock2.unlock();
                  wait();
                  lock2.lock();
                }
                lock1.lock();
              }
              

              抛开竞争条件不谈,我们这里的情况是,如果两个线程同时进入,它们最终会在内部循环中运行而不再继续。显然这是一个简化的例子。一个天真的解决方法是在线程等待的时间中加入某种随机性。

              正确的解决方法是始终尊重lock heirarchy。选择一个获得锁的顺序并坚持下去。例如,如果两个线程总是在 lock2 之前获得 lock1,那么就没有死锁的可能性。

              【讨论】:

              • 是的,我明白了。我正在寻找这样的实际代码示例。问题是“靠边站”是什么意思,它是如何产生这种情况的。
              • 我知道这是一个人为的例子,但这可能会导致活锁吗?由于线程大声运行的时间和安排它们的时间不一致,最终会打开一个窗口,其中一个函数可以同时获取两者,这难道不是更有可能吗?
              • 虽然它不是一个稳定的活锁,因为他们显然最终会打破它,但我认为它非常符合描述
              • 优秀而有意义的例子。
              【解决方案9】:

              jelbourn 代码的 Python 版本:

              import threading
              import time
              lock = threading.Lock()
              
              class Spoon:
                  def __init__(self, diner):
                      self.owner = diner
              
                  def setOwner(self, diner):
                      with lock:
                          self.owner = diner
              
                  def use(self):
                      with lock:
                          "{0} has eaten".format(self.owner)
              
              class Diner:
                  def __init__(self, name):
                      self.name = name
                      self.hungry = True
              
                  def eatsWith(self, spoon, spouse):
                      while(self.hungry):
                          if self != spoon.owner:
                              time.sleep(1) # blocks thread, not process
                              continue
              
                          if spouse.hungry:
                              print "{0}: you eat first, {1}".format(self.name, spouse.name)
                              spoon.setOwner(spouse)
                              continue
              
                          # Spouse was not hungry, eat
                          spoon.use()
                          print "{0}: I'm stuffed, {1}".format(self.name, spouse.name)
                          spoon.setOwner(spouse)
              
              def main():
                  husband = Diner("Bob")
                  wife = Diner("Alice")
                  spoon = Spoon(husband)
              
                  t0 = threading.Thread(target=husband.eatsWith, args=(spoon, wife))
                  t1 = threading.Thread(target=wife.eatsWith, args=(spoon, husband))
                  t0.start()
                  t1.start()
                  t0.join()
                  t1.join()
              
              if __name__ == "__main__":
                  main()
              

              【讨论】:

              • Bugs:在 use() 中,没有使用 print 并且 - 更重要的是 - 饥饿标志未设置为 False。
              【解决方案10】:

              一个真实的(尽管没有确切的代码)示例是两个竞争进程实时锁定以尝试纠正 SQL 服务器死锁,每个进程使用相同的等待重试算法进行重试。虽然这是时机的运气,但我已经看到这种情况发生在具有相似性能特征的不同机器上,以响应添加到 EMS 主题的消息(例如多次保存单个对象图的更新),并且无法控制锁定顺序。

              这种的情况下,一个好的解决方案是让相互竞争的消费者(通过划分无关对象的工作来防止重复处理在链中尽可能高的位置)。

              一个不太理想的(好吧,肮脏的黑客)解决方案是提前打破计时坏运气(处理中的一种强制差异),或者在死锁后通过使用不同的算法或一些随机元素来打破它。这可能仍然存在问题,因为每个进程的锁定顺序可能是“粘性的”,并且这需要一定的最短时间,而不会在等待重试中考虑。

              另一个解决方案(至少对于 SQL Server)是尝试不同的隔离级别(例如快照)。

              【讨论】:

                【解决方案11】:

                这里的一个例子可能是使用定时 tryLock 来获得多个锁,如果您无法获得所有锁,请退出并重试。

                boolean tryLockAll(Collection<Lock> locks) {
                  boolean grabbedAllLocks = false;
                  for(int i=0; i<locks.size(); i++) {
                    Lock lock = locks.get(i);
                    if(!lock.tryLock(5, TimeUnit.SECONDS)) {
                      grabbedAllLocks = false;
                
                      // undo the locks I already took in reverse order
                      for(int j=i-1; j >= 0; j--) {
                        lock.unlock();
                      }
                    }
                  }
                }
                

                我可以想象这样的代码会有问题,因为你有很多线程冲突并等待获得一组锁。但作为一个简单的例子,我不确定这对我来说是否非常引人注目。

                【讨论】:

                • 要成为活锁,您需要另一个线程以不同的顺序获取这些锁。如果所有线程以相同的顺序使用tryLockAll()locks 中的锁,则没有活锁。
                猜你喜欢
                • 2023-03-30
                • 2021-10-18
                • 2010-11-07
                • 2010-12-12
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                • 1970-01-01
                相关资源
                最近更新 更多