Concurrent programming solution
We often encounter concurrency problems in the development process. When we encounter concurrency problems, we usually solve them through locks. In fact, there are two implementation schemes for locks as follows
- signal
- Tube
The tube process and the semaphore are equivalent. The tube process can be used to realize the semaphore, and the semaphore can also be used to realize the tube process. The tube process is more friendly to developers
signal
Semaphore is a synchronization mechanism of the operating system, composed of an integer variable sem and two atomic operations
P (Prolaag, Dutch try to reduce): sem-1, if sem<0, enter the waiting queue, otherwise continue
V (Verhoog, Dutch increase): sem+1, if sem<=0, there are threads in the waiting queue, Wake up a thread
The sem variable can only be modified by PV operation when the initialization is completed. The operation guarantees the atomicity of the PV operation. P may be blocked, but V will not be blocked.
Code
public class Semaphore {
private int sem;
private WaitQueue q;
public void P() {
sem--;
if (sem < 0) {
add this thread t to q;
// 阻塞线程
block(t);
}
}
public void V() {
sem++;
if (sem <= 0) {
remove a thread t from q;
// 唤醒线程
wakeup(t)
}
}
}
Semaphores can be divided into 2 categories
- Binary semaphore: the number of resources is 0 or 1
- Resource semaphore: the number of resources is any non-negative value
The semaphore has the following functions
- Realize mutually exclusive access to critical section (area that can only be accessed by one thread at a time)
- Realize conditional synchronization
Realize mutually exclusive access to the critical section
Semaphore mutex = new Semaphore(1);
mutex.P();
// do something
mutex.V();
The initial value of the semaphore must be 1, PV operation paired use
Realize conditional synchronization
Semaphore condition = new Semaphore(0);
// ThreadA,进入等待队列中
condition.P();
// ThreadB,唤醒等待线程 ThreadA
condition.V();
The initial value of the semaphore must be 0, ThreadA will be blocked when performing P operations, and ThreadB will wake up the waiting thread ThreadA when performing V operations
Use semaphores to implement blocking queues
Use a binary semaphore mutex to achieve mutually exclusive access
Use two resource semaphores notFul, notEmptyl to achieve conditional synchronization
public class BlockingQueueUseSemaphore<T> {
private final Object[] items;
private Semaphore notFull;
private Semaphore notEmpty;
private Semaphore mutex;
private int putIndex;
private int takeIndex;
public BlockingQueueUseSemaphore(int capacity) {
this.items = new Object[capacity];
notFull = new Semaphore(capacity);
notEmpty = new Semaphore(0);
mutex = new Semaphore(1);
}
public void enq(T x) throws InterruptedException {
notFull.acquire();
mutex.acquire();
items[putIndex] = x;
if (++putIndex == items.length) {
putIndex = 0;
}
mutex.release();
notEmpty.release();
}
public T deq() throws InterruptedException {
notEmpty.acquire();
mutex.acquire();
T x = (T) items[takeIndex];
if (++takeIndex == items.length) {
takeIndex = 0;
}
mutex.release();
notFull.release();
return x;
}
}
Tube
In order to solve the trouble of pairing the semaphore in the critical region PV operation, the tube process gathers the paired PV operations together to generate a new concurrent programming method, which is more consistent with the object-oriented thinking.
The concept of condition variables is introduced in the monitoring process, and each shared variable corresponds to a waiting queue
Synchronized is implemented based on the monitor, which contains only one synchronization queue, and a waiting queue
AQS is also implemented based on the monitor. It contains only one synchronization queue, but it can contain multiple waiting queues.
Use monitors to implement blocking queues
Because the design idea of AQS in Java is management, I use the related APIs in AQS to realize this function
It can be achieved with one shared variable and two condition variables of the monitor
public class BlockingQueueUseMonitor<T> {
private final Object[] items;
private final Lock lock;
private Condition notFull;
private Condition notEmpty;
private int count;
private int putIndex;
private int takeIndex;
public BlockingQueueUseMonitor(int capacity) {
this.items = new Object[capacity];
lock = new ReentrantLock();
notFull = lock.newCondition();
notEmpty = lock.newCondition();
}
public void enq(T x) throws InterruptedException {
lock.lock();
try {
while (count == items.length) {
// 等待队列不满
notFull.await();
}
items[putIndex] = x;
if (++putIndex == items.length) {
putIndex = 0;
}
count++;
notEmpty.signal();
} finally {
lock.unlock();
}
}
public T deq() throws InterruptedException {
lock.lock();
try {
while (count == 0) {
// 等待队列不空
notEmpty.await();
}
T x = (T) items[takeIndex];
if (++takeIndex == items.length) {
takeIndex = 0;
}
count--;
notFull.signal();
return x;
} finally {
lock.unlock();
}
}
}
Reference blog
好文
[1]https://www.cnblogs.com/binarylei/p/12544002.html#26-aqs-%E5%92%8C-synchronized-%E5%8E%9F%E7%90%86
[2]https://www.codenong.com/cs109504287/
管程
[3]https://time.geekbang.org/column/article/86089
信号量
[4]https://time.geekbang.org/column/article/88499