Good example of livelock?

后端 未结 11 1742
后悔当初
后悔当初 2021-01-29 18:44

I understand what livelock is, but I was wondering if anyone had a good code-based example of it? And by code-based, I do not mean \"two people trying to get p

相关标签:
11条回答
  • 2021-01-29 18:45

    As there is no answer marked as accepted answer, I have attempted to create live lock example;

    Original program was written by me in Apr 2012 to learn various concept of multithreading. This time I have modified it to create deadlock, race condition, livelock etc.

    So let's understand the problem statement first;

    Cookie Maker Problem

    There are some ingredient containers: ChocoPowederContainer, WheatPowderContainer. CookieMaker takes some amount of powder from ingredient containers to bake a Cookie. If a cookie maker finds a container empty it checks for another container to save time. And waits until Filler fills the required container. There is a Filler who checks container on regular interval and fills some quantity if a container needs it.

    Please check the complete code on github;

    Let me explain you implementation in brief.

    • I start Filler as daemon thread. So it'll keep filling containers on regular interval. To fill a container first it locks the container -> check if it needs some powder -> fills it -> signal all makers who are waiting for it -> unlock container.
    • I create CookieMaker and set that it can bake up to 8 cookies in parallel. And I start 8 threads to bake cookies.
    • Each maker thread creates 2 callable sub-thread to take powder from containers.
    • sub-thread takes a lock on a container and check if it has enough powder. If not, wait for some time. Once Filler fills the container, it takes the powder, and unlock the container.
    • Now it completes other activities like: making mixture and baking etc.

    Let's have a look in the code:

    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;
    }
    

    Everything runs fine until Filler is filling the containers. But if I forget to start the filler, or filler goes on unexpected leave, sub-threads keep changing their states to allow other maker to go and check the container.

    I have also create a daemon ThreadTracer which keeps watch on thread states and deadlocks. This the output from console;

    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]
    

    You'll notice that sub-threads and changing their states and waiting.

    0 讨论(0)
  • 2021-01-29 18:49
    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;
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-29 18:54

    I coded up the example of 2 persons passing in a corridor. The two threads will avoid each other as soon as they realise their directions are the same.

    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) {}
            }
        }
    } 
    
    0 讨论(0)
  • 2021-01-29 18:57

    I modify the answer of @jelbourn. When one of them notices that the other is hungry, he(her) should release the spoon and wait another notify, so a livelock happens.

    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();
            }
        }
    }
    
    0 讨论(0)
  • 2021-01-29 18:59

    A real (albeit without exact code) example is two competing processes live locking in an attempt to correct for a SQL server deadlock, with each process using the same wait-retry algorithm for retrying. While it's the luck of timing, I have seen this happen on separate machines with similar performance characteristics in response to a message added to an EMS topic (e.g. saving an update of a single object graph more than once), and not being able to control the lock order.

    A good solution in this case would be to have competing consumers (prevent duplicate processing as high up in the chain as possible by partitioning the work on unrelated objects).

    A less desirable (ok, dirty-hack) solution is to break the timing bad luck (kind of force differences in processing) in advance or break it after deadlock by using different algorithms or some element of randomness. This could still have issues because its possible the lock taking order is "sticky" for each process, and this takes a certain minimum of time not accounted for in the wait-retry.

    Yet another solution (at least for SQL Server) is to try a different isolation level (e.g. snapshot).

    0 讨论(0)
  • 2021-01-29 19:00

    One example here might be using a timed tryLock to obtain more than one lock and if you can't obtain them all, back off and try again.

    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();
          }
        }
      }
    }
    

    I could imagine such code would be problematic as you have lots of threads colliding and waiting to obtain a set of locks. But I'm not sure this is very compelling to me as a simple example.

    0 讨论(0)
提交回复
热议问题