Язык Java предоставляет множество способов обработки условий гонки при работе с параллельными потоками, обращающимися к общему ресурсу. Некоторые включают;
- Используя ключевое слово volatile
- Использование классов, доступных в java.util.concurrent и java.util.concurrent.atomic
- Синхронизированные блоки
- Используя семафор
Конечно, я мог бы не знать о многих других вещах. На сегодня я хочу показать вам пример, использующий
Семафор Это было введено в JDK 1.5 и дает разработчику возможность беспрепятственно получать и снимать блокировки. Также пример, который я покажу, является гипотетическим сценарием, который я использовал только для того, чтобы показать, чего можно достичь с помощью семафора, и поэтому, пожалуйста, не смотрите на внутренние детали кода 🙂 ..
Таким образом, сценарий как таковой, есть кэш в памяти, содержащий объекты типа
‘Человек’. Пользователи могут вставлять и извлекать записи, используя кэш. Проблема в том, что мы собираемся контролировать одновременный доступ к нашему кэшу в памяти с помощью семафоров. Теперь я не хочу утомлять вас большим количеством текста, поэтому давайте приступим к делу и покажем немного кода;
|
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
|
import java.util.concurrent.Semaphore;/** * This class will allow thread to acquire and release locks as required * * @author dinuka.arseculeratne * */public class PersonLock { /** * We do not want multiple lock objects lying around so we make ths class * singleton */ private PersonLock() { } /** * Bill Pugh's way of lazy initializing the singleton instance * * @author dinuka.arseculeratne * */ private static class SingletonHolder { public static final PersonLock INSTANCE = new PersonLock(); } /** * Use this method to get a reference to the singleton instance of * {@link PersonLock} * * @return the singleton instance */ public static PersonLock getInstance() { return SingletonHolder.INSTANCE; } /** * In this sample, we allow only one thread at at time to update the cache * in order to maintain consistency */ private Semaphore writeLock = new Semaphore(1); /** * We allow 10 concurrent threads to access the cache at any given time */ private Semaphore readLock = new Semaphore(10); public void getWriteLock() throws InterruptedException { writeLock.acquire(); } public void releaseWriteLock() { writeLock.release(); } public void getReadLock() throws InterruptedException { readLock.acquire(); } public void releaseReadLock() { readLock.release(); }} |
Этот класс будет обрабатывать процесс получения и освобождения блокировок, необходимых для обеспечения безопасности нашего потока кеша. Я использовал два отдельных замка для чтения и записи. Основанием для этого было позволить пользователям читать данные, хотя они могут быть устаревшими во время чтения.
Обратите внимание, что я использовал
«десять» здесь означает, что десять потоков могут одновременно получать блокировки и обращаться к кешу для чтения. Далее вы можете увидеть в блокировке записи, я использовал
один ‘, который означает, что только один поток может одновременно обращаться к кешу для помещения в него элементов. Это важно для обеспечения согласованности в кэше. То есть я не хочу, чтобы несколько потоков пытались вставить элементы на карту, что привело бы к непредсказуемому поведению (по крайней мере, в некоторых случаях). Есть в основном два способа получения блокировки с помощью семафора.
1. acqu () : блокирующий вызов, который ожидает, пока блокировка не будет снята или поток не прерван
2. tryAcquire () : это неблокирующий вызов, который немедленно вернется и вернет истину или ложь, показывая, была ли получена блокировка или нет.
Здесь я использовал блокирующий вызов захвата, потому что я хочу, чтобы поток ожидал, пока блокировка не станет доступной. Конечно, это будет зависеть от вашего варианта использования. Вы также можете определить период ожидания в методе tryAcquire (), чтобы поток не ожидал блокировки бесконечно.
Далее класс хранения ниже показывает, как я использовал класс блокировки для вставки и чтения данных в кеше.
|
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
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
|
import java.util.HashMap;import java.util.Map;/** * A mock storage to hold the person objects in a map * * @author dinuka.arseculeratne * */public class PersonStorage { private Map<Integer, Person> personCache = new HashMap<Integer, Person>(); private int counter = 0; /** * This class is made singleton and hence the constructor is made private */ private PersonStorage() { } /** * Bill Pugh's way of lazy initializing the singleton instance * * @author dinuka.arseculeratne * */ private static final class SingletonHolder { public static final PersonStorage INSTANCE = new PersonStorage(); } /** * Use this method to get a reference to the singleton instance of * {@link PersonStorage} * * @return the singleton instance */ public static PersonStorage getInstance() { return SingletonHolder.INSTANCE; } /** * Inserts the person into the map. Note that we use defensive copying so * that even if the client changes the object later on, those changes will * not be reflected in the object within the map * * @param person * the instance of {@link Person} to be inserted * @return the key which signifies the location of the person object * @throws InterruptedException */ public int putPerson(Person person) throws InterruptedException { Person copyPerson = person.copyPerson(); personCache.put(++counter, copyPerson); return counter; } /** * Here as well we use defensive copying so that the value of the object * reference within the map is not passed in to the calling party. * * @param id * the id representing the location of the object within the map * @return the instance of the {@link Person} represented by the key passed * in * @throws InterruptedException */ public Person retrievePerson(int id) throws InterruptedException { PersonLock.getInstance().getReadLock(); if (!personCache.containsKey(id)) { throw new RuntimeException('Key is not found'); } PersonLock.getInstance().releaseReadLock(); return personCache.get(id).copyPerson(); }} |
Очевидно, что код будет работать и без блокировок, но проблема в том, что приложение будет несовместимым и будет давать разные результаты при каждом запуске. Это не то, что вы хотите, чтобы ваше приложение делало, и поэтому с блокировками вы гарантируете, что ваше приложение работает стабильно.
И, наконец, небольшой тестовый класс, чтобы показать, как он будет себя вести; не то чтобы здесь мы получили блокировку перед вызовом метода putPerson () и снимаем блокировку в блоке finally, чтобы гарантировать снятие блокировки.
|
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
51
52
53
54
55
56
|
/** * A test class to demonstrate the locking at work * * @author dinuka.arseculeratne * */public class TestLock { public static void main(String[] args) throws InterruptedException { Thread t1 = new Thread(new Runnable() { @Override public void run() { Person p1 = new Person(1L, 'Test1', 'XYZ'); try {PersonLock.getInstance().getWriteLock();PersonStorage.getInstance().putPerson(p1); } catch (InterruptedException e) { // Exception handling need to be done e.printStackTrace(); } finally{ PersonLock.getInstance().releaseWriteLock(); } } }); Thread t2 = new Thread(new Runnable() { @Override public void run() { Person p2 = new Person(2L, 'Test123', 'ABC'); try {PersonLock.getInstance().getWriteLock(); PersonStorage.getInstance().putPerson(p2); } catch (InterruptedException e) { // Exception handling need to be done } finally{ PersonLock.getInstance().releaseWriteLock(); } } }); t1.start(); t2.start(); System.out.println(PersonStorage.getInstance().retrievePerson(2)); }} |
На этом я заканчиваю свое краткое введение в использование Sempahores для обеспечения безопасности вашего кода. Для тех, кто хочет поиграть с кодом, вы можете получить его у
здесь Попробуйте снять блокировки в классе Storage и посмотрите, как он ведет себя при каждом запуске. Вы увидите возможные условия гонки.
Ссылка: Блокировка с помощью семафора: пример от нашего партнера по JCG Динуки Арурилератне в блоге My Journey By IT .