Статьи

AtomicInteger на Java и Round-Robin

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

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

Наш первый вариант использования — это функция на веб-странице, к которой можно обращаться несколько раз.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
package com.gkatzioura.concurrency;
 
import java.util.concurrent.atomic.AtomicInteger;
 
public class AtomicIntegerExample {
 
    private AtomicInteger atomicInteger = new AtomicInteger();
    public void serveRequest() {
        atomicInteger.incrementAndGet();
        /**
         * logic
         */
    }
 
    public int requestsServed() {
        return atomicInteger.get();
    }
}

И тест для нашего варианта использования

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
package com.gkatzioura.concurrency;
 
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
 
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
 
public class AtomicIntegerExampleTest {
 
    private AtomicIntegerExample atomicIntegerExample;
 
    @BeforeEach
    void setUp() {
        atomicIntegerExample = new AtomicIntegerExample();
    }
 
    @Test
    void testConcurrentIncrementAndGet() throws ExecutionException, InterruptedException {
        final int threads = 10;
 
        ExecutorService executorService = Executors.newFixedThreadPool(threads);
 
        List<Future> futures = new ArrayList();
 
        for (int i = 0; i  {
                atomicIntegerExample.serveRequest();
                return null;
            }));
        }
 
        for(Future future: futures) {
            future.get();
        }
 
        Assertions.assertEquals(10,atomicIntegerExample.requestsServed());
    }
 
}

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

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
package com.gkatzioura.concurrency;
 
import java.util.concurrent.atomic.AtomicInteger;
 
public class AtomicIntegerRoundRobin {
 
    private final int totalIndexes;
    private final AtomicInteger atomicInteger = new AtomicInteger(-1);
 
    public AtomicIntegerRoundRobin(int totalIndexes) {
        this.totalIndexes = totalIndexes;
    }
 
    public int index() {
        int currentIndex;
        int nextIndex;
 
        do {
            currentIndex = atomicInteger.get();
            nextIndex = currentIndex< Integer.MAX_VALUE ? currentIndex+1: 0;
        } while (!atomicInteger.compareAndSet(currentIndex, nextIndex));
 
        return nextIndex % totalIndexes;
    }
 
}

TotalIndex — это общее количество индексов. Когда запрашивается запрос следующего индекса, счетчик должен быть увеличен, и будет выполнена операция сравнения и задания. Если произойдет сбой из-за другого потока, он снова попытается выполнить операцию и получит следующее значение счетчика.
Операция по модулю даст текущий индекс. Если атомное целое число достигает максимального значения, оно должно быть сброшено до нуля. Сброс может вызвать крайний случай и изменить порядок индексов. Если это проблема, вы можете настроить максимальное значение на основе общего размера индекса, чтобы избежать этого.

Также некоторые испытания по этому вопросу.

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
package com.gkatzioura.concurrency;
 
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
 
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
 
class AtomicIntegerRoundRobinTest {
 
    private static final int MAX_INDEX = 10;
 
    private AtomicIntegerRoundRobin atomicIntegerRoundRobin;
 
    @BeforeEach
    void setUp() {
        atomicIntegerRoundRobin = new AtomicIntegerRoundRobin(MAX_INDEX);
    }
 
    @Test
    void testIndexesSerially() {
        for(long i=0;i<MAX_INDEX*20;i++) {
            System.out.println(atomicIntegerRoundRobin.index());
        }
 
        Assertions.assertEquals(0, atomicIntegerRoundRobin.index());
    }
 
    @Test
    void testIndexesConcurrently() throws ExecutionException, InterruptedException {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
 
        List<Future> futures = new ArrayList();
 
        for (int i = 0; i  atomicIntegerRoundRobin.index()));
        }
 
        for(Future future: futures) {
            System.out.println(future.get());
        }
 
        Assertions.assertEquals(0,atomicIntegerRoundRobin.index());
    }
 
}

Опубликовано на Java Code Geeks с разрешения Эммануила Гкациоураса, партнера нашей программы JCG. Смотрите оригинальную статью здесь: AtomicInteger на Java и Round-Robin

Мнения, высказанные участниками Java Code Geeks, являются их собственными.