[Interview] If you treat the thread as a person, all the problems will be understood instantly

The problem of multi-threading has troubled every developer. Today, I will explain it from a new perspective. I hope readers can understand it.

It is strongly recommended to run the sample code in the article and experience it yourself. The problem of multi-threading has troubled every developer. Today, I will explain it from a new perspective. I hope readers can understand it.

It is strongly recommended to run the sample code in the article and experience it yourself.

Where is the problem?

A thread execution, of course, is safe, but sometimes it is too slow, what should I do?

Our ancestors told us, "If one party is in trouble, all parties will support it." That is to call a few more threads to help. It's easy to handle. Does this work? Keep looking down.

As the saying goes, "rely on parents at home, rely on friends when going out". With the help of friends, you will get twice the result with half the effort. Is that so?

Not necessarily, if a friend is "unreliable", the result is actually "adding chaos". So it evolved into, "I am not afraid of opponents like gods, but I am afraid of teammates like pigs." It can be seen that although "many people are more powerful" is true, it must be done with good cooperation.

People and people are friends, and threads and threads are also "friends". If multi-threads cannot cooperate well, they will eventually become "pig-like teammates". As it turns out, this isn't easy either. Let me take it slow.

Development is a technique, and management is an art. Maybe you are about to take your brothers to a big fight, but someone has to resign. Or maybe you give so much, but others are never touched. Why is this so?

Because you are dealing with people. Everyone is an independent individual with thoughts, souls, emotions, and three views. It can accept the "input" from the outside world, and after "processing", it can produce "output".

To put it bluntly, it will analyze problems independently and make decisions. What is this called? The answer is, subjectivity.

For an object with subjective initiative (such as a person), you need to negotiate or cooperate with it to accomplish a thing together, and you cannot "force" it to do something, because it will often not have good results.

After spending so much talk, I just want to simplify the problem as much as possible. Finally, we can return to the program. Is the situation of the thread similar? The answer is yes.

After a thread is ready, it can run autonomously after being scheduled by the CPU. At this time it seems to have become an independent individual, and has subjective initiative.

This is a good thing, but it also has a bad side, that is, your ability to "control" it has become weaker, and there is quite a feeling of "the general will be outside, and the ruler's life will not be affected".

Maybe you don't agree with this view, saying that I can "force" it to stop running, and call the stop() method of the Thread class to "strangle" it directly. Sorry, this method is deprecated.

Because the thread may be running some "critical" code (such as a transfer), it cannot be terminated at the moment. The Thread class and some other methods are also abandoned, probably for the same reasons.

After talking so much, I believe you have understood it, and briefly summarize:

Cause : Threads can run independently and can be considered to be subjective.

The result : the ability to control it is weakened, and it can't be "killed" directly.

Solution : negotiate everything and cooperate with each other to get things done.

Author's point of view : In fact, it is to treat threads as people.

Give it a try

Once you think of threads as people, you come to the human world, which we are all too familiar with, so many problems will become very simple and clear. Let's take a look together.

scene one, stop

"Big fat, big fat, it's 12 o'clock, it's time to eat, stop writing"

"Okay, okay, wait a moment, write these lines of code and go"

Key point: Send the signal to others to stop, and others will take the initiative to stop after they have dealt with the matter at hand.

 static void stopByFlag() {
    ARunnable ar = new ARunnable();
    new Thread(ar).start();
    ar.tellToStop();
  }

  static class ARunnable implements Runnable {

    volatile boolean stop;

    void tellToStop() {
      stop = true;
    }

    @Override
    public void run() {
      println("进入不可停止区域 1。。。");
      doingLongTime(5);
      println("退出不可停止区域 1。。。");
      println("检测标志stop = %s", String.valueOf(stop));
      if (stop) {
        println("停止执行");
        return;
      }
      println("进入不可停止区域 2。。。");
      doingLongTime(5);
      println("退出不可停止区域 2。。。");
    }

  }

Explanation: The thread checks the flag at the preset location to decide whether to stop.

Scenario 2, Pause/Resume

"Big fat, big fat, don't make a request, the other party's server is about to hang up"

"Okay, okay, I won't post it after this is executed."

after a while

"Big fat, big fat, you can re-send the request"

"OK, OK"

Key point: Communicate the signal of suspension to others, and others will take the initiative to suspend themselves after finishing the matter at hand. However, recovery cannot be performed autonomously, and the execution of the thread can only be resumed by the operating system.

static void pauseByFlag() {
    BRunnable br = new BRunnable();
    new Thread(br).start();
    br.tellToPause();
    sleep(8);
    br.tellToResume();
  }

  static class BRunnable implements Runnable {

    volatile boolean pause;

    void tellToPause() {
      pause = true;
    }

    void tellToResume() {
      synchronized (this) {
        this.notify();
      }
    }

    @Override
    public void run() {
      println("进入不可暂停区域 1。。。");
      doingLongTime(5);
      println("退出不可暂停区域 1。。。");
      println("检测标志pause = %s", String.valueOf(pause));
      if (pause) {
        println("暂停执行");
        try {
          synchronized (this) {
            this.wait();
          }
        } catch (InterruptedException e) {
          e.printStackTrace();
        }
        println("恢复执行");
      }
      println("进入不可暂停区域 2。。。");
      doingLongTime(5);
      println("退出不可暂停区域 2。。。");
    }

  }

Explanation: The flag is still detected at the preset location. Then it is the use of wait/notify.

Scenario three, jumping in line

"Big fat, big fat, let me stand in front of you, don't want to line up"

"All right"

Takeaway: When someone jumps in front of you, you must wait for him to finish before it's your turn.

static void jqByJoin() {
    CRunnable cr = new CRunnable();
    Thread t = new Thread(cr);
    t.start();
    sleep(1);
    try {
      t.join();
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    println("终于轮到我了");
  }

  static class CRunnable implements Runnable {

    @Override
    public void run() {
      println("进入不可暂停区域 1。。。");
      doingLongTime(5);
      println("退出不可暂停区域 1。。。");
    }

  }

Explanation: The join method allows a thread to be inserted in front of itself, and it will continue to execute when it finishes executing.

Scene four, wake up

"Big fat, big fat, wake up, wake up, see who's coming"

"Who, I'll go"

Takeaway: To wake someone from their sleep, be sure to use a little more violence.

static void stopByInterrupt() {
    DRunnable dr = new DRunnable();
    Thread t = new Thread(dr);
    t.start();
    sleep(2);
    t.interrupt();
  }

  static class DRunnable implements Runnable {

    @Override
    public void run() {
      println("进入暂停。。。");
      try {
        sleep2(5);
      } catch (InterruptedException e) {
        println("收到中断异常。。。");
        println("做一些相关处理。。。");
      }
      println("继续执行或选择退出。。。");
    }

  }

Explanation: When a thread is sleeping or waiting, it is in a state that cannot interact. At this time, it can only be interrupted by the interrupt method, and the thread will be activated and receive an interrupt exception.

common collaboration

The above scenarios are actually operations on one thread. Let's look at some cooperation between multiple threads.

event one, exam

Suppose today's test, 20 students, 1 invigilator. It is stipulated that students can hand in their papers in advance, that is, leave the papers and leave directly.

But the teacher has to wait until all the students are gone before they can collect the papers, then bind and pack them.

If both students and teachers are regarded as threads, it is a problem of cooperation between 1 thread and 20 threads, that is, when all 20 threads are finished, this thread starts.

For example, 20 threads are calculating data respectively, and after all of them are finished, 20 intermediate results are obtained, and finally this thread will perform subsequent aggregation and processing.

  static final int COUNT = 20;
  static CountDownLatch cdl = new CountDownLatch(COUNT);

  public static void main(String[] args) throws Exception {
    new Thread(new Teacher(cdl)).start();
    sleep(1);
    for (int i = 0; i < COUNT; i++) {
      new Thread(new Student(i, cdl)).start();
    }
    synchronized (ThreadCo1.class) {
      ThreadCo1.class.wait();
    }
  }

  static class Teacher implements Runnable {

    CountDownLatch cdl;

    Teacher(CountDownLatch cdl) {
      this.cdl = cdl;
    }

    @Override
    public void run() {
      println("老师发卷子。。。");
      try {
        cdl.await();
      } catch (InterruptedException e) {
        e.printStackTrace();
      }
      println("老师收卷子。。。");
    }

  }

  static class Student implements Runnable {

    CountDownLatch cdl;
    int num;

    Student(int num, CountDownLatch cdl) {
      this.num = num;
      this.cdl = cdl;
    }

    @Override
    public void run() {
      println("学生(%d)写卷子。。。", num);
      doingLongTime();
      println("学生(%d)交卷子。。。", num);
      cdl.countDown();
    }

  }

Explanation: Each time a thread is completed, the counter is decremented by 1, and when it is decremented to 0, the blocked thread is automatically executed.

event two, travel

The scenery has been pleasant recently. The company organized a mountaineering trip. Everyone came to the foot of the mountain, and the mountaineering process was carried out freely.

But in order to take a group photo at a specific location, it is stipulated that they will gather at the halfway up the mountain after 1 hour. Whoever arrives last will perform a show for everyone.

Then continue to climb the mountain, and after 2 hours, gather at the top of the mountain to take pictures, or whoever arrives last show.

Then we started going down the mountain. After 2 hours, we gathered at the foot of the mountain, called home by name, and finally arrived to perform the show as usual.

  static final int COUNT = 5;
  static CyclicBarrier cb = new CyclicBarrier(COUNT, new Singer());

  public static void main(String[] args) throws Exception {
    for (int i = 0; i < COUNT; i++) {
      new Thread(new Staff(i, cb)).start();
    }
    synchronized (ThreadCo2.class) {
      ThreadCo2.class.wait();
    }
  }

  static class Singer implements Runnable {

    @Override
    public void run() {
      println("为大家唱歌。。。");
    }

  }

  static class Staff implements Runnable {

    CyclicBarrier cb;
    int num;

    Staff(int num, CyclicBarrier cb) {
      this.num = num;
      this.cb = cb;
    }

    @Override
    public void run() {
      println("员工(%d)出发。。。", num);
      doingLongTime();
      println("员工(%d)到达地点一。。。", num);
      try {
        cb.await();
      } catch (Exception e) {
        e.printStackTrace();
      }
      println("员工(%d)再出发。。。", num);
      doingLongTime();
      println("员工(%d)到达地点二。。。", num);
      try {
        cb.await();
      } catch (Exception e) {
        e.printStackTrace();
      }
      println("员工(%d)再出发。。。", num);
      doingLongTime();
      println("员工(%d)到达地点三。。。", num);
      try {
        cb.await();
      } catch (Exception e) {
        e.printStackTrace();
      }
      println("员工(%d)结束。。。", num);
    }

  }

Explanation: When a thread reaches the preset point, wait here. When all threads arrive, everyone will start to the next preset point together. This cycle repeats.

event three, labor

Big Fat and Xiaobai went to the start-up company. In order to save money, the company did not hire special cleaning staff. Let employees sweep floors and wipe tables themselves.

The big fat felt that it was easy to clean the table, so he asked Xiaobai to sweep the floor. But Xiaobai felt too tired to sweep the floor, and wanted to wipe the table too.

In the interest of fairness, it was decided that each person would do half of it first, then exchange tools, and then continue to do the other half.

  static Exchanger<Tool> ex = new Exchanger<>();

  public static void main(String[] args) throws Exception {
    new Thread(new Staff("大胖", new Tool("笤帚", "扫地"), ex)).start();
    new Thread(new Staff("小白", new Tool("抹布", "擦桌"), ex)).start();
    synchronized (ThreadCo3.class) {
      ThreadCo3.class.wait();
    }
  }

  static class Staff implements Runnable {

    String name;
    Tool tool;
    Exchanger<Tool> ex;

    Staff(String name, Tool tool, Exchanger<Tool> ex) {
      this.name = name;
      this.tool = tool;
      this.ex = ex;
    }

    @Override
    public void run() {
      println("%s拿的工具是[%s],他开始[%s]。。。", name, tool.name, tool.work);
      doingLongTime();
      println("%s开始交换工具。。。", name);
      try {
        tool = ex.exchange(tool);
      } catch (Exception e) {
        e.printStackTrace();
      }

      println("%s的工具变为[%s],他开始[%s]。。。", name, tool.name, tool.work);
    }

  }

  static class Tool {

    String name;
    String work;

    Tool(String name, String work) {
      this.name = name;
      this.work = work;
    }

  }

Explanation: Two threads exchange variables at a preset point, and the one that arrives first waits for each other.

Event 4, Magic Game

This is a magical little game played by a team. Everyone is drawn every 5 seconds, and everyone has a 50% probability of staying or being eliminated.

Those who stay will also have a 50% chance of being eliminated in the next draw. Those who are eliminated also have a 50% chance of being resurrected in the next draw.

All team members have been eliminated. In order to fail the challenge, all members of the team return to the game (except for the beginning), in order to succeed in the challenge.

For example, 10 people participated in the game at the beginning. After the first round of draws, 6 people stayed and 4 people were eliminated.

After the second round of draws, 4 of the remaining 6 were eliminated, and 2 of the eliminated 4 were resurrected, so there are currently 4 in the game and 6 eliminated.

Continue like this until all 10 are eliminated, or all return to the game.

It can be seen that the larger the number of people, the lower the probability of being eliminated, but the lower the probability of all returning to the game.

Conversely, the smaller the number of people, the greater the probability of all returning to the game, but the greater the probability of being eliminated.

Isn't it magical? Ha ha.

  static final int COUNT = 6;
  static Phaser ph = new Phaser() {
    protected boolean onAdvance(int phase, int registeredParties) {
      println2("第(%d)局,剩余[%d]人", phase, registeredParties);
      return registeredParties == 0 ||
          (phase != 0 && registeredParties == COUNT);
    };
  };

  public static void main(String[] args) throws Exception {
    new Thread(new Challenger("张三")).start();
    new Thread(new Challenger("李四")).start();
    new Thread(new Challenger("王五")).start();
    new Thread(new Challenger("赵六")).start();
    new Thread(new Challenger("大胖")).start();
    new Thread(new Challenger("小白")).start();
    synchronized (ThreadCo4.class) {
      ThreadCo4.class.wait();
    }
  }

  static class Challenger implements Runnable {

    String name;
    int state;

    Challenger(String name) {
      this.name = name;
      this.state = 0;
    }

    @Override
    public void run() {
      println("[%s]开始挑战。。。", name);
      ph.register();
      int phase = 0;
      int h;
      while (!ph.isTerminated() && phase < 100) {
        doingLongTime(5);
        if (state == 0) {
          if (Decide.goon()) {
            h = ph.arriveAndAwaitAdvance();
            if (h < 0)
              println("No%d.[%s]继续,但已胜利。。。", phase, name);
            else
              println("No%d.[%s]继续at(%d)。。。", phase, name, h);
          } else {
            state = -1;
            h = ph.arriveAndDeregister();
            println("No%d.[%s]退出at(%d)。。。", phase, name, h);
          }
        } else {
          if (Decide.revive()) {
            state = 0;
            h = ph.register();
            if (h < 0)
              println("No%d.[%s]复活,但已失败。。。", phase, name);
            else
              println("No%d.[%s]复活at(%d)。。。", phase, name, h);
          } else {
            println("No%d.[%s]没有复活。。。", phase, name);
          }
        }
        phase++;
      }
      if (state == 0) {
        ph.arriveAndDeregister();
      }
      println("[%s]结束。。。", name);
    }

  }

  static class Decide {

    static boolean goon() {
      return random(9) > 4;
    }

    static boolean revive() {
      return random(9) < 5;
    }
  }

Commentary: After a thread reaches the preset point, you can choose to wait for your partner or exit by yourself. After everyone arrives, you can start to the next preset point together. New threads can join at any time, and those who exit can also join again. .

Production and sales issues

In reality, the products produced by the factory will be stored in the warehouse first. After the salesperson signs the order, the products will be sent to the customer from the warehouse.

If the production is too fast, and the warehouse piles up more and more products, until the warehouse is full, then production must be stopped because there is nowhere to put it.

At this time, the sales staff can only quickly go out to sign the order and send the product, and the warehouse will have space and can resume production.

If the sales are too fast, and there are fewer and fewer products in the warehouse, until the warehouse is emptied, it must stop selling because there is no more product.

At this time, the production staff can only quickly produce the product, put the product in the warehouse, and the product will be available in the warehouse, and sales can be resumed.

Some people may ask, why not let production and sales be directly linked, and remove the warehouse as a link?

This leads to two bad situations:

  • First, a lot of orders came suddenly, and the production staff were so exhausted that Dog couldn't produce them.

  • The second is that there is no order for a long time, and the production staff is idle and has nothing to do.

In a slightly "professional" term, production and sales at this time are strongly coupled, and fluctuations in sales have too much impact on production.

The warehouse is a buffer, which can effectively absorb fluctuations, greatly reduce the transmission of fluctuations, and play a decoupling role, changing from strong coupling to loose coupling.

This actually corresponds to the classic producer and consumer problem in computers.

Classic producer and consumer

One or more threads act as producers, producing elements. One or more threads act as consumers, consuming elements.

Insert a queue (Queue) between the two to act as a buffer to establish loose coupling between producers and consumers.

Under normal circumstances, that is, when the speed of producing elements is similar to the speed of consuming elements, producers and consumers do not need to pay attention to each other.

The producer can keep producing because there is always room in the queue. Consumers can keep consuming because there are always elements in the queue. That is to achieve a dynamic balance.

But in special cases, such as the production speed of elements is very fast, and there is no space in the queue, at this time, the producer must "ba work" and start to "sleep".

Once the consumer consumes the element, there will be space in the queue, and the producer can restart production. Therefore, the consumer is obliged to wake the producer to resume work after consuming the element.

A more accurate statement should be that only when the producer is "sleeping" does the consumer need to wake up the producer after consuming the element. Otherwise, you don't actually need to wake up, because people haven't slept.

Conversely, if the consumption of elements is fast and there are no elements in the queue, just reverse the above situation.

But this will introduce a new problem, that is, to be able to judge whether the other party is sleeping, you must define a state variable, and set this variable when you are about to start sleeping.

The other party decides whether to perform a wake-up operation by detecting this variable. When I was woken up, the first thing I had to do was to clear this variable, indicating that I had woken up and returned to work.

In this way, it is necessary to maintain one more variable and add a part of the judgment logic. Some people may think that the wake-up operation can be determined by judging whether the queue is "empty" or "full" (that is, the number of elements in the queue).

Under high concurrency, it may be judged that the queue is not empty just now, and the queue may have become empty after an instant, which will lead to logic errors. The thread may never be woken up.

Therefore, after all, the producer will notify the consumer every time an element is produced, "If there is an element now, you can consume it".

Similarly, every time the consumer consumes an element, it will notify the producer, "Now there is space, you can produce".

Obviously, many of these notifications (i.e. when the other person isn't asleep) don't really make sense, but it doesn't matter, just ignore them.

It is "would rather kill a thousand by mistake than let one go". First of all to ensure that it is correct, and then to be eligible to BB anything else.

  public static void main(String[] args) {
    Queue queue = new Queue();
    new Thread(new Producer(queue)).start();
    new Thread(new Producer(queue)).start();
    new Thread(new Consumer(queue)).start();
  }

  static class Producer implements Runnable {

    Queue queue;

    Producer(Queue queue) {
      this.queue = queue;
    }

    @Override
    public void run() {
      try {
        for (int i = 0; i < 10000; i++) {
          doingLongTime();
          queue.putEle(random(10000));
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

  }

  static class Consumer implements Runnable {

    Queue queue;

    Consumer(Queue queue) {
      this.queue = queue;
    }

    @Override
    public void run() {
      try {
        for (int i = 0; i < 10000; i++) {
          doingLongTime();
          queue.takeEle();
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }

  }

  static class Queue {
    Lock lock = new ReentrantLock();
    Condition prodCond  = lock.newCondition();
    Condition consCond = lock.newCondition();

    final int CAPACITY = 10;
    Object[] container = new Object[CAPACITY];
    int count = 0;
    int putIndex = 0;
    int takeIndex = 0;

    public void putEle(Object ele) throws InterruptedException {
      try {
        lock.lock();
        while (count == CAPACITY) {
          println("队列已满:%d,生产者开始睡大觉。。。", count);
          prodCond.await();
        }
        container[putIndex] = ele;
        println("生产元素:%d", ele);
        putIndex++;
        if (putIndex >= CAPACITY) {
          putIndex = 0;
        }
        count++;
        println("通知消费者去消费。。。");
        consCond.signalAll();
      } finally {
        lock.unlock();
      }
    }

    public Object takeEle() throws InterruptedException {
      try {
        lock.lock();
        while (count == 0) {
          println("队列已空:%d,消费者开始睡大觉。。。", count);
          consCond.await();
        }
        Object ele = container[takeIndex];
        println("消费元素:%d", ele);
        takeIndex++;
        if (takeIndex >= CAPACITY) {
          takeIndex = 0;
        }
        count--;
        println("通知生产者去生产。。。");
        prodCond.signalAll();
        return ele;
      } finally {
        lock.unlock();
      }
    }
  }

Commentary: In fact, it is the application of await/signalAll, which is almost always asked in interviews.

Source code:

https://github.com/coding-new-talking/java-code-demo.git

Thanks for attention!

{{o.name}}
{{m.name}}

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=324030114&siteId=291194637