Множество решений Baidu для часто задаваемых вопросов на собеседовании по Java

Java задняя часть .NET Байду

В конце экзамена средний балл класса был только вторым в классе.Завуч спросил: Все знают самую высокую вершину мира, гору Эверест, кто-нибудь знает, какая вторая по высоте вершина в мире? Как только завуч собирался продолжить говорить, он услышал в углу голос, который безмолвно вспомнился:К2"

предисловие

Статья из:блог woo woo woo.cn on.com/dudu19939/afraid…Этот вопрос задан другом в группе. Выше приведена ссылка на оригинальный блог этого друга. Я также столкнулся с этим вопросом в интервью на Baidu. В то время этот друг также общался со мной в этом блоге. Я также внес свой вклад один из способов, хе-хе, я наконец прочитал написанный текст этого друга, я думаю, что это очень хорошо написано, я надеюсь поделиться с вами (PS:Добро пожаловать в Подчинение).

тема

Следующий вопрос был задан мне, когда я был на Baidu 11 октября 2018 года:

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

решение

1. Присоединяйтесь к методу

Используйте функцию join(), чтобы дождаться завершения выполнения всех подпотоков, выполняется основной поток, thread.join() добавляет указанный поток к текущему потоку, а два поочередно выполняемых потока могут быть объединены в последовательно выполняемый поток. Например, если метод join() потока A вызывается в потоке B, он не продолжит выполнение потока B, пока поток A не завершит выполнение.

import java.util.Vector;

public class Test {
    public static void main(String[] args) throws InterruptedException {
        Vector<Thread> vector = new Vector<>();
        for(int i=0;i<5;i++) {
            Thread childThread= new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("子线程被执行");
                }
                
            });
            vector.add(childThread);
            childThread.start();
        }
        for(Thread thread : vector) {
            thread.join();
        }
        System.out.println("主线程被执行");
    }

Результаты

子线程被执行
子线程被执行
子线程被执行
子线程被执行
子线程被执行
主线程被执行

2. CountDownLatch ожидает завершения многопоточности

Концепция CountDownLatch

CountDownLatch — это класс инструмента синхронизации, используемый для координации синхронизации между несколькими потоками или для связи между потоками (вместо использования для взаимного исключения).

CountDownLatch позволяет потоку ждать, пока другие потоки завершат свою работу, прежде чем продолжить. Реализовано с помощью счетчика. Начальное значение счетчика — количество потоков. Когда каждый поток завершает свою задачу, значение счетчика уменьшается на единицу. Когда значение счетчика равно 0, это означает, что все потоки завершили задачу, и тогда поток, ожидающий CountDownLatch, может возобновить выполнение задачи. Использование CountDownLatch

Типичное использование CountDownLatch 1: поток ожидает завершения выполнения n потоков, прежде чем начать выполнение. Инициализируйте счетчик CountDownLatch на n new CountDownLatch(n), когда поток задачи завершит выполнение, уменьшите счетчик на 1 countdownlatch.countDown(), когда значение счетчика станет равным 0, поток await() на CountDownLatch будет будет разбужен. Типичный сценарий приложения — при запуске службы основной поток должен дождаться загрузки нескольких компонентов, прежде чем продолжить выполнение.

Типичное использование CountDownLatch 2: для достижения максимального параллелизма, когда несколько потоков начинают выполнять задачи. Обратите внимание, что это параллелизм, а не параллелизм, подчеркивающий, что несколько потоков начинают выполняться одновременно. Подобно гонке, поместите несколько потоков в начальную точку, подождите, пока стартовый пистолет выстрелит, а затем начните работать одновременно. Метод заключается в инициализации общего CountDownLatch(1) и инициализации его счетчика равным 1. Прежде чем приступить к выполнению задачи, сначала выполняется несколько потоков countdownlatch.await() Когда основной поток вызывает countDown(), счетчик становится равным 0, а несколько потоков одновременно проснуться. Недостатки CountDownLatch

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

import java.util.Vector;
import java.util.concurrent.CountDownLatch;

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        final CountDownLatch latch = new CountDownLatch(5);
        for(int i=0;i<5;i++) {
            Thread childThread= new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("子线程被执行");
                    latch.countDown();
                }
                
            });
            
            childThread.start();
            
        }
        latch.await();//阻塞当前线程直到latch中的值
        System.out.println("主线程被执行");
    }
    
}

Результаты:

子线程被执行
子线程被执行
子线程被执行
子线程被执行
子线程被执行
主线程被执行

3. Циклический барьер

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

В чем разница между countDownLatch и циклическим барьером, их отличие: countDownLatch можно использовать только один раз, а метод CyclicBarrier можно сбросить с помощью метода reset(), поэтому метод CyclicBarrier может обрабатывать более сложные бизнес-сценарии.

Я как-то видел в интернете образную метафору про countDownLatch и cycloBarrier, то есть, если countDownLatch используется в 100-метровом забеге, если один человек пересекает финишную черту, результат одного человека будет отправлен судьям, а 10 человек будут быть отправлены 10 раз.С CyclicBarrier все данные отправляются только один раз, когда последний человек пересекает финишную черту, и в этом разница.

package interview;

import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class Test3 {
    public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
        final CyclicBarrier barrier = new CyclicBarrier(5);
        for(int i=0;i<4;i++) {
            Thread childThread= new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("子线程被执行");
                    try {
                        barrier.await();
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    } catch (BrokenBarrierException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                }
                
            });
            
            childThread.start();
            
        }
        barrier.await();//阻塞当前线程直到latch中的值
        System.out.println("主线程被执行");
    }
}

Результаты:

子线程被执行
子线程被执行
子线程被执行
子线程被执行
主线程被执行

4. Используйте метод yield (обратите внимание, что этот метод доказал свою ненадежность в ходе личных испытаний!)

public class Test4 {
    public static void main(String[] args) throws InterruptedException {
        for(int i=0;i<5;i++) {
            Thread childThread= new Thread(new Runnable() {

                @Override
                public void run() {
                    // TODO Auto-generated method stub
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException e) {
                        // TODO Auto-generated catch block
                        e.printStackTrace();
                    }
                    System.out.println("子线程被执行");
                    
                }
                
            });
            
            childThread.start();
            
        }
        while (Thread.activeCount() > 2) {  //保证前面的线程都执行完
            Thread.yield();
        }
        System.out.println("主线程被执行");
    }
}

Результаты:

子线程被执行
子线程被执行
子线程被执行
子线程被执行
主线程被执行
子线程被执行

Почему метод yield имеет такую ​​проблему?

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

В потоках Java есть метод Thread.yield(), который многие люди переводят в создание потока. Как следует из названия, это означает, что когда поток использует этот метод, он отказывается от времени выполнения своего собственного ЦП и позволяет работать себе или другим потокам.

Например: сейчас очень много людей в очереди в туалет, и наконец настала очередь этого человека ходить в туалет, и вдруг этот человек говорит: «Я собираюсь устроить соревнование со всеми, кто может первым сходить в туалет!», а затем все люди, спешащие в туалет на той же стартовой линии, возможно, его схватил кто-то другой, а может, он схватил его сам. Мы также знаем, что у потоков есть проблема с приоритетом, поэтому те люди, у которых приоритет в руках, обязательно смогут схватить сиденье унитаза — не обязательно, просто у них выше вероятность, и они могут его схватить без привилегий.

Суть yield заключается в том, чтобы поместить текущий поток обратно в «очередь», которая захватывает процессорное время (очередь просто означает, что все потоки находятся в начальной строке. Это не очередь в прямом смысле).

5.FutureTast можно использовать для фиксации, аналогично роли CountDownLatch.

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Test5 {
     public static void main(String[] args) {
        MyThread td = new MyThread();
          
        //1.执行 Callable 方式,需要 FutureTask 实现类的支持,用于接收运算结果。
        FutureTask<Integer> result1 = new FutureTask<>(td);
        new Thread(result1).start();
        FutureTask<Integer> result2 = new FutureTask<>(td);
        new Thread(result2).start();
        FutureTask<Integer> result3 = new FutureTask<>(td);
        new Thread(result3).start();
          
        Integer sum;
        try {
                sum = result1.get();
                sum = result2.get();
                sum = result3.get();
                //这里获取三个sum值只是为了同步,并没有实际意义
                System.out.println(sum);
        } catch (InterruptedException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
        } catch (ExecutionException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
        }  //FutureTask 可用于 闭锁 类似于CountDownLatch的作用,在所有的线程没有执行完成之后这里是不会执行的
            
        System.out.println("主线程被执行");
           
        }
     
    }
     
    class MyThread implements Callable<Integer> {
     
        @Override
        public Integer call() throws Exception {
            int sum = 0;
            Thread.sleep(1000);
            for (int i = 0; i <= 10; i++) {
                sum += i;
            }
            System.out.println("子线程被执行");
            return sum;
        }
}

6. Используйте callable+future

Callable+Future наконец реализован в виде Callable+FutureTask. Вызывается таким образом: Future future = executor.submit(task);

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;

public class Test6 {
    public static void main(String[] args) throws InterruptedException, ExecutionException { 
        ExecutorService executor = Executors.newCachedThreadPool(); 
        Task task = new Task(); 
        Future<Integer> future1 = executor.submit(task); 
        Future<Integer> future2 = executor.submit(task);
        //获取线程执行结果,用来同步
        Integer result1 = future1.get();
        Integer result2 = future2.get();
        
        System.out.println("主线程执行");
        executor.shutdown();
        } 
}
class Task implements Callable<Integer>{ 
        @Override public Integer call() throws Exception { 
            int sum = 0; 
            //do something; 
            System.out.println("子线程被执行");
            return sum; 
            }
}

Результаты:

子线程被执行
子线程被执行
主线程执行

Пополнить:

1) И CountDownLatch, и CyclicBarrier могут реализовать ожидание между потоками, но у них разные фокусы:

CountDownLatch обычно используется для того, чтобы поток A ожидал выполнения задач несколькими другими потоками перед его выполнением;

CyclicBarrier обычно используется для того, чтобы группа потоков ждала друг друга до определенного состояния, а затем эта группа потоков выполнялась одновременно;

Кроме того, CountDownLatch нельзя использовать повторно, а CyclicBarrier можно использовать повторно.

2) Семафор на самом деле немного похож на замок, он обычно используется для управления доступом к группе ресурсов.

Класс CountDownLatch на самом деле управляется счетчиком.Нетрудно представить, что когда мы инициализировали CountDownLatch, ему передавалась переменная int. .Значение этой переменной уменьшается на 1, и для метода await() судят, равно ли значение переменной int 0. Если да, то это означает, что все операции выполнены, иначе продолжаем ждать. На самом деле, если вы понимаете AQS, вам должно быть легко думать, что вы можете использовать общий метод AQS для получения состояния синхронизации для выполнения этой функции. И CountDownLatch именно это и делает.

использованная литература:

blog.CSDN.net/U011277123/… blog.CSDN.net/Joe n Second/Aretti… blog.CSDN.net/WeChat_3855… blog.CSDN.net/свет Мира ах… Блог Woohoo.cn на.com/Сотни битв/…

Автор Qiaogori лично испытал осенний набор 2019 года, бакалавр и магистр HIT, Baidu признался инженером Java, приветствую всех, кто обратил на меня вниманиеПубличный аккаунт WeChat: Programmer Qiaogori.