当前位置:网站首页>Alibaba interview question: multi thread related

Alibaba interview question: multi thread related

2022-06-24 00:36:00 Abin abin

One 、 Preface

  • Interview questions about thread synchronization , Those who consider the idea of solving problems from the perspective of time or priority , Basically not at all
  • Always from join sleep Considered ,99.99% It's not right . The thread ends gracefully , Generally do not use 【interrupt: interrupt 】、【stop: stop it 】、 【resume: recovery 】

Two 、 subject

1、 The first question is : Point out the difference between the following two procedures , And analyze ( From the perspective of multithreading )

final class Accumulator {
    
			// Thread 2 may be reading this value without stopping 
            private double result = 0.0D;

            public void addAll(double[] values) {
    
                for (double value : values) {
    
                	// Thread one is constantly accumulating 
                    result += value;
                }
            }
        }
        
final class Accumulator2 {
    
		 private double result = 0.0D;
		
		  public void addAll(double[] values) {
    
		      double sum = 0.0D;
		      for (double value : values) {
    
		          sum += value;
		      }
		      result += sum;
		  }
}
  • From the question we can We can see that if we consider multiple threads , So the problem with the first method is : There must be a thread to read this result The probability reading is an intermediate value , Not the final result value , because for The cycle is not over yet
  • The problem of the second method : When a thread is constantly reading this :result When I read it that way, it was : Initial or final value , Never read the middle value .
  • If you consider thread safety , The second is much safer than the first

Conclusion

  • The second way of writing is less likely to be inconsistent than the first way of writing , Because before the method is completed , Dirty data in intermediate state cannot be read

  • Minimize the exposure of the intermediate state of the thread computing process

  • Can use a small range of variables , Don't use a wide range of variables

2、 I know : The case of philosophers' dining problem

 Insert picture description here

What is the dining problem of philosophers

  • The dining problem of philosophers is a classic problem in computer science , Used to demonstrate multithreading synchronization in parallel computing (Synchronization) Problems arising from .
  • There are five philosophers , They share a round table , Sit on five chairs . There are five bowls and five chopsticks on the round table , Usually a philosopher thinks , When I'm hungry, I try to use the left side 、 Right is closest to his chopsticks , Only when he has two chopsticks can he eat . After dinner , Put down your chopsticks and continue to think .

Their thinking

  • First write a code to simulate this question type , Let's start with two classes : philosopher 、 chopsticks
  • Chopsticks : With numbering attribute
  • Philosophers : Left chopsticks 、 Right hand chopsticks 、 Number attributes

Code parsing

Scheme 1 :

 public class T01_DeadLock {
    
         public static void main(String[] args) {
    
             ChopStick cs0 = new ChopStick();
             ChopStick cs1 = new ChopStick();
             ChopStick cs2 = new ChopStick();
             ChopStick cs3 = new ChopStick();
             ChopStick cs4 = new ChopStick();
             Philosohper p0 = new Philosohper("p0", 0, cs0, cs1);
             Philosohper p1 = new Philosohper("p1", 1, cs1, cs2);
             Philosohper p2 = new Philosohper("p2", 2, cs2, cs3);
             Philosohper p3 = new Philosohper("p3", 3, cs3, cs4);
             Philosohper p4 = new Philosohper("p4", 4, cs4, cs0);
             p0.start();
             p1.start();
             p2.start();
             p3.start();
             p4.start();
         }

         public static class Philosohper extends Thread {
    
             private ChopStick left, right;
             private int index;

             public Philosohper(String name, int index, ChopStick left, ChopStick right) {
    
                 this.setName(name);
                 this.index = index;
                 this.left = left;
                 this.right = right;
             }
		/** * 1、 Lock the right side first , Lock the left side again, and then it indicates that someone has finished , Because you can only eat with two chopsticks  * 2、 Be careful : This method is problematic , When a deadlock occurs , Then no one can get chopsticks , Everyone has to starve to death  **/
		@Override
         public void run () {
    
         	// Lock the right chopsticks 
            synchronized (left) {
    
                Thread.sleep(1 + index);
                // Lock the left chopsticks 
                synchronized (right) {
    
                    SleepHelper.sleepSeconds(1);
                    // Print this to show that one is full 
                    System.out.println(index + "  Number   The philosopher has finished ");
                }
            }
         }
     }

Problem of scheme 1 :

  • There will be deadlock problems
  • Concept : Deadlocks generally have 2 Put the above lock , Wait for another lock while locking one

Option two : Optimized version

  • Ideas : Two locks merge into one (5 hold , 5 Turn the lock into a lock , Chopsticks collection , Lock the entire object )
  • Mix in a left-handed
  • More efficient writing , Odd number Even numbers separate , Mix in half of the left-handed
     public void run() {
    
// try {
    
// Thread.sleep(new Random().nextInt(5));
// } catch (InterruptedException e) {
    
// System.out.println(e);
// }
                    if (index == 0) {
    
                        // Left handed algorithm   It's fine too index % 2 == 0
                        synchronized (left) {
    
                            try {
    
                                Thread.sleep(1);
                            } catch (InterruptedException e) {
    
                                System.out.println(e);
                            }
                            synchronized (right) {
    
                                try {
    
                                    Thread.sleep(1);
                                } catch (InterruptedException e) {
    
                                    System.out.println(e);
                                }
                                System.out.println(index + "  I'm full !");
                            }
                        }
                    } else {
    
                        synchronized (right) {
    
                            try {
    
                                Thread.sleep(1);
                            } catch (InterruptedException e) {
    
                                System.out.println(e);
                            }
                            synchronized (left) {
    
                                try {
    
                                    Thread.sleep(1);
                                } catch (InterruptedException e) {
    
                                    System.out.println(e);
                                }
                                System.out.println(index + "  I'm full !");
                            }
                        }
                    }
                }			    

3、 Alternating output problem

Concept

  • Be sure to ensure alternate output , This involves the synchronization of two threads .
  • One might think of , Use sleep time difference to realize , But as long as it is multithreaded , Thread synchronization play sleep() Functional ,99.99% All wrong .
  • 【 Alternating output 】 The problem is classic , There are many solutions . Let's start with :【 The easiest way 】 Realize this problem .

Be careful : The key function

  • Locksupport.park(): Block the current thread
  • Locksupport.unpark(“”): Wake up a thread

Mode one :LockSupport( Lock support ) Simplest solution

 Insert picture description here

public class T02_00_LockSupport {
    
    
    static Thread t1 = null, t2 = null;
    
    public static void main(String[] args) throws Exception {
    
        char[] aI = "1234567".toCharArray();
        char[] aC = "ABCDEFG".toCharArray();
    
        t1 = new Thread(() -> {
    

                for (char c : aI) {
    
                    System.out.print(c);
                    LockSupport.unpark(t2); //  Wake up t2
                    LockSupport.park(); // t1 Blocking   The current thread is blocked 
                }

            }, "t1");

            t2 = new Thread(() -> {
    

                for (char c : aC) {
    
                    LockSupport.park(); // t2 Hang up 
                    System.out.print(c);
                    LockSupport.unpark(t1); //  Wake up t1
                }

            }, "t2");

            t1.start();
            t2.start();
    }
}
  • The execution result is :1A2B3C4D5E6F7G

Mode two :Sync wait notify( Sync 、 wait for 、 notice ) Optimized version

 Insert picture description here

public class T06_00_sync_wait_notify {
    
    
    public static void main(String[] args) {
    

        final Object o = new Object();

        char[] aI = "1234567".toCharArray();
        char[] aC = "ABCDEFG".toCharArray();

        new Thread(() -> {
    
            //  First create a lock 
            synchronized (o) {
    
                for (char c : aI) {
    
                    System.out.print(c);
                    try {
    
                        o.notify(); //  Wake up a thread in the waiting queue , For this program, it is another thread 
                        o.wait(); //  Get out of the lock 
                    } catch (InterruptedException e) {
    
                        e.printStackTrace();
                    }
                }
                o.notify(); //  must , Otherwise, the program cannot be stopped 
            }
        }, "t1").start();

        new Thread(() -> {
    
            synchronized (o) {
    
                for (char c : aC) {
    
                    System.out.print(c);
                    try {
    
                        o.notify();
                        o.wait();
                    } catch (InterruptedException e) {
    
                        e.printStackTrace();
                    }
                }
                o.notify();
            }
        }, "t2").start();
    }
}

  • From the above code, we may raise a problem , So that is :notify() and wait() Whether the calling sequence has a direct impact ?

  • answer : Yes , So let's take a look at ,notify() and wait() The implementation process of

  • This question was once Huawei's written test
     Insert picture description here

  • As you can see from the code snippet above , If we execute first wait(), Will first let oneself directly into the waiting queue . Then you and another thread are waiting in the waiting queue , Two threads have been waiting , No one can wake the other , That is, it cannot be implemented at all notify()

A pit :

  • We found that , There is another one at the end of the program notify(), And there must be , Why is it necessary ? Let's comment it out , Take a look at the output :1A2B3C4D5E6F7G
  • We can see from the output , There is no problem with the execution result , But the program will not stop .
  • principle : We can find out from the dynamic graph , Finally, there must be a thread in wait() State of , No one woke it up , It will always be waiting , Thus the program cannot end , To avoid this , We're going to add one after that notify()

Pit two :

  • Those who have played with threads should have discovered this problem long ago , If the second thread preempts first , So the output is A1B2C3 了 , How to ensure that the first output is always the number first ?
  • We can use CountDownLatch This class , It is JUC New synchronization tools , This class can be thought of as a door bolt , When we have threads executing to the gate , It will wait for the bolt to open the door , Threads will execute .
  • If t2 Take the lead , Then it will execute await() Method , Because of the existence of the door bolt , It can only wait outside the door , therefore t1 The thread will execute directly , Execute to countDown() Method , To create CountDownLatch(1) The parameter is set to 0, Release the door latch , So it will always be t1 The thread is finished ,t2 Threads will execute

Complete code

  • Pit avoiding writing
public class T07_00_sync_wait_notify {
    

    private static CountDownLatch latch = new CountDownLatch(1); //  Set the parameters of the door bolt to 1, That is, there is only one bolt 

    public static void main(String[] args) {
    

        final Object o = new Object();

        char[] aI = "1234567".toCharArray();
        char[] aC = "ABCDEFG".toCharArray();

        new Thread(() -> {
    
            synchronized (o) {
    
                for (char c : aI) {
    
                    System.out.print(c);
                    latch.countDown(); //  The value of the door bolt -1, That is, open the door 
                    try {
    
                        o.notify();
                        o.wait(); 
                    } catch (InterruptedException e) {
    
                        e.printStackTrace();
                    }
                }
                o.notify(); 
            }
        }, "t1").start();
        
        new Thread(() -> {
    
            try {
    
                latch.await(); //  Which thread do you want to execute after ,await() In which thread 
            } catch (InterruptedException e) {
    
                e.printStackTrace();
            }
            synchronized (o) {
    
                for (char c : aC) {
    
                    System.out.print(c);
                    try {
    
                        o.notify();
                        o.wait();
                    } catch (InterruptedException e) {
    
                        e.printStackTrace();
                    }
                }
                o.notify();
            }
        }, "t2").start();
    }
}

Lock ReentrantLock await signal( Wait for the signal )

  • JDK Many new synchronization tools are available , stay JUC It's a bag , There is a special alternative synchronized Lock of :Lock
public class T08_00_lock_condition {
    
        public static void main(String[] args) {
    
        char[] aI = "1234567".toCharArray();
        char[] aC = "ABCDEFG".toCharArray();

		Lock lock = new ReentrantLock();
        Condition condition = lock.newCondition();

        new Thread(() -> {
    
            
            lock.lock();
            
            try {
    
                for (char c : aI) {
    
                    System.out.print(c);
                    condition.signal();  // notify()
                    condition.await(); // wait()
                }
                condition.signal();
            } catch (Exception e) {
    
                e.printStackTrace();
            } finally {
    
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {
    
            lock.lock(); // synchronized
            try {
    
                for (char c : aC) {
    
                    System.out.print(c);
                    condition.signal(); // o.notify
                    condition.await(); // o.wait
                }
                condition.signal();
            } catch (Exception e) {
    
                e.printStackTrace();
            } finally {
    
                lock.unlock();
            }
        }, "t2").start();
    }
}
  • From the above code , We can see that : Create a lock , Call methods with synchronized There is no difference between , But the point is Condition This class
  • You should know the concept of producer and consumer , Producers produce steamed buns , The production is full and enters the waiting queue , Consumers eat steamed buns , Eat up the same into the waiting queue
  • If we use traditional synchronized , When the producer is full , The consumer needs to be awakened from the waiting queue , But the call notify When the method is used
  • Can we guarantee that it is the consumers who must wake up ? You can't , This is impossible , How can we ensure that the consumers must wake up ?

4、 Producer consumer issues ReentantLock Condition

 Insert picture description here

Two solutions

Scheme 1 :

  • If the basket is full , The producer waits for a thread to wake up in the queue , But if the waking thread is still a producer , After the new producer gets up, he must check whether the basket is full , You can't just come up and produce , If it's full , Then go and wake up the next thread , Repeat in this order , We will definitely wake up the consumers once

Option two :

  • notifyAll() Method : All producers and consumers in the waiting queue will wake up , Consumer finds basket full , Just consume , The producer found the basket full , Just go back to the waiting queue .

Be careful

  • But either of these solutions , The threads we wake up are all imprecise , There is waste ; This is it. synchronized The problem of synchronization

Code explanation

 Insert picture description here

  • Lock It can solve this problem by itself , It depends on Condition,Condition It can wake up accurately

  • Condition It means conditional , But we can think of it as a queue

  • One condition It is a waiting queue

Code demonstration ( Optimized version )

public class T08_00_lock_condition {
    

    public static void main(String[] args) {
    

        char[] aI = "1234567".toCharArray();
        char[] aC = "ABCDEFG".toCharArray();

        Lock lock = new ReentrantLock();
        Condition conditionT1 = lock.newCondition(); //  queue 1
        Condition conditionT2 = lock.newCondition(); //  queue 2

        CountDownLatch latch = new CountDownLatch(1);
        new Thread(() -> {
    

            lock.lock(); // synchronized

            try {
    
                for (char c : aI) {
    
                    System.out.print(c);
                    latch.countDown();
                    conditionT2.signal();  // o.notify()
                    conditionT1.await(); // o.wait()
                }
                conditionT2.signal();
            } catch (Exception e) {
    
                e.printStackTrace();
            } finally {
    
                lock.unlock();
            }
        }, "t1").start();

        new Thread(() -> {
    

            try {
    
                latch.await();
            } catch (InterruptedException e) {
    
                e.printStackTrace();
            }

            lock.lock(); // synchronized

            try {
    
                for (char c : aC) {
    
                    System.out.print(c);
                    conditionT1.signal(); // o.notify
                    conditionT2.await(); // o.wait
                }
                conditionT1.signal();
            } catch (Exception e) {
    
                e.printStackTrace();
            } finally {
    
                lock.unlock();
            }
        }, "t2").start();
    }
}

  • From the code optimized online, we can see , First thread t1 First come up and hold the lock , Wake up the contents of the second queue after holding the lock
  • Then I enter the first queue and wait , Empathy ,t2 The thread wakes up the contents of the first queue , Enter the second queue and wait , In this way, you can wake up accurately
原网站

版权声明
本文为[Abin abin]所创,转载请带上原文链接,感谢
https://yzsam.com/2022/175/202206232247308697.html