Programación concurrente: herramienta de concurrencia JUC


Insertar descripción de la imagen aquí

Prefacio

JUC es una biblioteca de herramientas de programación concurrente de Java que proporciona algunas herramientas de concurrencia de uso común, como bloqueos, semáforos, contadores, bucles de eventos, grupos de subprocesos, colecciones concurrentes, etc. Estas herramientas pueden ayudar a los desarrolladores a simplificar la complejidad de la programación concurrente y mejorar la eficiencia y confiabilidad del programa.

cuenta atrás

Aplicación CountDownLatch

CountDownLatch en sí es como un contador que puede esperar a que se completen uno o más subprocesos antes de ejecutarse y está implementado en base a AQS.

public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
    
    
    CountDownLatch countDownLatch = new CountDownLatch(3);

    new Thread(() -> {
    
    
        System.out.println("111");
        countDownLatch.countDown();
    }).start();

    new Thread(() -> {
    
    
        System.out.println("222");
        countDownLatch.countDown();
    }).start();

    new Thread(() -> {
    
    
        try {
    
    
            Thread.sleep(1000);
        } catch (InterruptedException e) {
    
    
            e.printStackTrace();
        }
        System.out.println("333");
        countDownLatch.countDown();
    }).start();

    // 主线会阻塞在这个位置,直到CountDownLatch的state变为0
    countDownLatch.await();
    System.out.println("main");
}

Código fuente principal de CountDownLatch

// CountDownLatch 的有参构造
public CountDownLatch(int count) {
    
    
    // 健壮性校验
    if (count < 0) throw new IllegalArgumentException("count < 0");
    // 构建Sync给AQS的state赋值
    this.sync = new Sync(count);
}

El método countDown esencialmente llama a la operación de liberación de bloqueo compartido de AQS.

public final boolean releaseShared(int arg) {
    
    
    if (tryReleaseShared(arg)) {
    
    
        // 唤醒在AQS队列中排队的线程。
        doReleaseShared();
        return true;
    }
    return false;
}

// countDownLatch实现的业务
protected boolean tryReleaseShared(int releases) {
    
    
    for (;;) {
    
    
        int c = getState();
        if (c == 0)
            return false;
        // state - 1
        int nextc = c-1;
        // 用CAS赋值
        if (compareAndSetState(c, nextc))
            return nextc == 0;
    }
}
// 如果CountDownLatch中的state已经为0了,那么再次执行countDown跟没执行一样。
// 而且只要state变为0,await就不会阻塞线程。

Todas las funciones son proporcionadas por AQS, solo las clases que deben implementarse mediante tryReleaseShared deben escribirse por sí mismas.

El método de espera llama al método proporcionado por AQS para adquirir el bloqueo compartido y permitir la interrupción.

// await方法
public void await() throws InterruptedException {
    
    
    sync.acquireSharedInterruptibly(1);
}

// AQS获取共享锁并且允许中断的方法
public final void acquireSharedInterruptibly(int arg)
        throws InterruptedException {
    
    
    if (Thread.interrupted())
        throw new InterruptedException();
    // countDownLatch操作
    if (tryAcquireShared(arg) < 0)
        // 如果返回的是-1,代表state肯定大于0
        doAcquireSharedInterruptibly(arg);
}

// CountDownLatch实现的tryAcquireShared
protected int tryAcquireShared(int acquires) {
    
    
    // state为0,返回1,。否则返回-1
    return (getState() == 0) ? 1 : -1;
}

// 让当前线程进到AQS队列,排队去
private void doAcquireSharedInterruptibly(int arg) throws InterruptedException {
    
    
    // 将当前线程封装为Node,并且添加到AQS的队列中
    final Node node = addWaiter(Node.SHARED);
    boolean failed = true;
    try {
    
    
        for (;;) {
    
    
            final Node p = node.predecessor();
            if (p == head) {
    
    
                // 再次走上面的tryAcquireShared,如果返回的是的1,代表state为0
                int r = tryAcquireShared(arg);
                if (r >= 0) {
    
    
                    // 会将当前线程和后面所有排队的线程都唤醒。
                    setHeadAndPropagate(node, r);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
            }
            if (shouldParkAfterFailedAcquire(p, node) &&
                parkAndCheckInterrupt())
                throw new InterruptedException();
        }
    } finally {
    
    
        if (failed)
            cancelAcquire(node);
    }
}


Semáforo

Aplicación de semáforo

El semáforo se utiliza generalmente para el control de flujo. Por ejemplo, cuando hay un recurso público al que pueden acceder varios subprocesos, Semaphore se puede utilizar como semáforo para limitarlo. Siempre que un hilo obtiene un objeto de conexión, se agrega -1 al semáforo y se agrega +1 al semáforo cuando el hilo devuelve el recurso. Si el hilo encuentra que la cantidad de recursos dentro del Semaphore es 0 al tomar recursos, será bloqueado.

public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
    
    
    // 声明信号量
    Semaphore semaphore = new Semaphore(1);
    // 能否去拿资源
    semaphore.acquire();
    // 拿资源处理业务
    System.out.println("main");
    // 归还资源
    semaphore.release();
}

Código fuente central del semáforo

Semaphore tiene dos formas de competir por los recursos, justa e injusta.

// 
public Semaphore(int permits, boolean fair) {
    
    
    sync = fair ? new FairSync(permits) : new NonfairSync(permits);
}

// 设置资源个数,State其实就是信号量的资源个数
Sync(int permits) {
    
    
    setState(permits);
}

Cuando se llama a adquirir para adquirir recursos, también se basa en el método de adquisición de bloqueos compartidos proporcionado por AQS.

Liberar significa agregar estado + 1 y devolver recursos.

// 两个一起 阿巴阿巴
public void release() {
    
    
    sync.releaseShared(1);
}

public final boolean releaseShared(int arg) {
    
    
    if (tryReleaseShared(arg)) {
    
    
        // 唤醒在AQS中排队的Node,去竞争资源
        doReleaseShared();
        return true;
    }
    return false;
}

// 信号量实现的归还资源
protected final boolean tryReleaseShared(int releases) {
    
    
    for (;;) {
    
    
        // 拿state
        int current = getState();
        // state + 1
        int next = current + releases;
        // 资源最大值,再+1,变为负数
        if (next < current)
            throw new Error("Maximum permit count exceeded");
        // CAS 改一手
        if (compareAndSetState(current, next))
            return true;
    }
}

Después de que el bloqueo compartido libera el recurso, si el nodo principal es 0, no se puede confirmar que realmente no haya un nodo sucesor. Si el nodo principal es 0, debe cambiar el estado del nodo principal a -3. Cuando el subproceso que obtiene el último recurso de bloqueo verifica si hay un nodo sucesor y si es un bloqueo compartido, active el subproceso en cola.

Barrera cíclica

Aplicación de barrera cíclica

CyclicBarrier generalmente se denomina barrera y es muy similar a CountDownLatch. CountDownLatch solo se puede usar una vez durante la operación, es decir, después de que el estado se vuelve 0, no se puede usar nuevamente. CyclicBarrier se puede reutilizar y su contador se puede restablecer y procesar nuevamente. Y después de que ocurra un problema durante el proceso de conteo, puede restablecer el CyclicBarrier actual y comenzar la operación nuevamente.

public static void main(String[] args) throws InterruptedException, BrokenBarrierException {
    
    
    // 声明栅栏
    CyclicBarrier barrier = new CyclicBarrier(3,() -> {
    
    
        System.out.println("开始!");
    });

    new Thread(() -> {
    
    
        System.out.println("第一位选手到位");
        try {
    
    
            barrier.await();
            System.out.println("第一位往死里跑!");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }).start();

    new Thread(() -> {
    
    
        System.out.println("第二位选手到位");
        try {
    
    
            barrier.await();
            System.out.println("第二位也往死里跑!");
        } catch (Exception e) {
    
    
            e.printStackTrace();
        }
    }).start();

    System.out.println("裁判已经到位");
    barrier.await();
}

Código fuente principal de CyclicBarrier

CyclicBarrier no usa AQS directamente, pero usa ReentrantLock, que usa AQS indirectamente.

// CyclicBarrier的有参
public CyclicBarrier(int parties, Runnable barrierAction) {
    
    // 健壮性判断!
    if (parties <= 0) throw new IllegalArgumentException();
    // parties是final修饰的,需要在重置时,使用!
    this.parties = parties;
    // count是在执行await用来计数的。
    this.count = parties;
    // 当计数count为0时 ,先执行这个Runnnable!在唤醒被阻塞的线程
    this.barrierCommand = barrierAction;
}

Cuando el hilo ejecuta el método de espera, contará -1 y luego determinará si el recuento es 0. Si no es 0, debe agregarse a la cola de camarero de ConditionObject en AQS y estacionar el hilo actual. Si es 0, demuestra que los subprocesos están listos y es necesario ejecutar nextGeneration. Todos los nodos en la cola de camarero se transferirán primero a la cola AQS y ningún nodo sucesor se establecerá en 0. Luego restablezca los indicadores de recuento y corredor. Una vez ejecutado el desbloqueo, se despertará cada hilo.

private int dowait(boolean timed, long nanos) throws InterruptedException, BrokenBarrierException, TimeoutException {
    
    
    // 相当于synchronized中使用wait和notify
    final ReentrantLock lock = this.lock;
    lock.lock();
    try {
    
    
        // 里面就是boolean,默认false
        final Generation g = generation;

        // 判断之前栅栏加入线程时,是否有超时、中断等问题,如果有,设置boolean为true,其他线程再进来,直接凉凉
        if (g.broken)
            throw new BrokenBarrierException();

        if (Thread.interrupted()) {
    
    
            breakBarrier();
            throw new InterruptedException();
        }


        // 对计数器count--
        int index = --count;
        // 如果--完,是0,代表突破栅栏,干活!
        if (index == 0) {
    
      
            // 默认false
            boolean ranAction = false;
            try {
    
    
                // 如果你用的是2个参数的有参构造,说明你传入了任务,index == 0,先执行CyclicBarrier有参的任务
                final Runnable command = barrierCommand;
                if (command != null)
                    command.run();
                // 设置为true
                ranAction = true;
                nextGeneration();
                return 0;
            } finally {
    
    
                if (!ranAction)
                    breakBarrier();
            }
        }

        // --完之后,index不是0,代表还需要等待其他线程
        for (;;) {
    
    
            try {
    
    
                // 如果没设置超时时间。  await()
                if (!timed)
                    trip.await();
                // 设置了超时时间。  await(1,SECOND)
                else if (nanos > 0L)
                    nanos = trip.awaitNanos(nanos);
            } catch (InterruptedException ie) {
    
    
                if (g == generation && ! g.broken) {
    
    
                    breakBarrier();
                    throw ie;
                } else {
    
    
                    Thread.currentThread().interrupt();
                }
            }

            if (g.broken)
                throw new BrokenBarrierException();

            if (g != generation)
                return index;

            if (timed && nanos <= 0L) {
    
    
                breakBarrier();
                throw new TimeoutException();
            }
        }
    } finally {
    
    
        lock.unlock();
    }
}



// 挂起线程
public final void await() throws InterruptedException {
    
    
    // 允许中断
    if (Thread.interrupted())
        throw new InterruptedException();
    // 添加到队列(不是AQS队列,是AQS里的ConditionObject中的队列)
    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
    
    
        // 挂起当前线程
        LockSupport.park(this);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
}


// count到0,唤醒所有队列里的线程线程
private void nextGeneration() {
    
    
    // 这个方法就是将Waiter队列中的节点遍历都扔到AQS的队列中,真正唤醒的时机,是unlock方法
    trip.signalAll();
    // 重置计数器
    count = parties;
    // 重置异常判断
    generation = new Generation();
}

Resumir

Cosas a tener en cuenta al utilizar estas clases de herramientas:

  • El uso de Semaphore debería evitar interbloqueos y problemas de rendimiento causados ​​por una sincronización excesiva.
  • El código de CyclicBarrier después del punto de barrera debe garantizar que todos los subprocesos se puedan ejecutar correctamente; de ​​lo contrario, algunos subprocesos pueden esperar para siempre.
  • Se debe llamar al método countDown de CountDownLatch antes de que todos los subprocesos hayan completado la ejecución; de lo contrario, algunos subprocesos pueden seguir esperando.

Seleccionar la clase de herramienta adecuada de acuerdo con el escenario de aplicación específico, usarla correctamente y diseñar la estrategia de concurrencia de manera razonable puede mejorar la eficiencia y confiabilidad del programa.

Supongo que te gusta

Origin blog.csdn.net/qq_28314431/article/details/133136660
Recomendado
Clasificación