Análisis de código fuente de semáforo de semáforo multiproceso de Java

Recientemente me dediqué a la ejecución de múltiples hilos. Leí un artículo seco y lo reimprimí.

Blog original: https://www.cnblogs.com/tong-yuan/p/Semaphore.html

 

problema

(1) ¿Qué es el semáforo?

(2) ¿Cuáles son las características de Semaphore?

(3) ¿En qué escenarios se usa habitualmente Semaphore?

(4) ¿Se puede aumentar o disminuir dinámicamente el número de licencias de Semaphore?

(5) ¿Cómo se da cuenta Semaphore de la limitación de corriente?

Introducción

Semáforo, semáforo, guarda una serie de permisos (permisos), cada llamada a adquirir () consumirá un permiso, cada llamada a liberar () devolverá un permiso.

característica

El semáforo se usa generalmente para limitar el número de accesos a recursos compartidos al mismo tiempo, lo que a menudo se denomina límite actual.

Aprendamos a implementar Semaphore en Java.

Estructura de clase

Semáforo

Semaphore incluye un sincronizador Sync que implementa AQS y sus dos subclases FairSync y NonFairSync, lo que muestra que Semaphore también distingue entre el modo justo y el modo injusto.

Análisis de código fuente

Basado en el análisis anterior de ReentrantLock y ReentrantReadWriteLock, este artículo es relativamente simple. Algunos de los métodos mencionados anteriormente se omitirán directamente. Aquellos que estén interesados ​​pueden ir al final del artículo para ver el artículo anterior.

Sincronización de clase interna

// java.util.concurrent.Semaphore.Sync
abstract static class Sync extends AbstractQueuedSynchronizer {
    private static final long serialVersionUID = 1192457210091910933L;
    // 构造方法,传入许可次数,放入state中
    Sync(int permits) {
        setState(permits);
    }
    // 获取许可次数
    final int getPermits() {
        return getState();
    }
    // 非公平模式尝试获取许可
    final int nonfairTryAcquireShared(int acquires) {
        for (;;) {
            // 看看还有几个许可
            int available = getState();
            // 减去这次需要获取的许可还剩下几个许可
            int remaining = available - acquires;
            // 如果剩余许可小于0了则直接返回
            // 如果剩余许可不小于0,则尝试原子更新state的值,成功了返回剩余许可
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
    // 释放许可
    protected final boolean tryReleaseShared(int releases) {
        for (;;) {
            // 看看还有几个许可
            int current = getState();
            // 加上这次释放的许可
            int next = current + releases;
            // 检测溢出
            if (next < current) // overflow
                throw new Error("Maximum permit count exceeded");
            // 如果原子更新state的值成功,就说明释放许可成功,则返回true
            if (compareAndSetState(current, next))
                return true;
        }
    }
    // 减少许可
    final void reducePermits(int reductions) {
        for (;;) {
            // 看看还有几个许可
            int current = getState();
            // 减去将要减少的许可
            int next = current - reductions;
            // 检测举出
            if (next > current) // underflow
                throw new Error("Permit count underflow");
            // 原子更新state的值,成功了返回true
            if (compareAndSetState(current, next))
                return;
        }
    }
    // 销毁许可
    final int drainPermits() {
        for (;;) {
            // 看看还有几个许可
            int current = getState();
            // 如果为0,直接返回
            // 如果不为0,把state原子更新为0
            if (current == 0 || compareAndSetState(current, 0))
                return current;
        }
    }
}

A través de varios métodos de implementación de Sync, hemos obtenido la siguiente información:

(1) El permiso se pasa cuando se construye el método;

(2) El permiso se almacena en el estado de la variable de estado;

(3) Al intentar obtener una licencia, el valor del estado se reduce en 1;

(4) Cuando el valor de estado es 0, no se pueden obtener más permisos;

(5) Cuando se libera una licencia, el valor del estado aumenta en 1;

(6) El número de licencias se puede cambiar de forma dinámica;

Clase interna NonfairSync

// java.util.concurrent.Semaphore.NonfairSync
static final class NonfairSync extends Sync {
    private static final long serialVersionUID = -2694183684443567898L;
    // 构造方法,调用父类的构造方法
    NonfairSync(int permits) {
        super(permits);
    }
    // 尝试获取许可,调用父类的nonfairTryAcquireShared()方法
    protected int tryAcquireShared(int acquires) {
        return nonfairTryAcquireShared(acquires);
    }
}

En el modo no justo, llame directamente a nonfairTryAcquireShared () de la clase principal para intentar obtener el permiso.

FairSync de clase interna

// java.util.concurrent.Semaphore.FairSync
static final class FairSync extends Sync {
    private static final long serialVersionUID = 2014338818796000944L;
    // 构造方法,调用父类的构造方法
    FairSync(int permits) {
        super(permits);
    }
    // 尝试获取许可
    protected int tryAcquireShared(int acquires) {
        for (;;) {
            // 公平模式需要检测是否前面有排队的
            // 如果有排队的直接返回失败
            if (hasQueuedPredecessors())
                return -1;
            // 没有排队的再尝试更新state的值
            int available = getState();
            int remaining = available - acquires;
            if (remaining < 0 ||
                compareAndSetState(available, remaining))
                return remaining;
        }
    }
}

En el modo justo, primero verifique si hay una cola antes, si hay una cola, no obtendrá el permiso y entrará en la cola, de lo contrario, intentará actualizar atómicamente el valor del estado.

Método de construcción

// 构造方法,创建时要传入许可次数,默认使用非公平模式
public Semaphore(int permits) {
    sync = new NonfairSync(permits);
}
// 构造方法,需要传入许可次数,及是否公平模式
public Semaphore(int permits, boolean fair) {
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

Al crear un semáforo, debe pasar el número de licencias.

El semáforo también está en modo injusto de forma predeterminada, pero puede llamar al segundo constructor para declararlo como modo justo.

Los siguientes métodos parecen ser relativamente simples después de estudiar el contenido anterior: Tong Ge solo enumera algunas funciones compatibles con Semaphore.

Los siguientes métodos se describen todos para el modo injusto.

adquirir () método

public void acquire() throws InterruptedException {
    sync.acquireSharedInterruptibly(1);
}

Para obtener una licencia, el método predeterminado es interrumpible. Si el intento de obtener una licencia falla, ingresará a la cola de AQS.

adquirir ininterrumpidamente () 方法

public void acquireUninterruptibly() {
    sync.acquireShared(1);
}

Obtenga una licencia de manera ininterrumpida. Si falla el intento de obtener una licencia, ingresará a la cola de AQS.

método tryAcquire ()

public boolean tryAcquire() {
    return sync.nonfairTryAcquireShared(1) >= 0;
}

Intente obtener una licencia, use el modo injusto de Sync para intentar obtener un método de licencia y regrese independientemente de si se obtiene la licencia, solo intente una vez y no entrará en la cola.

tryAcquire (tiempo de espera prolongado, unidad TimeUnit) 方法

public boolean tryAcquire(long timeout, TimeUnit unit)
    throws InterruptedException {
    return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}

Intente obtener una licencia, primero intente obtener una licencia, si falla, esperará el período de tiempo de espera.Si no se obtiene una licencia dentro de este período, devuelva falso, de lo contrario devuelva verdadero;

método release ()

public void release() {
    sync.releaseShared(1);
}

Liberar una licencia Cuando se libera una licencia, el valor del estado aumentará en 1, y se activará el siguiente hilo que esté esperando para obtener la licencia.

método de adquisición (permisos int)

public void acquire(int permits) throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireSharedInterruptibly(permits);
}

Obtenga varias licencias a la vez y se pueden interrumpir.

adquirir ininterrumpidamente (permisos int) 方法

public void acquireUninterruptibly(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    sync.acquireShared(permits);
}

Obtenga varias licencias a la vez, sin interrupciones.

método tryAcquire (permisos int)

public boolean tryAcquire(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    return sync.nonfairTryAcquireShared(permits) >= 0;
}

Intente obtener varias licencias a la vez y solo inténtelo una vez.

tryAcquire (permisos int, tiempo de espera prolongado, unidad TimeUnit) 方法

public boolean tryAcquire(int permits, long timeout, TimeUnit unit)
    throws InterruptedException {
    if (permits < 0) throw new IllegalArgumentException();
    return sync.tryAcquireSharedNanos(permits, unit.toNanos(timeout));
}

Intente obtener varias licencias y espere el período de tiempo de espera. Si no se obtiene ninguna licencia durante este período, devolverá falso, de lo contrario devolverá verdadero.

método de liberación (permisos int)

public void release(int permits) {
    if (permits < 0) throw new IllegalArgumentException();
    sync.releaseShared(permits);
}

Si se emiten varios permisos a la vez, el valor del estado aumentará el número de permisos en consecuencia.

availablePermits () 方法

public int availablePermits() {
    return sync.getPermits();
}

Obtenga la cantidad de licencias disponibles.

método DrainPermits ()

public int drainPermits() {
    return sync.drainPermits();
}

La destrucción del número de licencias disponibles actualmente no tiene ningún efecto sobre las licencias que se han obtenido y todas las licencias restantes serán destruidas.

reducePermits (reducción int) 方法

protected void reducePermits(int reduction) {
    if (reduction < 0) throw new IllegalArgumentException();
    sync.reducePermits(reduction);
}

Reducir el número de permisos.

para resumir

(1) El semáforo, también llamado semáforo, se usa generalmente para controlar el acceso a los recursos compartidos al mismo tiempo, es decir, los escenarios limitantes actuales;

(2) La implementación interna de Semaphore se basa en el bloqueo compartido de AQS;

(3) Semaphore necesita especificar el número de permisos cuando se inicializa, y el número de permisos se almacena en el estado;

(4) Cuando se obtiene una licencia, el valor estatal se reduce en 1;

(5) Cuando se libera una licencia, el valor estatal aumenta en 1;

(6) N permisos se pueden reducir dinámicamente;

(7) ¿Se pueden agregar n licencias de forma dinámica?

huevos de Pascua

(1) ¿Cómo agregar dinámicamente n licencias?

Respuesta: Simplemente llame a liberación (permisos int). Sabemos que cuando se libera la licencia, el valor del estado aumentará en consecuencia. Cuando miramos hacia atrás en el código fuente de la licencia de liberación, encontramos que es algo diferente del bloqueo de liberación de ReentrantLock. Semaphore no verifica si el hilo actual ha obtenido una licencia al liberar la licencia. Por lo tanto, puede llamar al método de licencia de lanzamiento para agregar dinámicamente algunas licencias.

(2) ¿Cómo lograr la limitación de corriente?

Respuesta: Limitación de corriente, es decir, cuando el tráfico aumenta repentinamente, la capa superior debe poder limitar el impacto de un gran tráfico repentino en los servicios descendentes. En un sistema distribuido, la limitación de corriente generalmente se realiza en la capa de puerta de enlace. Por supuesto, también se puede utilizar en funciones individuales. Simplemente limite el flujo, como un escenario de picos. Si solo hay 10 productos que deben agregarse, el servicio en sí puede limitar solo 100 solicitudes al mismo tiempo y todas las demás solicitudes se invalidan, por lo que la presión del servicio no será demasiado grande.

El uso de Semaphore puede limitar directamente la corriente para esta función. La siguiente es la implementación del código:

public class SemaphoreTest {
    public static final Semaphore SEMAPHORE = new Semaphore(100);
    public static final AtomicInteger failCount = new AtomicInteger(0);
    public static final AtomicInteger successCount = new AtomicInteger(0);

    public static void main(String[] args) {
        for (int i = 0; i < 1000; i++) {
            new Thread(()->seckill()).start();
        }
    }

    public static boolean seckill() {
        if (!SEMAPHORE.tryAcquire()) {
            System.out.println("no permits, count="+failCount.incrementAndGet());
            return false;
        }

        try {
            // 处理业务逻辑
            Thread.sleep(2000);
            System.out.println("seckill success, count="+successCount.incrementAndGet());
        } catch (InterruptedException e) {
            // todo 处理异常
            e.printStackTrace();
        } finally {
            SEMAPHORE.release();
        }
        return true;
    }
}

Supongo que te gusta

Origin blog.csdn.net/x950913/article/details/106228905
Recomendado
Clasificación