Другие ответы правильные, с важными моментами. Кроме того, позвольте мне показать, как будущие технологии, разрабатываемые в Project Loom, упростят такой код.
Проект Ткацкий станок
Project Loom внесет некоторые изменения в Java. Экспериментальные сборки технологии Loom, основанные на ранней версии Java 17, уже доступно. Команда Loom запрашивает отзывы.
AutoCloseable
и попробуйте с ресурсами
Одно изменение заключается в том, что ExecutorService
расширяет AutoCloseable
. Это означает, что мы можем использовать синтаксис try-with-resources для удобно и автоматически закрывать сервис после завершения блока try.
Блокировать, пока отправленные задачи не будут выполнены
Кроме того, поток управления блокируется в этом блоке попыток до тех пор, пока все отправленные задачи не будут выполнены/не пройдены/отменены. Нет необходимости отслеживать ход выполнения отдельных задач, если вам это не нужно.
try (
ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
)
{
… submit tasks to the executor service, to be run on background threads.
}
// At this point, all submitted are done/failed/canceled.
// At this point, the executor service is automatically being shut down.
AtomicInteger
Как говорили другие, использование вами примитивов int
для переменных a
и b
в потоках может завершиться неудачей из-за проблем с видимостью в Модель памяти Java. Один из вариантов — пометить их как volatile
.
Я предпочитаю альтернативу, используя классы Atomic…
. Замените эти переменные int
на AtomicInteger
для переноса увеличивающегося числа счетчиков.
Отметьте эти поля-члены final
, чтобы каждый экземпляр никогда не заменялся.
// Member fields
final AtomicInteger a, b;
// Constructor
public Incrementor ( )
{
this.a = new AtomicInteger();
this.b = new AtomicInteger();
}
Чтобы увеличить на единицу значение в AtomicInteger
, мы вызываем incrementAndGet
. Этот вызов возвращает новое увеличенное число. Поэтому я изменил сигнатуру ваших методов добавления, чтобы показать, что мы можем вернуть новое значение, если это когда-либо понадобится.
// Logic
public int addA ( )
{
return this.a.incrementAndGet();
}
public int addB ( )
{
return this.b.incrementAndGet();
}
Виртуальные потоки
Еще одна функция, появившаяся в Project Loom, — это виртуальные потоки, также известные как волокна. Многие из этих облегченных потоков отображаются для работы в потоках платформы/ядра. Если ваш код часто блокируется, то использование виртуальных потоков значительно повысит производительность вашего приложения. Используйте новую функцию, вызвав Executors.newVirtualThreadExecutor
.
try (
ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
)
{ … }
Пример класса
Я написал класс Incrementor
, похожий на ваш. Использование такого класса выглядит так:
Incrementor incrementor = new Incrementor();
try (
ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
)
{
for ( int i = 0 ; i < 10 ; i++ )
{
executorService.submit( ( ) -> {
int newValueA = incrementor.addA();
int newValueB = incrementor.addB();
System.out.println( "Thread " + Thread.currentThread().getId() + " incremented a & b to: " + newValueA + " & " + newValueB + " at " + Instant.now() );
} );
}
}
Координация a
и b
Предостережение: как уже отмечали другие, такой код не увеличивает атомарно a
и b
вместе синхронно. Судя по всему, вам это не нужно, поэтому я игнорирую этот вопрос. Вы можете увидеть это поведение в действии в примере вывода запуска, показанном внизу ниже, где два потока чередуются во время их доступа к a
и b
. Выдержка здесь:
Thread 24 incremented a & b to: 10 & 9 at 2021-02-09T02:21:30.270246Z
Thread 23 incremented a & b to: 9 & 10 at 2021-02-09T02:21:30.270246Z
Полный код класса
Соберите весь этот код.
Обратите внимание на простоту кода, когда (а) используется Loom и (б) используются константы Atomic…
. Нет необходимости ни в семафорах, ни в защелках, ни в CompletableFuture
, ни в вызове ExecutorService#shutdown
.
package work.basil.example;
import java.time.Instant;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
public class Incrementor
{
// Member fields
final AtomicInteger a , b ;
// Constructor
public Incrementor ( )
{
this.a = new AtomicInteger();
this.b = new AtomicInteger();
}
// Logic
public int addA ( )
{
return this.a.incrementAndGet();
}
public int addB ( )
{
return this.b.incrementAndGet();
}
}
И метод main
для демонстрации использования этого класса.
public static void main ( String[] args )
{
// Exercise this class by instantiating, then incrementing ten times.
System.out.println( "INFO - `main` starting the demo. " + Instant.now() );
Incrementor incrementor = new Incrementor();
try (
ExecutorService executorService = Executors.newVirtualThreadExecutor() ;
)
{
for ( int i = 0 ; i < 10 ; i++ )
{
executorService.submit( ( ) -> {
int newValueA = incrementor.addA();
int newValueB = incrementor.addB();
System.out.println( "Thread " + Thread.currentThread().getId() + " incremented a & b to: " + newValueA + " & " + newValueB + " at " + Instant.now() );
} );
}
}
System.out.println( "INFO - At this point all submitted tasks are done/failed/canceled, and executor service is shutting down. " + Instant.now() );
System.out.println( "incrementor.a.get() = " + incrementor.a.get() );
System.out.println( "incrementor.b.get() = " + incrementor.b.get() );
System.out.println( "INFO - `main` ending. " + Instant.now() );
}
Когда бег.
INFO - `main` starting the demo. 2021-02-09T02:21:30.173816Z
Thread 18 incremented a & b to: 4 & 4 at 2021-02-09T02:21:30.245812Z
Thread 14 incremented a & b to: 1 & 1 at 2021-02-09T02:21:30.242306Z
Thread 20 incremented a & b to: 6 & 6 at 2021-02-09T02:21:30.246784Z
Thread 21 incremented a & b to: 8 & 8 at 2021-02-09T02:21:30.269666Z
Thread 22 incremented a & b to: 7 & 7 at 2021-02-09T02:21:30.269666Z
Thread 17 incremented a & b to: 3 & 3 at 2021-02-09T02:21:30.243580Z
Thread 24 incremented a & b to: 10 & 9 at 2021-02-09T02:21:30.270246Z
Thread 23 incremented a & b to: 9 & 10 at 2021-02-09T02:21:30.270246Z
Thread 16 incremented a & b to: 2 & 2 at 2021-02-09T02:21:30.242335Z
Thread 19 incremented a & b to: 5 & 5 at 2021-02-09T02:21:30.246646Z
INFO - At this point all submitted tasks are done/failed/canceled, and executor service is shutting down. 2021-02-09T02:21:30.279542Z
incrementor.a.get() = 10
incrementor.b.get() = 10
INFO - `main` ending. 2021-02-09T02:21:30.285862Z
09.02.2021
a
иb
, чтобы иметь предсказуемое поведение. 08.02.2021