4D Вопросы для интервью по параллельному программированию на Java (с ответами, коллекционное издание)

Java задняя часть
4D Вопросы для интервью по параллельному программированию на Java (с ответами, коллекционное издание)

Всем привет, меня зовут Дабин. В недавнем интервью я прочитал много личного опыта и нашел время, чтобы обобщить общие вопросы интервью о параллельном программировании на Java и поделиться ими с вами здесь ~

Каталог статей:

image.png image.png

Поделитесь репозиторием github с более чем 200 классическими компьютерными книгами, включая язык C, C++, Java, Python, внешний интерфейс, базу данных, операционную систему, компьютерную сеть, структуру данных и алгоритмы, машинное обучение, программирование и т. д. Вы можете стать звездой , и в следующий раз, когда вы найдете книгу, вы сможете искать ее прямо в ней, а склад постоянно обновляется~

GitHub.com/Tyson0314/Просто…

Если доступ к github недоступен, вы можете получить доступ к репозиторию gitee.

git ee.com/Пояс Тайсона/Срочно…

Пул потоков

Пул потоков: пул, который управляет потоками.

Зачем использовать пулы потоков?

  • Сокращение потребления ресурсов. Сократите стоимость создания и уничтожения потоков за счет повторного использования уже созданных потоков.
  • Улучшить отзывчивость. При поступлении задачи она может выполняться немедленно, не дожидаясь создания потока.
  • Улучшить управляемость потоками. Унифицированное управление потоками для предотвращения создания системой большого количества потоков одного типа и нехватки памяти.
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);

Принцип выполнения пула потоков?

Создание нового потока требует получения глобальной блокировки. Этот дизайн позволяет максимально избежать получения глобальной блокировки. Когда ThreadPoolExecutor завершит предварительный прогрев (количество текущих потоков больше или равно corePoolSize), большинство отправленных задач будет быть помещенным в BlockingQueue.

Чтобы наглядно описать выполнение пула потоков, используйте аналогию:

  • Основная нить уподобляется штатным сотрудникам компании.
  • Непрофильные потоки уподобляются аутсорсинговым сотрудникам
  • Очереди блокировки сравниваются с пулами запросов
  • Отправка задачи похожа на запрос

Каковы параметры пула потоков?

Общий конструктор для ThreadPoolExecutor:

public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler);
  • corePoolSize: при наличии новой задачи, если количество потоков в пуле потоков не достигает базового размера пула потоков, будет создан новый поток для выполнения задачи, в противном случае задача будет помещена в очередь блокировки . Если количество выживших потоков в пуле потоков всегда больше, чем corePoolSize, следует рассмотреть возможность увеличения corePoolSize.

  • maxPoolSize: когда очередь блокировки заполнена, если количество потоков в пуле потоков не превышает максимальное количество потоков, для выполнения задачи будет создан новый поток. В противном случае новая задача обрабатывается в соответствии с политикой отклонения. Неосновные потоки похожи на временно заимствованные ресурсы.Эти потоки должны завершаться после того, как время простоя превысит keepAliveTime, чтобы не тратить ресурсы впустую.

  • BlockingQueue: сохраняет задачи, ожидающие выполнения.

  • держать в живых:неосновная нитьВремя поддержания активности после простоя, этот параметр действителен только для неосновных потоков. Установите в 0, указывая, что избыточные простаивающие потоки будут немедленно завершены.

  • TimeUnit: единица времени

    TimeUnit.DAYS
    TimeUnit.HOURS
    TimeUnit.MINUTES
    TimeUnit.SECONDS
    TimeUnit.MILLISECONDS
    TimeUnit.MICROSECONDS
    TimeUnit.NANOSECONDS
    
  • ThreadFactory: Всякий раз, когда пул потоков создает новый поток, это делается с помощью метода фабрики потоков. В ThreadFactory определен только один метод newThread, который вызывается всякий раз, когда пулу потоков необходимо создать новый поток.

    public class MyThreadFactory implements ThreadFactory {
        private final String poolName;
        
        public MyThreadFactory(String poolName) {
            this.poolName = poolName;
        }
        
        public Thread newThread(Runnable runnable) {
            return new MyAppThread(runnable, poolName);//将线程池名字传递给构造函数,用于区分不同线程池的线程
        }
    }
    
  • RejectedExecutionHandler: когда и очередь, и пул потоков заполнены, новые задачи обрабатываются в соответствии с политикой отклонения.

    AbortPolicy:默认的策略,直接抛出RejectedExecutionException
    DiscardPolicy:不处理,直接丢弃
    DiscardOldestPolicy:将等待队列队首的任务丢弃,并执行当前任务
    CallerRunsPolicy:由调用线程处理该任务
    

Как установить размер пула потоков?

Если количество потоков в пуле потоков слишком мало, когда необходимо обработать большое количество запросов, ответ системы будет медленным, что повлияет на работу, и даже может накопиться большое количество задач в очереди задач, что приведет к в ООМ.

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

Задачи с интенсивным использованием ЦП (N+1): Эта задача в основном потребляет ресурсы ЦП.Количество потоков может быть установлено равным N (количество ядер ЦП) + 1. На один поток больше, чем число ядер ЦП, чтобы предотвратить приостановку задачи по какой-либо причине (блокировка потока, такие как операции ввода-вывода, ожидание блокировки, спящий режим потока) и влияние. Как только поток блокируется, ресурсы ЦП высвобождаются, и в этом случае дополнительный поток может полностью использовать время простоя ЦП.

Задачи с интенсивным вводом-выводом (2N): система будет тратить большую часть времени на обработку операций ввода-вывода, а потоки, ожидающие операций ввода-вывода, будут заблокированы, освобождая ресурсы ЦП, а затем передавая ЦП другим потокам для использования. Таким образом, в приложении задач с интенсивным вводом-выводом мы можем настроить несколько потоков.Конкретный метод расчета: оптимальное количество потоков = количество ядер ЦП * (1/загрузка ЦП) = количество ядер ЦП * (1 + (время ввода-вывода/затраты времени ЦП)), обычно можно установить на 2N

Какие существуют типы пулов потоков? Применимая сцена?

Распространенными пулами потоков являются FixedThreadPool, SingleThreadExecutor, CachedThreadPool и ScheduledThreadPool. Это все экземпляры ExecutorService (пул потоков).

FixedThreadPool

Пул потоков с фиксированным количеством потоков. В любой момент времени не более потоков nThreads активно выполняют задачи.

public static ExecutorService newFixedThreadPool(int nThreads) {
	return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}

При использовании неограниченной очереди LinkedBlockingQueue (емкость очереди Integer.MAX_VALUE) работающий пул потоков не будет отклонять задачу, то есть метод RejectedExecutionHandler.rejectedExecution() не будет вызываться.

maxThreadPoolSize — недопустимый параметр, поэтому установите его значение таким же, как coreThreadPoolSize.

keepAliveTime также является недопустимым параметром, для которого задано значение 0L, так как все потоки в этом пуле потоков являются основными потоками, а основные потоки не будут перезапущены (если не установлено значение executor.allowCoreThreadTimeOut(true)).

Применимые сценарии: он подходит для обработки задач с интенсивным использованием ЦП, гарантируя, что при длительном использовании ЦП рабочими потоками выделяется как можно меньше потоков, то есть он подходит для выполнения долгосрочных задач. Следует отметить, что FixedThreadPool не будет отклонять задачи,Когда задач много, это вызовет OOM.

SingleThreadExecutor

Пул потоков только с одним потоком.

public static ExecutionService newSingleThreadExecutor() {
	return new ThreadPoolExecutor(1, 1, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue<Runnable>());
}

Используйте неограниченную очередь LinkedBlockingQueue. В пуле потоков работает только один поток. Новые задачи помещаются в рабочую очередь. После того, как поток обработает задачи, он циклически получает задачи из очереди для выполнения. Гарантированное последовательное выполнение каждой задачи.

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

CachedThreadPool

Пул потоков, который создает новые потоки по мере необходимости.

public static ExecutorService newCachedThreadPool() {
	return new ThreadPoolExecutor(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new SynchronousQueue<Runnable>());
}

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

Используйте SynchronousQueue без емкости в качестве рабочей очереди пула потоков, когда в пуле потоков есть простаивающие потоки,SynchronousQueue.offer(Runnable task)Отправленные задачи будут обрабатываться бездействующими потоками, в противном случае для обработки задач будут созданы новые потоки.

Применимый сценарий: используется для одновременного выполнения большого количества краткосрочных небольших задач.CachedThreadPoolКоличество потоков, которые можно создать, равно Integer.MAX_VALUE ,Может быть создано большое количество потоков, вызывающих OOM.

ScheduledThreadPoolExecutor

Запускать задачу после заданной задержки или периодически. Он в основном не используется в реальных проектах, потому что есть другие варианты, такие какquartz.

очередь задач используетсяDelayQueueупаковалPriorityQueue,PriorityQueueЗадачи в очереди сортируются, и задачи с самым ранним временем выполнения выполняются первыми (т.е.ScheduledFutureTaskизtimeЕсли переменная меньше, то она будет выполнена первой), если время одинаковое, то задача, отправленная первой, будет выполнена первой (ScheduledFutureTaskизsquenceNumberМеньшие переменные выполняются первыми).

Выполняйте периодические шаги задачи:

  1. нить изDelayQueueпросроченныйScheduledFutureTask(DelayQueue.take()). Должные задачиScheduledFutureTaskВремя больше или равно текущему системному времени;
  2. выполнить этоScheduledFutureTask;
  3. ИсправлятьScheduledFutureTaskПеременная time — это время, которое должно быть выполнено в следующий раз;
  4. После изменения этого времениScheduledFutureTaskположить обратноDelayQueueсередина(DelayQueue.add()).

Применимые сценарии: сценарии, в которых задачи выполняются периодически, и сценарии, в которых необходимо ограничить количество потоков.

поток процесса

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

жизненный цикл нити

Initial (NEW): поток создан, но start() не вызывался.

RUNNABLE: включая состояния готовности и работы операционной системы.

Заблокировано (BLOCKED): обычно пассивно, ресурсы не могут быть получены при вытеснении ресурсов, пассивно приостановлены в памяти, ожидая освобождения ресурсов для их пробуждения. Заблокированные потоки освобождают ЦП, а не память.

Ожидание (WAITING): Поток, входящий в это состояние, должен ждать, пока другие потоки выполнят определенное действие (уведомление или прерывание).

Ожидание тайм-аута (TIMED_WAITING): это состояние отличается от WAITING, которое может вернуться само по себе через указанное время.

Завершено (TERMINATED): Указывает, что поток завершил выполнение.

Изображение предоставлено: Искусство параллельного программирования на Java

Говорите о прерывании потока?

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

Существует три важных метода прерывания потока:

1. java.lang.Thread#прерывание

Вызовите метод interrupt() целевого потока, отправьте сигнал прерывания целевому потоку, и поток будет помечен как прерванный.

2. java.lang.Thread#isInterrupted()

Чтобы определить, прерван ли целевой поток, флаг прерывания не сбрасывается.

3. java.lang.Thread#прервано

Чтобы определить, прерван ли целевой поток, флаг прерывания будет очищен.

private static void test2() {
    Thread thread = new Thread(() -> {
        while (true) {
            Thread.yield();

            // 响应中断
            if (Thread.currentThread().isInterrupted()) {
                System.out.println("Java技术栈线程被中断,程序退出。");
                return;
            }
        }
    });
    thread.start();
    thread.interrupt();
}

Какие есть способы создать нить?

  • Создайте несколько потоков, расширив класс Thread
  • Создайте несколько потоков, реализуя интерфейс Runnable, который может реализовать совместное использование ресурсов между потоками.
  • Реализуйте интерфейс Callable и создайте поток через интерфейс FutureTask.
  • Используйте платформу Executor для создания пулов потоков.

Наследовать Thread для создания потокакод показывает, как показано ниже. Метод run() является методом обратного вызова после того, как JVM создает поток уровня операционной системы. Его нельзя вызвать вручную. Вызов вручную эквивалентен вызову обычного метода.

/**
 * @author: 程序员大彬
 * @time: 2021-09-11 10:15
 */
public class MyThread extends Thread {
    public MyThread() {
    }

    @Override
    public void run() {
        for (int i = 0; i < 10; i++) {
            System.out.println(Thread.currentThread() + ":" + i);
        }
    }

    public static void main(String[] args) {
        MyThread mThread1 = new MyThread();
        MyThread mThread2 = new MyThread();
        MyThread myThread3 = new MyThread();
        mThread1.start();
        mThread2.start();
        myThread3.start();
    }
}

Runnable создает код потока:

/**
 * @author: 程序员大彬
 * @time: 2021-09-11 10:04
 */
public class RunnableTest {
    public static  void main(String[] args){
        Runnable1 r = new Runnable1();
        Thread thread = new Thread(r);
        thread.start();
        System.out.println("主线程:["+Thread.currentThread().getName()+"]");
    }
}

class Runnable1 implements Runnable{
    @Override
    public void run() {
        System.out.println("当前线程:"+Thread.currentThread().getName());
    }
}

Преимущества реализации интерфейса Runnable по сравнению с расширением класса Thread:

  1. Совместное использование ресурсов, подходящее для нескольких потоков одного и того же программного кода для обработки одного и того же ресурса.
  2. Ограничения одиночного наследования в java можно избежать
  3. Пул потоков может быть размещен только в потоках, реализующих классы Runable или Callable, и не может быть размещен непосредственно в классах, наследующих Thread.

Callable создает код потока:

/**
 * @author: 程序员大彬
 * @time: 2021-09-11 10:21
 */
public class CallableTest {
    public static void main(String[] args) {
        Callable1 c = new Callable1();

        //异步计算的结果
        FutureTask<Integer> result = new FutureTask<>(c);

        new Thread(result).start();

        try {
            //等待任务完成,返回结果
            int sum = result.get();
            System.out.println(sum);
        } catch (InterruptedException | ExecutionException e) {
            e.printStackTrace();
        }
    }

}

class Callable1 implements Callable<Integer> {

    @Override
    public Integer call() throws Exception {
        int sum = 0;

        for (int i = 0; i <= 100; i++) {
            sum += i;
        }
        return sum;
    }
}

Используйте Executor для создания кода потока:

/**
 * @author: 程序员大彬
 * @time: 2021-09-11 10:44
 */
public class ExecutorsTest {
    public static void main(String[] args) {
        //获取ExecutorService实例,生产禁用,需要手动创建线程池
        ExecutorService executorService = Executors.newCachedThreadPool();
        //提交任务
        executorService.submit(new RunnableDemo());
    }
}

class RunnableDemo implements Runnable {
    @Override
    public void run() {
        System.out.println("大彬");
    }
}

Что такое взаимоблокировка потока?

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

Как показано на рисунке ниже, поток A содержит ресурс 2, а поток B — ресурс 1. Они оба хотят одновременно обращаться за ресурсами друг друга, поэтому эти два потока будут ждать друг друга и войдут в состояние взаимоблокировки.

Следующий пример иллюстрирует взаимоблокировку потока, а код исходит из красоты параллельного программирования.

public class DeadLockDemo {
    private static Object resource1 = new Object();//资源 1
    private static Object resource2 = new Object();//资源 2

    public static void main(String[] args) {
        new Thread(() -> {
            synchronized (resource1) {
                System.out.println(Thread.currentThread() + "get resource1");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource2");
                synchronized (resource2) {
                    System.out.println(Thread.currentThread() + "get resource2");
                }
            }
        }, "线程 1").start();

        new Thread(() -> {
            synchronized (resource2) {
                System.out.println(Thread.currentThread() + "get resource2");
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread() + "waiting get resource1");
                synchronized (resource1) {
                    System.out.println(Thread.currentThread() + "get resource1");
                }
            }
        }, "线程 2").start();
    }
}

Вывод кода выглядит следующим образом:

Thread[线程 1,5,main]get resource1
Thread[线程 2,5,main]get resource2
Thread[线程 1,5,main]waiting get resource2
Thread[线程 2,5,main]waiting get resource1

Поток A получает блокировку монитора resource1 через synchronized (resource1), а затем передает Thread.sleep(1000);, чтобы позволить потоку A заснуть на 1 с, чтобы позволить потоку B выполниться, а затем получить блокировку монитора resource2. После того, как поток A и поток B спят, они оба начинают запрашивать ресурсы друг друга, а затем два потока переходят в состояние ожидания друг друга, что приводит к взаимоблокировке.

Как происходит взаимоблокировка потока? Как этого избежать?

Четыре необходимых условия возникновения взаимоблокировки:

  • Взаимное исключение: ресурс может использоваться только одним процессом одновременно (независимость от ресурсов).

  • Запрос и удержание: когда процесс заблокирован запросом ресурсов, он будет удерживать полученные ресурсы (не снимать блокировку).

  • Отсутствие лишения: ресурсы, полученные процессом, не могут быть принудительно лишены (вырваны ресурсы), пока они не будут использованы.

  • Циклическое ожидание: между несколькими процессами формируется своего рода циклическое ожидание закрытия ресурса (бесконечный цикл).

Способы избежать тупика:

  • Первое условие «взаимное исключение» нельзя нарушать, потому что блокировка должна обеспечивать взаимное исключение.
  • Подайте заявку на все ресурсы сразу, нарушив условие «держать и ждать»
  • Когда поток, занимающий некоторые ресурсы, в дальнейшем обращается за другими ресурсами, если он не может подать заявку на них, он будет активно освобождать занимаемые им ресурсы, разрушая условие «не вытеснения».
  • Подавать заявки на ресурсы по порядку, нарушая условие «циклического ожидания».

В чем разница между запуском потока и запуском?

Вызов метода start() используется для запуска потока. Когда наступает очередь выполнения потока, он автоматически вызывает run(); прямой вызов метода run() не может достичь цели запуска нескольких потоков, что эквивалентно линейное выполнение объекта Thread основным потоком методом run(). Метод start() пары потоков может быть вызван только один раз, а многократные вызовы вызовут исключение java.lang.IllegalThreadStateException; для метода run() нет ограничений.

Каковы методы потоков?

join

Thread.join(), поток потока создается в основном, thread.join()/thread.join(long millis) вызывается в основном, основной поток отказывается от управления процессором, поток входит в состояние WAITING/TIMED_WAITING и ждет, пока поток потока продолжит выполнение основного потока после выполнения.

public final void join() throws InterruptedException {
    join(0);
}

yield

Thread.yield(), текущий поток должен вызвать этот метод, текущий поток отказывается от полученного кванта времени ЦП, но не освобождает ресурсы блокировки и переходит из состояния выполнения в состояние готовности, позволяя ОС выбрать нить снова. Роль: Пусть потоки с одинаковым приоритетом выполняются по очереди, но нет гарантии, что они будут выполняться по очереди. На практике нет гарантии, что yield() будет уступать, потому что уступающий поток все еще может быть снова выбран планировщиком потоков. Thread.yield() не вызывает блокировки. Этот метод похож на sleep(), за исключением того, что пользователь не может указать, как долго делать паузу.

public static native void yield(); //static方法

sleep

Thread.sleep(long миллисекунды), этот метод должен вызываться текущим потоком, текущий поток входит в состояние TIMED_WAITING, отдает ресурсы процессора, но не снимает блокировку объекта и возобновляет работу через указанное время. Роль: лучший способ дать другим потокам возможность выполниться.

public static native void sleep(long millis) throws InterruptedException;//static方法

Основной принцип изменчивости

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

  1. Когда записывается изменчивая переменная, JVM отправляет процессору инструкцию с префиксом LOCK для записи данных строки кэша, в которой находится переменная, обратно в системную память.
  2. Благодаря протоколу когерентности кеша каждый процессор проверяет, истек ли срок действия его кеша, прослушивая данные, распространяющиеся по шине.Когда процессор обнаруживает, что адрес памяти, соответствующий его строке кеша, был изменен, он копирует текущий процессор. устанавливается в недопустимое состояние.Когда процессор изменяет данные, он повторно считывает данные из системной памяти в кэш-память процессора.

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

Две функции ключевого слова volatile:

  1. Он обеспечивает видимость, когда разные потоки работают с общими переменными, то есть поток модифицирует значение переменной, и новое значение сразу видно другим потокам.
  2. Переупорядочивание инструкций отключено.

Переупорядочивание инструкций предназначено для оптимизации инструкций и повышения эффективности выполнения программы с помощью JVM, чтобы максимально улучшить параллелизм, не влияя на результаты выполнения однопоточных программ. Компилятор Java будет вставлять в соответствующие места при создании серии инструкций内存屏障инструкция по отключению переупорядочивания процессоров. Вставка барьера памяти эквивалентна указанию ЦП и компилятору, что те, которые предшествуют этой команде, должны быть выполнены первыми, а те, которые следуют за этой командой, должны быть выполнены. Для операции записи в volatile-поле модель памяти Java будет вставлять инструкцию барьера записи после операции записи, которая сбрасывает ранее записанное значение в память.

Принцип AQS

AQS, AbstractQueuedSynchronizer, абстрактный синхронизатор очередей, определяет набор структур синхронизатора для многопоточного доступа к общим ресурсам. От него зависит реализация многих параллельных инструментов, таких как обычно используемый ReentrantLock/Semaphore/CountDownLatch.

AQS использует состояние переменной-члена типа int Volatile для представления состояния синхронизации, изменения значения синхронного состояния с помощью CAS. Когда поток вызывает метод Lock, если состояние = 0, блокировки с общим ресурсом нет, а состояние = 1 будет получено без какого-либо потока. Если состояние = 1, поток в настоящее время использует общие переменные, и другие потоки должны быть добавлены в очередь синхронизации.

private volatile int state;//共享变量,使用volatile修饰保证线程可见性

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

Каково использование синхронизированного?

  1. Модифицированный обычный метод: воздействуя на текущий экземпляр объекта, чтобы получить блокировку текущего экземпляра объекта перед вводом кода синхронизации
  2. Модифицированный статический метод: работая с текущим классом, перед вводом кода синхронизации необходимо получить блокировку объекта текущего класса.К статическому статическому методу и блоку кода synchronized(class) добавляется ключевое слово synchronized для блокировки класса Class.
  3. Модифицированный блок кода: укажите объект блокировки, заблокируйте данный объект и получите блокировку данного объекта перед входом в базу кода синхронизации.

Каковы функции Synchronized?

Атомарность: доступ к коду синхронизации, обеспечивающему взаимное исключение потоков; Видимость: чтобы гарантировать, что изменение общих переменных можно будет увидеть вовремя, по сути, через модель памяти Java, «перед разблокировкой переменной она должна быть синхронизирована с основной памятью; если переменная заблокирована, рабочая память будет очищен. Прежде чем механизм выполнения использует эту переменную, ему необходимо повторно инициализировать значение переменной из операции загрузки основной памяти или операции назначения», чтобы гарантировать; Упорядочивание: эффективно решить проблему переупорядочивания, то есть «операция разблокировки происходит до того, как операция блокировки сталкивается с той же блокировкой».

Каков лежащий в основе принцип реализации synchronized?

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

Он содержит счетчик внутри. Когда счетчик равен 0, он может быть успешно получен. После получения счетчик блокировки устанавливается на 1, то есть увеличивается на 1. Соответственно, после выполнения инструкции monitorexit установить счетчик блокировок в 0 , указывая на то, что блокировка снята. Если получение блокировки объекта не удается, текущий поток заблокируется и будет ждать, пока блокировка не будет снята другим потоком.

Синхронизированный модифицированный метод не имеет инструкции monitorenter и инструкции monitorexit. Вместо этого, это действительно флаг ACC_SYNCHRONIZED, который указывает, что метод является синхронизированным методом. JVM использует флаг доступа ACC_SYNCHRONIZED, чтобы определить, объявлен ли метод как синхронизированный метод, чтобы выполнить соответствующий синхронный вызов.

Как ReentrantLock обеспечивает повторный вход?

ReentrantLock имеет настраиваемый синхронизатор Sync. При блокировке объект потока помещается в двусвязный список с помощью алгоритма CAS. Каждый раз при получении блокировки проверяйте, согласуется ли текущий поддерживаемый идентификатор потока с текущим запрошенным идентификатором потока. , если если оно согласовано, состояние синхронизации увеличивается на 1, указывая на то, что блокировка была получена текущим потоком несколько раз.

Разница между ReentrantLock и синхронизированным

  1. Используя ключевое слово synchronized для достижения синхронизации, поток автоматически снимает блокировку после выполнения синхронизированного блока кода, в то время как ReentrantLock необходимо снять блокировку вручную.
  2. Synchronized — это несправедливая блокировка, а ReentrantLock может быть установлен как справедливая блокировка.
  3. Потоки, ожидающие получения блокировки на ReentrantLock, могут быть прерваны, и поток может отказаться от ожидания блокировки. А синхронизированные будут ждать бесконечно долго.
  4. ReentrantLock может установить тайм-аут для получения блокировки. Получает блокировку до указанного срока и возвращает значение, если блокировка не была получена до указанного срока.
  5. Метод tryLock () ReentrantLock может попытаться получить неблокирующую блокировку и вернуться сразу после вызова этого метода.Если он может быть получен, он вернет true, в противном случае он вернет false.

Разница между ожиданием() и сном()

Тот же пункт:

  1. Приостановить текущий поток и дать возможность другим потокам
  2. Любой поток, который прерывается во время ожидания, вызовет InterruptedException.

разница:

  1. wait() — это метод суперкласса Object, а sleep() — метод класса Thread.
  2. Удержание блокировки отличается, wait() снимает блокировку, а sleep() не снимает блокировку.
  3. Методы пробуждения не совсем одинаковы, wait() полагается на notify или notifyAll, прерывание и указанное время для пробуждения; в то время как sleep() достигает указанного времени для пробуждения
  4. Вызов obj.wait() должен сначала получить блокировку объекта, в то время как Thread.sleep() этого не делает.

Разница между ожиданием(), уведомлением() и приостановкой(), возобновлением()

  • wait() заставляет поток войти в блокирующее состояние ожидания и освобождает блокировку
  • notify() пробуждает поток в состоянии ожидания, которое обычно используется в сочетании с методом wait().
  • Suspend() переводит поток в заблокированное состояние и не возобновляет его автоматически. Для повторного перехода потока в исполняемое состояние необходимо вызвать соответствующий метод submit(). Метод suspend() может легко вызвать взаимоблокировку.
  • Метод возобновления() используется в сочетании с методом suspend().

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

В чем разница между Runnable и Callable?

  • Метод интерфейса Callable — call(), а метод Runnable — run();
  • Метод call интерфейса Callable имеет возвращаемое значение и поддерживает дженерики, а метод run интерфейса Runnable не имеет возвращаемого значения.
  • Метод вызываемого интерфейса call() позволяет генерировать исключения, в то время как метод run() интерфейса Runnable не может продолжать генерировать исключения;

В чем разница между volatile и синхронизированным?

  1. volatile можно использовать только с переменными, а synchronized можно использовать с классами, переменными, методами и блоками кода.
  2. Волатильность гарантирует видимость, синхронизация гарантирует атомарность и видимость.
  3. volatile отключает переупорядочивание инструкций, а synchronized — нет.
  4. volatile не блокирует, синхронизированный блокирует.

Как контролировать порядок выполнения потоков?

Предположим, что есть три потока T1, T2, T3. Как вы гарантируете, что T2 будет выполнен после выполнения T1, а T3 будет выполнен после выполнения T2?

можно использоватьметод присоединениярешить эту проблему. Например, в потоке A вызов метода соединения потока B означает **: A ожидает завершения выполнения потока B (освобождает право на выполнение ЦП), а затем продолжает выполнение. **

код показывает, как показано ниже:

public class ThreadTest {

    public static void main(String[] args) {

        Thread spring = new Thread(new SeasonThreadTask("春天"));
        Thread summer = new Thread(new SeasonThreadTask("夏天"));
        Thread autumn = new Thread(new SeasonThreadTask("秋天"));

        try
        {
            //春天线程先启动
            spring.start();
            //主线程等待线程spring执行完,再往下执行
            spring.join();
            //夏天线程再启动
            summer.start();
            //主线程等待线程summer执行完,再往下执行
            summer.join();
            //秋天线程最后启动
            autumn.start();
            //主线程等待线程autumn执行完,再往下执行
            autumn.join();
        } catch (InterruptedException e)
        {
            e.printStackTrace();
        }
    }
}

class SeasonThreadTask implements Runnable{

    private String name;

    public SeasonThreadTask(String name){
        this.name = name;
    }

    @Override
    public void run() {
        for (int i = 1; i <4; i++) {
            System.out.println(this.name + "来了: " + i + "次");
            try {
                Thread.sleep(100);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

результат операции:

春天来了: 1次
春天来了: 2次
春天来了: 3次
夏天来了: 1次
夏天来了: 2次
夏天来了: 3次
秋天来了: 1次
秋天来了: 2次
秋天来了: 3次

Обязательно ли оптимистическая блокировка хороша?

Оптимистическая блокировка позволяет избежать явления пессимистической блокировки эксклюзивных объектов и повышает производительность параллелизма, но у нее есть и недостатки:

  • Оптимистическая блокировка может гарантировать только атомарные операции над общей переменной. Если добавить одну или несколько переменных, оптимистическая блокировка станет неэффективной, но блокировку мьютекса можно легко решить, независимо от количества объектов и размера детализации объектов.
  • Длительное вращение может привести к большим накладным расходам. Если CAS не работает в течение длительного времени и продолжает вращаться, это приведет к большим накладным расходам ЦП.
  • проблема с АБА. Основная идея CAS состоит в том, чтобы определить, было ли изменено значение памяти, сравнивая, совпадает ли значение памяти с ожидаемым значением, но логика этого суждения не является строгой.Если значение памяти изначально было A, оно позже был изменен потоком на B и, наконец, изменен на Если возвращается A, CAS считает, что значение памяти не изменилось, но на самом деле оно было изменено другими потоками.Эта ситуация оказывает большое влияние на результаты работы сценариев которые зависят от значений процесса. Решение состоит в том, чтобы ввести номер версии и увеличивать номер версии на единицу при каждом обновлении переменной.

Что такое поток демона?

Поток демона — это особый тип процесса, который работает в фоновом режиме. Он не зависит от управляющего терминала и периодически выполняет какую-либо задачу или ожидает наступления какого-либо события. В Java потоки сборки мусора — это специальные потоки демона.

Межпоточная связь

volatile

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

synchronized

Гарантирует видимость и эксклюзивность доступа потоков к переменным.

механизм уведомления об ожидании

wait/notify — это метод объекта Object.Вызов wait/notify должен сначала получить блокировку объекта. После того, как объект вызывает ожидание, поток снимает блокировку и помещает поток в очередь ожидания объекта.Когда поток уведомлений вызывает метод notify() объекта, ожидающий поток не выходит из ожидания немедленно, и ему подождите, пока поток уведомлений снимет блокировку (поток уведомлений выполняет блок кода После завершения синхронизации), подождите, пока поток в очереди получит блокировку, и блокировка может быть возвращена из метода wait() только в том случае, если блокировка получена успешно, то есть предпосылка возврата из метода ожидания состоит в том, что поток получает блокировку.

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

ThreadLocal

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

Принцип ThreadLocal

Каждый поток имеет ThreadLocalMap (внутренний класс ThreadLocal), ключ элементов в Map — ThreadLocal, а значение соответствует копии переменной потока.

Вызовите threadLocal.set() --> вызовите getMap(Thread) --> верните ThreadLocalMap --> map.set(this, value) текущего потока, это ThreadLocal

public void set(T value) {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
}

ThreadLocalMap getMap(Thread t) {
    return t.threadLocals;
}

void createMap(Thread t, T firstValue) {
    t.threadLocals = new ThreadLocalMap(this, firstValue);
}

Вызов get() --> вызов getMap(Thread) --> возврат текущего потока ThreadLocalMap --> map.getEntry(this), возвращаемое значение

    public T get() {
        Thread t = Thread.currentThread();
        ThreadLocalMap map = getMap(t);
        if (map != null) {
            ThreadLocalMap.Entry e = map.getEntry(this);
            if (e != null) {
                @SuppressWarnings("unchecked")
                T result = (T)e.value;
                return result;
            }
        }
        return setInitialValue();
    }

Ключом типа threadLocals ThreadLocalMap является объект ThreadLocal, поскольку каждый поток может иметь несколько переменных threadLocal, таких как longLocal и stringLocal.

public class ThreadLocalDemo {
    ThreadLocal<Long> longLocal = new ThreadLocal<>();

    public void set() {
        longLocal.set(Thread.currentThread().getId());
    }
    public Long get() {
        return longLocal.get();
    }

    public static void main(String[] args) throws InterruptedException {
        ThreadLocalDemo threadLocalDemo = new ThreadLocalDemo();
        threadLocalDemo.set();
        System.out.println(threadLocalDemo.get());

        Thread thread = new Thread(() -> {
            threadLocalDemo.set();
            System.out.println(threadLocalDemo.get());
        }
        );

        thread.start();
        thread.join();

        System.out.println(threadLocalDemo.get());
    }
}

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

Причина утечки памяти ThreadLocal?

Каждый поток имеет внутреннее свойство ThreadLocalMap, ключ карты — ThraLocal, который определяется как слабая ссылка, а значение — тип строгой ссылки. Ключ автоматически перерабатывается во время сборки мусора, и значение этого значения зависит от жизненного цикла объекта Thread. Как правило, объекты Thread повторно используются пулами потоков для экономии ресурсов, что приводит к длительному жизненному циклу объектов Thread, поэтому всегда существует прочная связь в цепочке ссылок: Thread --> ThreadLocalMap --> Entry --> Value, т.к. задача выполняется, значение может становиться все больше и больше и не может быть освобождено, что в конечном итоге приводит к утечке памяти.

Решение. После каждого использования ThreadLocal вызывайте его метод remove() и вручную удаляйте соответствующую пару ключ-значение, чтобы избежать утечек памяти.

currentTime.set(System.currentTimeMillis());
result = joinPoint.proceed();
Log log = new Log("INFO",System.currentTimeMillis() - currentTime.get());
currentTime.remove();

Каковы сценарии использования ThreadLocal?

ThreadLocal Применимый сценарий: каждый поток должен иметь свой собственный экземпляр, и экземпляр должен быть общим для нескольких методов, то есть изоляция экземпляра между потоками и совместное использование между методами выполняются одновременно. Например, в веб-приложении Java каждый поток имеет свой отдельный экземпляр Session, который можно реализовать с помощью ThreadLocal.

Классификация замков

Справедливая блокировка и нечестная блокировка

Блокировки объектов устанавливаются в порядке доступа к потокам. синхронизировано — это несправедливая блокировка. Блокировка — это несправедливая блокировка по умолчанию. Можно установить равноправную блокировку. Равноправная блокировка повлияет на производительность.

public ReentrantLock() {
    sync = new NonfairSync();
}

public ReentrantLock(boolean fair) {
    sync = fair ? new FairSync() : new NonfairSync();
}

Общие и эксклюзивные блокировки

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

Пессимистическая блокировка и оптимистичная блокировка

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

Оптимистическая блокировка не блокирует ресурсы. Все потоки могут получать доступ к одному и тому же ресурсу и изменять его. Если конфликта нет, изменение выполняется успешно и завершается. В противном случае цикл продолжит попытки. Наиболее распространенной реализацией оптимистической блокировки является CAS.

Оптимистическая блокировка обычно имеет следующие два способа:

  1. Он реализуется с помощью механизма записи версии данных, который является наиболее часто используемой реализацией оптимистической блокировки. Добавление идентификатора версии к данным обычно достигается путем добавления числового поля версии в таблицу базы данных. При чтении данных вместе считывайте значение поля версии и добавляйте единицу к значению версии каждый раз при обновлении данных. Когда мы отправляем обновление, сравниваем информацию о текущей версии соответствующей записи в таблице базы данных со значением версии, извлеченным в первый раз.Если номер текущей версии таблицы базы данных равен значению версии, извлеченному для первого время, они будут обновлены, в противном случае они считаются просроченными данными.
  2. Используйте временные метки. Поле добавляется в таблицу базы данных, а тип поля использует временную метку (timestamp).Подобно версии выше, также проверяется при отправке обновления.Временная метка данных в текущей базе данных сравнивается с полученной временной меткой перед обновлением.Если они непротиворечивы, то ОК, в противном случае возникает конфликт версий.

Применимая сцена:

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

CAS

Что такое КАС?

Полное название CAS — Compare And Swap, которое является основной реализацией оптимистической блокировки. CAS реализует синхронизацию переменных между несколькими потоками без использования блокировок. AQS внутри ReentrantLock и CAS используются внутри атомарных классов.

Алгоритм CAS включает три операнда:

  • Значение памяти V, которое необходимо прочитать и записать.
  • Значение A для сравнения.
  • Новое значение B для записи.

Только если значение V равно A , значение V будет автоматически обновлено новым значением B, в противном случае будут повторяться попытки до тех пор, пока значение не будет успешно обновлено.

В качестве примера возьмем AtomicInteger. Нижний уровень метода getAndIncrement() AtomicInteger — это реализация CAS, а код ключа —compareAndSwapInt(obj, offset, expect, update), что означает, что еслиobjвнутриvalueиexpectравным, это доказывает, что ни один другой поток не изменил эту переменную, а затем обновите ее доupdate, если они не равны, он будет повторять попытки до тех пор, пока значение не будет успешно обновлено.

Проблемы с КАС?

Три основные проблемы CAS:

  1. АВА-проблема. CAS необходимо проверить, изменилось ли значение памяти при работе со значением, и значение памяти будет обновлено, если изменений нет. Но если значение памяти изначально было A, потом стало B, а потом снова стало A, то когда CAS его проверит, то обнаружит, что значение не изменилось, а на самом деле изменилось. Решение проблемы ABA состоит в том, чтобы добавить номер версии перед переменной и добавлять единицу к номеру версии каждый раз, когда переменная обновляется, чтобы процесс изменения начинался сA-B-Aстал1A-2B-3A.

    Начиная с версии 1.5, JDK предоставляет класс AtomicStampedReference для решения проблемы ABA, атомарно обновляя ссылочные типы номерами версий.

  2. Длительное время цикла и высокие накладные расходы. Если операция CAS безуспешна в течение длительного времени, это приведет к ее постоянному вращению, что приведет к очень большим накладным расходам ЦП.

  3. Только гарантированные атомарные операции над общей переменной. При выполнении операции с общей переменной CAS может гарантировать атомарность операции, но при работе с несколькими общими переменными CAS не может гарантировать атомарность операции.

    Начиная с Java 1.5, JDK предоставляет класс AtomicReference для обеспечения атомарности между ссылочными объектами, и несколько переменных могут быть помещены в один объект для выполнения операций CAS.

Инструменты параллелизма

В пакете параллелизма JDK содержится несколько очень полезных классов инструментов параллелизма. Вспомогательные классы CountDownLatch, CyclicBarrier и Semaphore предоставляют средства управления параллельным потоком.

CountDownLatch

CountDownLatch используется для ожидания потока другими потоками.закончить заданиеВыполните снова, аналогично функции thread.join(). Обычный сценарий приложения — запуск нескольких потоков для одновременного выполнения задачи и ожидание выполнения всех задач перед выполнением конкретных операций, таких как суммирование статистических результатов.

public class CountDownLatchDemo {
    static final int N = 4;
    static CountDownLatch latch = new CountDownLatch(N);

    public static void main(String[] args) throws InterruptedException {

       for(int i = 0; i < N; i++) {
            new Thread(new Thread1()).start();
       }

       latch.await(1000, TimeUnit.MILLISECONDS); //调用await()方法的线程会被挂起,它会等待直到count值为0才继续执行;等待timeout时间后count值还没变为0的话就会继续执行
       System.out.println("task finished");
    }

    static class Thread1 implements Runnable {

        @Override
        public void run() {
            try {
                System.out.println(Thread.currentThread().getName() + "starts working");
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                latch.countDown();
            }
        }
    }
}

результат операции:

Thread-0starts working
Thread-1starts working
Thread-2starts working
Thread-3starts working
task finished

CyclicBarrier

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

public CyclicBarrier(int parties, Runnable barrierAction) {
}
public CyclicBarrier(int parties) {
}

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

public class CyclicBarrierTest {
    // 请求的数量
    private static final int threadCount = 10;
    // 需要同步的线程数量
    private static final CyclicBarrier cyclicBarrier = new CyclicBarrier(5);

    public static void main(String[] args) throws InterruptedException {
        // 创建线程池
        ExecutorService threadPool = Executors.newFixedThreadPool(10);

        for (int i = 0; i < threadCount; i++) {
            final int threadNum = i;
            Thread.sleep(1000);
            threadPool.execute(() -> {
                try {
                    test(threadNum);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
            });
        }
        threadPool.shutdown();
    }

    public static void test(int threadnum) throws InterruptedException, BrokenBarrierException {
        System.out.println("threadnum:" + threadnum + "is ready");
        try {
            /**等待60秒,保证子线程完全执行结束*/
            cyclicBarrier.await(60, TimeUnit.SECONDS);
        } catch (Exception e) {
            System.out.println("-----CyclicBarrierException------");
        }
        System.out.println("threadnum:" + threadnum + "is finish");
    }

}

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

threadnum:0is ready
threadnum:1is ready
threadnum:2is ready
threadnum:3is ready
threadnum:4is ready
threadnum:4is finish
threadnum:3is finish
threadnum:2is finish
threadnum:1is finish
threadnum:0is finish
threadnum:5is ready
threadnum:6is ready
...

Когда все четыре потока достигают состояния барьера, один из четырех потоков будет выбран для выполнения Runnable.

Разница между CyclicBarrier и CountDownLatch

И CyclicBarrier, и CountDownLatch могут реализовать ожидание между потоками.

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

Semaphore

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

public class SemaphoreDemo {
    public static void main(String[] args) {
        final int N = 7;
        Semaphore s = new Semaphore(3);
        for(int i = 0; i < N; i++) {
            new Worker(s, i).start();
        }
    }

    static class Worker extends Thread {
        private Semaphore s;
        private int num;
        public Worker(Semaphore s, int num) {
            this.s = s;
            this.num = num;
        }

        @Override
        public void run() {
            try {
                s.acquire();
                System.out.println("worker" + num +  " using the machine");
                Thread.sleep(1000);
                System.out.println("worker" + num +  " finished the task");
                s.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

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

worker0 using the machine
worker1 using the machine
worker2 using the machine
worker2 finished the task
worker0 finished the task
worker3 using the machine
worker4 using the machine
worker1 finished the task
worker6 using the machine
worker4 finished the task
worker3 finished the task
worker6 finished the task
worker5 using the machine
worker5 finished the task

Атомный класс

атомарный класс базового типа

Обновление примитивных типов атомарно

  • AtomicInteger: целочисленный атомарный класс
  • AtomicLong: длинный целочисленный атомарный класс
  • AtomicBoolean : логический атомарный класс

Общие методы класса AtomicInteger:

public final int get() //获取当前的值
public final int getAndSet(int newValue)//获取当前的值,并设置新的值
public final int getAndIncrement()//获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值
boolean compareAndSet(int expect, int update) //如果输入的数值等于预期值,则以原子方式将该值设置为输入值(update)
public final void lazySet(int newValue)//最终设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

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

Атомарный класс типа массива

Обновить элемент в массиве атомарно

  • AtomicIntegerArray: атомарный класс целочисленного массива
  • AtomicLongArray: атомарный класс для массивов длинных целых чисел.
  • AtomicReferenceArray: атомный класс массива ссылочных типов

Общие методы класса AtomicIntegerArray:

public final int get(int i) //获取 index=i 位置元素的值
public final int getAndSet(int i, int newValue)//返回 index=i 位置的当前的值,并将其设置为新值:newValue
public final int getAndIncrement(int i)//获取 index=i 位置元素的值,并让该位置的元素自增
public final int getAndDecrement(int i) //获取 index=i 位置元素的值,并让该位置的元素自减
public final int getAndAdd(int i, int delta) //获取 index=i 位置元素的值,并加上预期的值
boolean compareAndSet(int i, int expect, int update) //如果输入的数值等于预期值,则以原子方式将 index=i 位置的元素值设置为输入值(update)
public final void lazySet(int i, int newValue)//最终 将index=i 位置的元素设置为newValue,使用 lazySet 设置之后可能导致其他线程在之后的一小段时间内还是可以读到旧的值。

атомарный класс ссылочного типа

  • AtomicReference: атомарный класс ссылочного типа
  • AtomicStampedReference: атомарный класс ссылочного типа с номером версии. Этот класс связывает целочисленное значение со ссылкой и может использоваться для разрешения данных атомарного обновления и номеров версий данных, что может решить проблемы ABA, которые могут возникнуть при использовании CAS для атомарных обновлений.
  • AtomicMarkableReference : атомарные обновления ссылочных типов с маркерами. Этот класс связывает логические теги со ссылками

Эта статья была включена в репозиторий github, который используется для обмена сводкой знаний, связанных с Java, включая основу Java, MySQL, Spring Boot, MyBatis, Redis, RabbitMQ, компьютерную сеть, структуру данных и алгоритм и т. д. Добро пожаловать в пр и звезда!

GitHub.com/Tyson0314/J…

Если доступ к github недоступен, вы можете получить доступ к репозиторию gitee.

git ee.com/Tyson Tape/ja…