Soluciones de programación concurrente
A menudo nos encontramos con problemas de concurrencia en el proceso de desarrollo, cuando nos encontramos con problemas de concurrencia, generalmente los resolvemos mediante bloqueos. De hecho, hay dos esquemas de implementación para bloqueos como sigue
- señal
- Tubo
El proceso del tubo y el semáforo son equivalentes. El proceso del tubo se puede utilizar para realizar el semáforo, y el semáforo también se puede utilizar para realizar el proceso del tubo. El proceso del tubo es más amigable para los desarrolladores
señal
El semáforo es un mecanismo de sincronización del sistema operativo, compuesto por una variable entera sem y dos operaciones atómicas
P (Prolaag, holandés intenta reducir): sem-1, si sem <0, ingresa a la cola de espera, de lo contrario continúa
V (Verhoog, holandés aumento): sem + 1, si sem <= 0, hay hilos en espera cola, despierta un hilo
La variable sem sólo puede ser modificada por operación PV cuando se completa la inicialización. La operación garantiza la atomicidad de la operación PV. P puede ser bloqueado, pero V no será bloqueado.
Código
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)
}
}
}
Los semáforos se pueden dividir en 2 categorías
- Semáforo binario: el número de recursos es 0 o 1
- Semáforo de recursos: el número de recursos es cualquier valor no negativo
El semáforo tiene las siguientes funciones
- Obtenga acceso mutuamente exclusivo a la sección crítica (área a la que solo puede acceder un hilo a la vez)
- Realice la sincronización condicional
Obtenga acceso mutuamente excluyente a la sección crítica
Semaphore mutex = new Semaphore(1);
mutex.P();
// do something
mutex.V();
El valor inicial del semáforo debe ser 1, uso emparejado de operación fotovoltaica
Realice la sincronización condicional
Semaphore condition = new Semaphore(0);
// ThreadA,进入等待队列中
condition.P();
// ThreadB,唤醒等待线程 ThreadA
condition.V();
El valor inicial del semáforo debe ser 0, ThreadA se bloqueará al realizar operaciones P, y ThreadB despertará el thread en espera ThreadA al realizar operaciones V
Utilice semáforos para implementar colas de bloqueo
Utilice un mutex de semáforo binario para lograr un acceso mutuamente exclusivo
Utilice dos semáforos de recursos notFul, notEmptyl para lograr la sincronización condicional
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;
}
}
Tubo
Para resolver el problema de emparejar el semáforo en la operación PV de la región crítica, el proceso del tubo reúne las operaciones PV emparejadas para generar un nuevo método de programación concurrente, que es más consistente con el pensamiento orientado a objetos.
El concepto de variables de condición se introduce en el proceso de seguimiento y cada variable compartida corresponde a una cola de espera.
Sincronizado se implementa en base al monitor, que contiene solo una cola de sincronización, y
también se implementa una cola de espera AQS basada en el monitor, que contiene solo una cola de sincronización, pero puede contener múltiples colas de espera.
Utilice monitores para implementar colas de bloqueo
Debido a que la idea de diseño de AQS en Java es la administración, utilizo las API relacionadas en AQS para realizar esta función.
Se puede lograr con una variable compartida y dos variables de condición del 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();
}
}
}
Blog de referencia
好 文
[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