Avanzada --JUC multi-hilo fuente de programación concurrente para averiguar el riesgo de CyclicBarrier

navegación de páginas blog personal (haga clic a la derecha de enlace para abrir un blog personal): Daniel lo lleve en la pila de tecnología 

1, el punto de partida del aprendizaje

Baidu traducción probablemente significa:

Una ayuda de sincronización que permite a un conjunto de hilos cada uno espera la llegada de un punto de barrera común. programa del partido hilo útil CyclicBarrier implica un tamaño fijo, estos hilos partido debe esperar a veces uno para el otro. Esta barrera se denomina barrera cíclico, ya que puede ser reutilizada después de esperar hilo es liberado.

CyclicBarrier soporta un comando Ejecutable opcional, que es después de que el último hilo llega prescripción involucrados, pero antes de la liberación de cualquier tema, ejecute cada barrera punto de una vez. Esta barrera ayuda para actualizar el estado de funcionamiento compartida antes de cualquier participante para continuar.

Presentación dinámica:

En el análisis anterior Terminamos  código fuente CountDownLatch , se puede entender como un contador descendente , se basa en el modo AQS compartida de uso, y CyclicBarrier comparado con CountDownLatch, es mucho más simple, que es similar a la adición de contador , utilizar el código fuente  ReentrantLock y condición de combinación de usar.

2, caja presentaciones CyclicBarrier 

//加法计数器
public class CyclicBarrierDemo {
    public static void main(String[] args) {
        /**
         * 集齐5名队员,开始游戏
         */
        // 开始战斗的线程
        CyclicBarrier cyclicBarrier = new CyclicBarrier(5,()->{
            System.out.println("欢迎来到王者荣耀,敌军还有五秒到达战场!全军出击!");
        });
        for (int i = 1; i <=5 ; i++) {
            final int temp = i;
            // lambda能操作到 i 吗
            new Thread(()->{
                System.out.println(Thread.currentThread().getName()+"第"+temp+"个进入游戏!");
                try {
                    cyclicBarrier.await(); // 等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } catch (BrokenBarrierException e) {
                    e.printStackTrace();
                }
            }).start();
        }
    }
}

3, constructor de partida

//构造器1
/** 创建一个新的CyclicBarrier,它将在给定数量的参与方(线程)等待时触发,并在触发屏障时执行给定的屏障操作,由最后一个进入屏障的线程执行 */   
public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

//构造器2
/** 创建一个新的CyclicBarrier,当给定数量的参与方(线程)在等待它时,它将跳闸,并且在屏障跳闸时不执行预定义的操作 */
public CyclicBarrier(int parties) {
        this(parties, null);
    }

1 en el que el constructor como el constructor principal, donde puede especificar las  partes  el número de participantes del Consejo del juego (el número de hilos a bloquear) y  barrierAction  tarea del Consejo al final del juego para ser ejecutados.

3, a partir de variables miembro

   /** 同步操作锁 */
    private final ReentrantLock lock = new ReentrantLock();
    /** 线程拦截器 Condition维护了一个阻塞队列*/
    private final Condition trip = lock.newCondition();
    /** 每次拦截的线程数 */
    private final int parties;
    /* 换代前执行的任务 */
    private final Runnable barrierCommand;
    /** 表示栅栏的当前代 类似代表本局游戏*/
    private Generation generation = new Generation();
    /** 计数器 */
    private int count;
    /** 静态内部类Generation  */
    private static class Generation {
        boolean broken = false;
    }

3, comenzar con los métodos básicos

3.1, el método de análisis de código fuente [await]

El siguiente análisis de estos dos métodos, respectivamente [ de espera no programada ] y [ espera temporizada ]!

//非定时等待
public int await() throws InterruptedException, BrokenBarrierException {
        try {
            return dowait(false, 0L);
        } catch (TimeoutException toe) {
            throw new Error(toe); // cannot happen
        }
    }
//定时等待
public int await(long timeout, TimeUnit unit)throws InterruptedException,
              BrokenBarrierException,
              TimeoutException {
       return dowait(true, unit.toNanos(timeout));
   }

Se puede ver, los dos últimos métodos se han ido método [dowait], pero con diferentes parámetros. Aquí nos centramos en este enfoque en el final para ver lo que se había hecho cosa.

//核心等待方法
 private int dowait(boolean timed, long nanos)
        throws InterruptedException, BrokenBarrierException,
               TimeoutException {
        final ReentrantLock lock = this.lock;
        lock.lock();//加锁操作
        try {
            final Generation g = generation;
            //检查当前栅栏是否被打翻
            if (g.broken)
                throw new BrokenBarrierException();
            //检查当前线程是否被中断
            if (Thread.interrupted()) {
                breakBarrier();
                throw new InterruptedException();
            }
            //每次都将计数器的值-1
            int index = --count;
            //计数器的值减为0,则需要唤醒所有线程并转换到下一代
            if (index == 0) {  // tripped
                boolean ranAction = false;
                try {
                    //唤醒所有线程前先执行指定的任务
                    final Runnable command = barrierCommand;
                    if (command != null)
                        command.run();
                    ranAction = true;
                    //唤醒所有线程并转换到下一代
                    nextGeneration();
                    return 0;
                } finally {
                    //确保在任务未成功执行时能将所有线程唤醒
                    if (!ranAction)
                        breakBarrier();
                }
            }
            //如果计数器不为0 则执行此循环
            // loop until tripped, broken, interrupted, or timed out
            for (;;) {
                try {
                    //根据传入的参数来觉得是定时等待还是非定时等待
                    if (!timed)
                        //如果没有时间限制,则直接等待,直到被唤醒
                        trip.await();
                    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();//最终解锁
        }
    }

Análisis en dos pasos, en primer lugar el valor de contador descendente es cero, y el contador no es 0, el primer primer caso:

El segundo caso, el contador no es 0, entonces entrar en el giro para la (;;):

Multi-threaded acceso concurrente, la forma de bloquear el hilo actual?

Nos fijamos en el código fuente, aquí hay un vistazo a ningún límite de tiempo [ trip.await método]:

La espera de todo el proceso:

1, se añade a la cola actual bloqueo de rosca Condición. En particular, para distinguir entre las principales AQS cola, donde cola FIFO de entrada es Condición de espera

2, liberar el bloqueo. Aquí se puede ver [fullyRelease] liberará el bloqueo, de lo contrario [acquireQueued (nodo, savedState)] no puede obtener un bloqueo en punto muerto se produce otro hilo.

3, vuelta (el tiempo) suspendido hasta CACELLED despertado o fuera o similares.

4, obtener un bloqueo método [acquireQueued], y liberarse de la condición de la cola FIFO, la superficie que ya no necesita encerrarse (ya he bloqueado)

3.2 Condición de cola, y la cola de espera de suplementos AQS

AQS cola de espera y la cola de Estado es de dos colas separadas, [aguardan] está supeditado a la obtención del hilo actual mantiene el bloqueo de recursos de bloqueo, y crear se añade un nuevo nodo a la cola Condición Condición de la cola, bloqueando el flujo actual. [Señal] es el actual nodo principal se mueve a AQS Condición cola cola de nodo, se dejan esperar para adquirir un bloqueo de nuevo. El siguiente dibujo muestra la diferencia:

nodo de ejecución Condition.await 1 () -> (1) se moverá la cabeza -> (2) y un nodo de liberación de bloqueo cola de espera se retira de la AQS -> (3) se añade un nodo a la cola de espera Condición -> (4) un nodo se actualiza lastWrite

Nodo 2 realiza señal () operación -> (1) Después de cambio firstWrite -> (2) el nodo 4 se retira Condición Queue -> (3) El nodo se añade 4 a la AQS cola de espera a -> (4) de actualización de AQS esperando la cola cola

3.3 Resumen:

Una estructura de datos, Estado:

Sabemos que puede ser un Await Condición (), tendrá una estructura FIFO en muchos lugares estas condiciones juntas, y luego despertar una o más según sea necesario (generalmente todos). Así internamente Condición necesita una cola FIFO.
la firstWaiter Nodo transitoria privada;
privada lastWaiter el Nodo transitoria;

Lo anterior describe dos nodos es una cola FIFO. previamente hemos mencionado estructura de datos combinada nodo (nodo). Hemos encontrado Node.nextWaiter viene muy bien! nextWaiter es una serie de Condition.await unidos entre sí para formar una cola FIFO.

En segundo lugar, cuando el hilo y la liberación de bloqueo

El bloqueo: Método Await (), las emisiones de rosca el bloqueo después de recursos, si el nodo no está AQS cola de espera, bloqueando el flujo actual adquiere el bloqueo si la cola de espera, a la espera de probar el giro
de prensa: La señal (), el nodo de la condición cola de AQS a la cola, el proceso pasa al procedimiento normal de adquisición de la cerradura.

3,4, análisis de código fuente signalAll [signalAll]

[ SignalAll método], despierta todos los hilos en la condición de la cola de bloqueo

private void breakBarrier() {
        generation.broken = true;
        count = parties;
        trip.signalAll();//唤醒Condition中等待的线程
    }

public final void signalAll() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignalAll(first);
     }
/** 这个方法相当于把Condition队列中的所有Node全部取出插入到等待队列中去 */
private void doSignalAll(Node first) {
            lastWaiter = firstWaiter = null;
            do {
                Node next = first.nextWaiter;
                first.nextWaiter = null;
                transferForSignal(first);
                first = next;
            } while (first != null);
       }
/** 将节点从条件队列传输到同步队列AQS的等待队列中 */
final boolean transferForSignal(Node node) {
        //核心添加节点到AQS队列方法
        Node p = enq(node);
        int ws = p.waitStatus;
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
            LockSupport.unpark(node.thread);
        return true;
    }
/** 使用CAS+自旋方式插入节点到等待队列,如果队列为空,则初始化队列 */
private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }

3.5, el método de análisis de código fuente [RESET]

Por último, nos fijamos en cómo restablecer una valla:

La barrera se restablece al estado inicial. Si cualquiera de las partes está esperando actualmente en la pared, que se llevará a BrokenBarrierException retorno. Tenga en cuenta que restablecer la interrupción se produce por otras razones pueden ser complejos, hilo tiene que volver a sincronizar de otro modo, y elegir un camino para realizar un reinicio. La mejor es la creación de una nueva barrera para su uso futuro

    public void reset() {
        final ReentrantLock lock = this.lock;
        lock.lock();
        try {
            breakBarrier();   // break the current generation
            nextGeneration(); // start a new generation
        } finally {
            lock.unlock();
        }
    }

Prueba de restablecer el código:

En primer lugar, romper el cerco, es decir, todas las discusiones de espera (5 hilos de espera) se despierta, [Await] por [método lanzará BrokenBarrierException ] retorno excepción. A continuación, abra una nueva generación, la cuenta se pone a cero y la generación, el equivalente a toda la vuelta 0.

4 diferencia, CyclicBarrier con el CountDownLatch

El mismo punto:

1, se puede lograr un conjunto de hilos que esperar antes de llegar a una cierta condición

2, que tiene un contador interno cuando el contador se reduce continuamente a 0 cuando todas hilo bloqueado ser despertada!

diferencias:

1, CyclicBarrier contador es controlado por su propia cuenta, y el contador CountDownLatch es controlado por el uso de

2, en las discusiones CyclicBarrier Se invoca esperan no sólo a poner su obstrucción, sino también el contador en 1, y en CountDownLatch subproceso llama método esperan ser simplemente bloquear a sí mismos, sin disminuir el valor del contador.

3 Además, CountDownLatch solamente una intercepción, la interceptación y el ciclo de CyclicBarrier se puede lograr. En función general CyclicBarrier CountDownLatch se puede lograr, pero no viceversa.

5. Resumen:

Cuando se llama método de [cyclicBarrier.await], finalmente implementado] [dowait método utilizado para ReentrantLock bloqueo, cada valor de cuenta del contador hablando de -1, -1 cuando el valor del contador es 0, primero realizará una tarea específica, condición de las llamadas [trip.signalAll ()] despertar todos los hilos y en la próxima generación

-1 si el valor actual del contador no es 0 al entrar en el giro, la ejecución Await del método Condición [()], añadir el hilo actual que esperar a la condición en la cola de Estado, realiza la llamada [] [fullyRelease el valor de cómputo tryRelease] - 1 y, a continuación, determinar si el valor de conteo es 0, 0, se ejecuta las tareas especificadas, llame a la condición de [trip.signalAll ()] despertar todos los hilos y en la próxima generación, y luego determinar si el AQS cola de espera, si no, entonces aparcar el hilo actual en la cola de espera AQS, o giro en la cola esperando a ser despertado hasta que la condición es signalAll en la cola de espera para adquirir los AQS bloqueo

Lectura recomendada:

aprendizaje adjunta Java / C / C ++ / máquina / Algoritmos y Estructuras de Datos / front-end / Android / Python / programador de lectura / libros individuales libros Daquan:

(Haga clic en la derecha para abrir allí en el blog personal en seco): seca Técnica de floración
===== >> ① [Java Daniel lo lleve en el camino a avanzado] << ====
===== >> ② [+ acm algoritmo de estructura de datos Daniel lo lleve en el camino a avanzado] << ===
===== >> ③ [base de datos de Daniel lo lleve en el camino a avanzado] << == ===
===== >> ④ [Daniel cliente web para llevarlo en el camino a avanzado] << ====
===== >> ⑤ [pitón máquina de aprendizaje y Daniel le llevará a la entrada camino avanzada] << ====
===== >> ⑥ [arquitecto Daniel lo lleve en el camino a avanzado] << =====
===== >> ⑦ [C ++ Daniel avanzó para llevarlo en el camino] << ====
===== >> ⑧ [ios Daniel lo lleve en el camino a avanzado] << ====
=====> > ⑨ [seguridad web Daniel lo lleve en el camino a avanzado] << =====
===== >> ⑩ [sistema operativo Linux y Daniel se toman en el camino a avanzado] << = ====

No hay frutas consumidas, espero que jóvenes amigos, amigos desea aprender técnicas, superando todos los obstáculos en el camino de la carretera determinada para atar en la tecnología, entender el libro, y luego golpear en el código, entender el principio, e ir a la práctica, se se le dan vida, su trabajo, su futuro un sueño.

 

Publicados 141 artículos originales · ganado elogios 17 · vistas 8160

Supongo que te gusta

Origin blog.csdn.net/JKX_geek/article/details/102925109
Recomendado
Clasificación