Вы знаете этот вопрос многопоточного интервью Али?

Java

задний план

Несколько дней назад друг группы в группе задал мне многопоточный вопрос, с которым я столкнулся при интервьюировании Али.Этот вопрос более интересный, и я поделюсь им с вами здесь.

Без лишних слов, перейдем непосредственно к теме:

通过N个线程顺序循环打印从0至100,如给定N=3则输出:
thread0: 0
thread1: 1
thread2: 2
thread0: 3
thread1: 4
.....

Некоторые друзья, которые часто отмахиваются от вопросов на собеседовании, должно быть, уже сталкивались со следующим вопросом:

两个线程交替打印0~100的奇偶数:
偶线程:0
奇线程:1
偶线程:2
奇线程:3

Эти две темы похожи, вторая тема немного проще, можно сначала подумать, как напечатать нечетные и четные числа двух потоков.

Двухпоточная печать с контролем четности

Некоторые люди могут использовать здесь хитрость, использовать нить для зацикливания, и в каждом цикле судить, нечетное оно или четное, а затем распечатывать желаемый результат. Здесь мы не обсуждаем слишком много об этой практике, которая идет вразрез с замыслом заголовка.

На самом деле, чтобы сделать эту тему, нам нужно контролировать порядок выполнения двух потоков.После выполнения четного потока выполняется нечетный поток.Это немного похоже на механизм уведомления.Четный поток уведомляет нечетный поток, и нечетный поток уведомляет четный поток. Как только я увидел уведомление/ожидание, некоторые друзья сразу подумали о ожидании и уведомлении в Object. Да, здесь мы используем ожидание и уведомление для его реализации, код выглядит следующим образом:

public class 交替打印奇偶数 {
    static class SoulutionTask implements Runnable{
        static int value = 0;
        @Override
        public void run() {
            while (value <= 100){
                synchronized (SoulutionTask.class){
                    System.out.println(Thread.currentThread().getName() + ":" + value++);
                    SoulutionTask.class.notify();
                    try {
                        SoulutionTask.class.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }
    }
    public static void main(String[] args) {
        new Thread(new SoulutionTask(), "偶数").start();
        new Thread(new SoulutionTask(), "奇数").start();
    }
}

Здесь у нас есть два потока, которые используются для управления выполнением наших потоков через уведомление и ждут, чтобы распечатать результат нашей цели.

Печать цикла N потоков

Возвращаясь к нашему первоначальному вопросу,N потоков печатаются циклически.После того, как я помог группе ответить на этот вопрос, я снова закинул этот вопрос в группу.Многие старые драйверы видели попеременную печать нечетных и четных чисел раньше.Эта тема,так Сделал сразу несколько версий, посмотрим код старого драйвера 1:

public class 老司机1 implements Runnable {

    private static final Object LOCK = new Object();
    /**
     * 当前即将打印的数字
     */
    private static int current = 0;
    /**
     * 当前线程编号,从0开始
     */
    private int threadNo;
    /**
     * 线程数量
     */
    private int threadCount;
    /**
     * 打印的最大数值
     */
    private int maxInt;

    public 老司机1(int threadNo, int threadCount, int maxInt) {
        this.threadNo = threadNo;
        this.threadCount = threadCount;
        this.maxInt = maxInt;
    }

    @Override
    public void run() {
        while (true) {
            synchronized (LOCK) {
                // 判断是否轮到当前线程执行
                while (current % threadCount != threadNo) {
                    if (current > maxInt) {
                        break;
                    }
                    try {
                        // 如果不是,则当前线程进入wait
                        LOCK.wait();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                // 最大值跳出循环
                if (current > maxInt) {
                    break;
                }
                System.out.println("thread" + threadNo + " : " + current);
                current++;
                // 唤醒其他wait线程
                LOCK.notifyAll();
            }
        }
    }

    public static void main(String[] args) {
        int threadCount = 3;
        int max = 100;
        for (int i = 0; i < threadCount; i++) {
            new Thread(new 老司机1(i, threadCount, max)).start();
        }
    }
}

Основной метод находится в run.Вы можете видеть, что он похож на принцип вывода нечетных и четных чисел попеременно.Здесь мы меняем наш notify на notifyAll.Следует отметить, что здесь многие поймут, что notifyAll означает, что все остальные ждут потоки будут выполняться, что на самом деле неправильно. Здесь из текущего состояния ожидания будет освобожден только ожидающий поток, который также называется пробуждением.Поскольку мы оборачиваем его синхронизированным блоком блокировки синхронизации, пробужденный поток вырвет блокировку синхронизации.

Этот код старого драйвера действительно может работать, но в чем проблема? Когда у нас есть большое количество потоков, из-за того, что мы не уверены, является ли пробужденный поток следующим для выполнения, может возникнуть ситуация, когда он захватывает блокировку, но не должен выполнять ее сам, а затем снова входит в ожидание. Например, сейчас 100 Thread, сейчас выполняется первый поток, после завершения выполнения ему требуется второй поток для выполнения, но 100-й поток хватает его, обнаруживает, что он не в себе и затем входит в ожидание, а затем 99-й поток схватывает его, находит, что он не тот и потом снова Enter ждет, а потом 98-й, 97-й... пока третий поток не схватит его, и, наконец, второй поток не схватит блокировку синхронизации, там будет еще много процессов в напрасно, хотя цель может быть достигнута в конце концов.

Существуют и другие старые драйверы, которые также используют блокировку/условие для достижения этой функции, а некоторые старые драйверы используют для этого относительно новые методы, такие как очереди.Конечно, я не буду упоминать об этом здесь.Общий принцип основан на вышеизложенном. ,вот я и говорюДавайте посмотрим на мой подход.Некоторые часто используемые синхронизаторы предусмотрены в многопоточности Java.В этом сценарии больше подходит использование Semaphore,то есть семафор.Наша предыдущая нить держит семафор следующей поток.Через сигнал Массив количества будет связан, код выглядит следующим образом:

static int result = 0;
    public static void main(String[] args) throws InterruptedException {
        int N = 3;
        Thread[] threads = new Thread[N];
        final Semaphore[] syncObjects = new Semaphore[N];
        for (int i = 0; i < N; i++) {
            syncObjects[i] = new Semaphore(1);
            if (i != N-1){
                syncObjects[i].acquire();
            }
        }
        for (int i = 0; i < N; i++) {
            final Semaphore lastSemphore = i == 0 ? syncObjects[N - 1] : syncObjects[i - 1];
            final Semaphore curSemphore = syncObjects[i];
            final int index = i;
            threads[i] = new Thread(new Runnable() {

                public void run() {
                    try {
                        while (true) {
                            lastSemphore.acquire();
                            System.out.println("thread" + index + ": " + result++);
                            if (result > 100){
                                System.exit(0);
                            }
                            curSemphore.release();
                        }
                    } catch (Exception e) {
                        e.printStackTrace();
                    }

                }
            });
            threads[i].start();
        }
    }

Таким образом, у нас не будет тредов, которые просыпаются напрасно, и каждый тред будет выполняться в том порядке, в котором мы договорились.Это собственно то, что нужно тестировать интервьюеру, чтобы выполнение каждого треда могло быть в ваших руках. Получите контроль, это также может подтвердить, что ваши знания многопоточности прочны.

Наконец, эта статья была включена в интервью JGrowing-Java, всеобъемлющий и отличный маршрут изучения Java, совместно созданный сообществом.Если вы хотите участвовать в обслуживании проектов с открытым исходным кодом, вы можете создать его вместе.Адрес github: https://github.com/javagrowing/JGrowing Пожалуйста, дайте мне маленькую звезду.

Если вы считаете, что эта статья полезна для вас, то ваше внимание и пересылка - самая большая поддержка для меня, O(∩_∩)O: