Programação simultânea - ferramenta de simultaneidade JUC


Insira a descrição da imagem aqui

Prefácio

JUC é uma biblioteca de ferramentas de programação simultânea Java que fornece algumas ferramentas de simultaneidade comumente usadas, como bloqueios, semáforos, contadores, loops de eventos, pools de threads, coleções simultâneas, etc. Essas ferramentas podem ajudar os desenvolvedores a simplificar a complexidade da programação simultânea e a melhorar a eficiência e a confiabilidade do programa.

Contagem regressiva

Aplicativo CountDownLatch

O próprio CountDownLatch é como um contador que pode esperar a conclusão de um ou mais threads antes de ser executado. Ele é implementado com base em 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-fonte principal do CountDownLatch

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

O método countDown chama essencialmente a operação de liberação de bloqueio compartilhado do 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 as funções são fornecidas pelo AQS, apenas as classes que precisam ser implementadas pelo tryReleaseShared precisam ser escritas por elas mesmas.

O método await chama o método fornecido pelo AQS para adquirir o bloqueio compartilhado e permitir a interrupção.

// 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

Aplicação de semáforo

O semáforo é geralmente usado para controle de fluxo. Por exemplo, quando há um recurso público que pode ser acessado por vários threads, o Semaphore pode ser usado como semáforo para limitá-lo. Sempre que um thread adquire um objeto de conexão, o semáforo é -1, e quando o thread retorna o recurso, o semáforo é +1. Se o thread descobrir que o número de recursos dentro do semáforo é 0 ao obter recursos, ele 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-fonte do núcleo do semáforo

O Semaphore tem duas formas de competir por recursos: justa e injusta.

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

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

Ao chamar a aquisição para adquirir recursos, também se baseia no método de aquisição de bloqueios compartilhados fornecido pelo AQS.

Liberar significa adicionar estado + 1 e retornar 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;
    }
}

Após o bloqueio compartilhado liberar o recurso, se o nó principal for 0, não será possível confirmar se realmente não há nó sucessor. Se o nó principal for 0, você precisará alterar o status do nó principal para -3.Quando o encadeamento que obtém o recurso de bloqueio mais recente verificar se há um nó sucessor e se é um bloqueio compartilhado, ative o encadeamento na fila.

Barreira Cíclica

Aplicação CyclicBarrier

CyclicBarrier é geralmente chamado de barreira e é muito semelhante ao CountDownLatch. CountDownLatch só pode ser utilizado uma vez durante a operação, ou seja, após o estado chegar a 0, ele não poderá ser utilizado novamente. CyclicBarrier pode ser reutilizado e seu contador pode ser zerado e processado novamente. E após ocorrer um problema durante o processo de contagem, você pode redefinir o CyclicBarrier atual e iniciar a operação novamente!

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-fonte principal do CyclicBarrier

CyclicBarrier não usa AQS diretamente, mas usa ReentrantLock, que usa AQS indiretamente.

// 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;
}

Quando o thread executa o método await, ele contará 1 e, em seguida, determinará se a contagem é 0. Se não for 0, ele precisa ser adicionado à fila Waiter do ConditionObject no AQS e estacionar o thread atual. Se for 0, prova que os threads estão prontos e nextGeneration precisa ser executado.Todos os nós na fila Waiter serão transferidos primeiro para a fila AQS e nenhum nó sucessor será definido como 0. Em seguida, redefina a contagem e os sinalizadores do corretor. Após a execução do desbloqueio, cada thread será despertado.

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

Coisas a serem observadas ao usar essas classes de ferramentas:

  • O uso do Semaphore deve evitar deadlocks e problemas de desempenho causados ​​por sincronização excessiva.
  • O código CyclicBarrier após o ponto de barreira deve garantir que todos os threads possam ser executados corretamente, caso contrário, alguns threads poderão esperar para sempre.
  • O método countDown de CountDownLatch deve ser chamado antes que todos os threads tenham concluído a execução, caso contrário, alguns threads podem continuar esperando.

Selecionar a classe de ferramenta apropriada de acordo com o cenário de aplicação específico, utilizá-la corretamente e projetar a estratégia de simultaneidade de maneira razoável pode melhorar a eficiência e a confiabilidade do programa.

Acho que você gosta

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