Fuente consulta ReentrantLock

ReentrantLockEs una cerradura de reentrada, de reentrada es decir el mismo hilo puede adquirir la misma cerradura varias veces, no será un campo interno correspondiente registra el número de re-entrada, también es un mutex, lo que significa que sólo un hilo se puede obtener de bloqueo de reentrada.

1. Constructor

    public ReentrantLock() {
        sync = new NonfairSync();
    }

    public ReentrantLock(boolean fair) {
        sync = fair ? new FairSync() : new NonfairSync();
    }

ReentrantLockProporcionar dos constructores, constructor utiliza simplemente para inicializar el synccampo, se puede ver, por defecto ReentrantLockel uso no equitativa de la cerradura, por supuesto, también es posible utilizar el parámetro booleano con un constructor para elegir bloqueo equidad. justo cerradura de bloqueo y la aplicación injusta se basa en dos clases internas: FairSyncy NonfairSync, a continuación, aprender lo que estas dos clases:

    //非公平锁
    static final class NonfairSync extends Sync {
        private static final long serialVersionUID = 7316153563782823691L;

        /**
         * Performs lock.  Try immediate barge, backing up to normal
         * acquire on failure.
         */
        final void lock() {
            if (compareAndSetState(0, 1))
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }

        protected final boolean tryAcquire(int acquires) {
            return nonfairTryAcquire(acquires);
        }
    }

    static final class FairSync extends Sync {
        private static final long serialVersionUID = -3000897897090466540L;

        final void lock() {
            acquire(1);
        }

        /**
         * Fair version of tryAcquire.  Don't grant access unless
         * recursive call or no waiters or is first.
         */
        protected final boolean tryAcquire(int acquires) {
            final Thread current = Thread.currentThread();
            int c = getState();
            if (c == 0) {
                if (!hasQueuedPredecessors() &&
                    compareAndSetState(0, acquires)) {
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            else if (current == getExclusiveOwnerThread()) {
                int nextc = c + acquires;
                if (nextc < 0)
                    throw new Error("Maximum lock count exceeded");
                setState(nextc);
                return true;
            }
            return false;
        }
    }

El código de dos clase interna es muy corto, y heredar otra clase interna Sync. Aquí no tiene prisa para introducir Syncla clase porque la clase en sí no es complicado, por cierto, para explicar la necesidad de que el método posterior que se utiliza en la actualidad sólo necesita saber esto hereda de la clase AbstractQueuedSynchronizer(AQS)pueden ser.

2. El método común

  • lock()
    public void lock() {
        sync.lock();
    }

lockEl método proporciona una función de bloqueo, justo y operación de bloqueo bloqueo bloqueo injusto no es lo mismo, echar un vistazo a los detalles del bloqueo injusto, seguido explicar cerraduras justas.

  • la lógica de bloqueo de bloqueo injusto
    final void lock() {
        //使用CAS操作,尝试将state字段从0修改为1,如果成功修改该字段,则表示获取了互斥锁
        //如果获取互斥锁失败,转入acquier()方法逻辑
        if (compareAndSetState(0, 1))
            setExclusiveOwnerThread(Thread.currentThread());
        else
            acquire(1);
    }
    
    //设置获得了互斥锁的线程
    protected final void setExclusiveOwnerThread(Thread thread) {
        exclusiveOwnerThread = thread;
    }

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

Para no bloqueo términos justos, usando lock()el método uno arriba en el intento de adquirir el mutex para el éxito va a exclusiveOwnerThreadapuntar a sí mismos, eso significa que tendrá su propia cerradura, de lo contrario ejecutar acquire()método de la lógica, el siguiente de acquire()métodos lógicos analizados uno por uno.
El primero es el tryAcquire()método, las anulaciones de bloqueo injustos este método se llama internamente y Syncclase nonfairTryAcquire().

    //从上面的逻辑来看,这里的acquires=1
    protected final boolean tryAcquire(int acquires) {
        return nonfairTryAcquire(acquires);
    }

    final boolean nonfairTryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //c==0,说明当前处于未加锁状态,锁没有被其他线程获取
        if (c == 0) {
            //在锁没有被其他线程占有的情况下,非公平锁再次尝试获取锁,获取成功则将exclusiveOwnerThread指向自己
            if (compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //执行到这里说明锁已经被占有,如果是被自己占有,将state字段加1,记录重入次数
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            //当nextc达到int类型最大值时会溢出,因此可重入次数的最大值就是int类型的最大值
            if (nextc < 0) // overflow
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        //执行到这里说明:1)锁未被占有的情况下,抢锁失败,说明当前有其他线程抢到了锁;2)锁已经被其他线程占有
        //即只要当前线程没有获取到锁,就返回false
        return false;
    }
    
    //获取state字段,该字段定义在AQS中
    protected final int getState() {
        return state;
    }
    //设置state字段
    protected final void setState(int newState) {
        state = newState;
    }

No es la hebra actual tryAcquire()adquirir el método de bloqueo, primero se realiza addWaiter(Node.EXCLUSIVE)un método, en el que el parámetro Node.EXCLUSIVEes una constante, que se define static final Node EXCLUSIVE = null, es a los atributos de la etiqueta son de bloqueo mutex. addWaiter()El papel del método actual es para enhebrar empaquetado en un Nodenodo, en la cola de la cola, el método introducido CountDownLatchen detalle explicó antes de la clase, los amigos que estén interesados pueden referirse a ConcurrentHashMap explorar la fuente (JDK 1.8) , esto no va a repetir.
Después de la adición de la rosca actual que esperar en la cola, entonces la ejecución acquireQueued()método, el siguiente código:

    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            //自旋
            for (;;) {
                //获取当前节点的前一个节点
                final Node p = node.predecessor();
                //如果前一个节点是头节点,说明当前节点排在队首,非公平锁会则再次通过tryAcquire方法获取锁
                if (p == head && tryAcquire(arg)) {
                    //将自己设置为头节点
                    setHead(node);
                    //前一个头结点没用了,会被垃圾回收掉
                    p.next = null; // help GC
                    failed = false;
                    //正常结束,返回false,注意该字段可能会在下面的条件语句中被改变
                    return interrupted;
                }
                //如果前一个节点不是头节点,或者当前线程获取锁失败,会执行到这里
                //shouldParkAfterFailedAcquire()方法只有在p的状态是SIGNAL时才返回false,此时parkAndCheckInterrupt()方法才有机会执行
                //注意外层的自旋,for循环体会一直重试,因此只要执行到这里,总会有机会将p设置成SIGNAL状态从而将当前线程挂起
                //另外,如果parkAndCheckInterrupt()返回true,说明当前线程设置了中断状态,会将interrupted设置为true,代码接着自旋,会在上一个条件语句中返回true
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            //如果在自旋中线程被中断或者发送异常,failed字段的值将会为true,这里会处理这种情况,放弃让当前线程获取锁,并抛出中断异常
            if (failed)
                cancelAcquire(node);
        }
    }
    //方法逻辑是:只有在前置节点的状态是SIGNAL时才返回true,其他情况都返回false
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            return true;
        //删除当前节点之前连续状态是CANCELLED的节点
        if (ws > 0) {
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }
    //线程在这里阻塞,并在被唤醒后检查中断状态
    private final boolean parkAndCheckInterrupt() {
        LockSupport.park(this);
        return Thread.interrupted();
    }
    
    //
    private void cancelAcquire(Node node) {
        // Ignore if node doesn't exist
        if (node == null)
            return;

        node.thread = null;

        // Skip cancelled predecessors
        Node pred = node.prev;
        while (pred.waitStatus > 0)
            node.prev = pred = pred.prev;

        Node predNext = pred.next;

        node.waitStatus = Node.CANCELLED;

        // If we are the tail, remove ourselves.
        if (node == tail && compareAndSetTail(node, pred)) {
            compareAndSetNext(pred, predNext, null);
        } else {
            // If successor needs signal, try to set pred's next-link
            // so it will get one. Otherwise wake it up to propagate.
            int ws;
            if (pred != head &&
                ((ws = pred.waitStatus) == Node.SIGNAL ||
                 (ws <= 0 && compareAndSetWaitStatus(pred, ws, Node.SIGNAL))) &&
                pred.thread != null) {
                Node next = node.next;
                if (next != null && next.waitStatus <= 0)
                    compareAndSetNext(pred, predNext, next);
            } else {
                //唤醒后一个节点
                unparkSuccessor(node);
            }

            node.next = node; // help GC
        }
    }

Tenga en cuenta que acquireQueued()cualquiera excepción de interrupción será lanzada, o el retorno al final normal false, sólo para ser despertados después de establecer el estado de alarma hilo será devuelto true. La comparación se puede encontrar, acquireQueued()la lógica y los métodos CountDownLatchde doAcquireSharedInterruptibly()muy similar, en muchos aspectos CountDownLatchhabló a través de este blog, este artículo no voy a entrar en detalles sobre estos métodos.
Introducido sobre acquire()método, volver al método lógico:

    public final void acquire(int arg) {
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }

Si tryAcquire()no existe un método para adquirir el bloqueo, entonces el hilo actual se añade a la cola de espera de la cola, para ver si el nodo nodo anterior es el primer nodo es entonces el hilo actual puede continuar hacia abajo, de lo contrario va a tapar delta. Cuando acquireQueuedregresan verdad, explicar conjuntos de hilos hasta el estado de interrupción, se llama selfInterrupt()interrupción del hilo, de lo contrario selfInterrupt()el método ninguna posibilidad de ejecutar.
Aquí injusta para bloquear el proceso de bloqueo se ha introducido otra vez, debido a la lógica de código relativamente largo, Look proceso en la fuente será hacia delante y atrás varias clases, la idea es muy fácil de romper, la lectura del código de tiempo para prestar atención. (Es necesario para compensar un diagrama de flujo).

  • Feria de bloqueo bloqueo lógica
    Echemos un vistazo a las cerraduras de bloqueo justas lógica:
    final void lock() {
        acquire(1);
    }

En comparación con el bloqueo no justo, el bloqueo no es justo para tomar un candado a la lógica, que es la manifestación de la justicia. Otros tipos de cerraduras de acquire()la misma manera que el marco, pero con diferentes detalles de implementación, miren cerraduras justas tryAcquire()métodos:

    protected final boolean tryAcquire(int acquires) {
        final Thread current = Thread.currentThread();
        int c = getState();
        //c=0表示当前没有其他线程持有锁
        if (c == 0) {
            //下面的代码与非公平锁相比,多了hasQueuedPredecessors()方法的处理逻辑,公平锁只有在前面没有其他线程排队的情况下才会尝试获取锁
            if (!hasQueuedPredecessors() &&
                compareAndSetState(0, acquires)) {
                setExclusiveOwnerThread(current);
                return true;
            }
        }
        //如果当前线程已经占有公平锁,则记录重入次数
        else if (current == getExclusiveOwnerThread()) {
            int nextc = c + acquires;
            if (nextc < 0)
                throw new Error("Maximum lock count exceeded");
            setState(nextc);
            return true;
        }
        //只要当前线程没有获取到锁,就返回false
        return false;
    }

    public final boolean hasQueuedPredecessors() {
        // The correctness of this depends on head being initialized
        // before tail and on head.next being accurate if the current
        // thread is first in queue.
        Node t = tail; // Read fields in reverse initialization order
        Node h = head;
        Node s;
        //h != t表示等待队列中有其他节点
        //h.next == null可能会有点费解,按理说h!=t之后,h后面肯定会有节点才对,这种情况其实已经见过,在上文介绍acquireQueued()方法时说过,
        //被唤醒的第一个等待节点会将自己设置为头结点,如果这个节点是队列中的唯一节点的话,它的下一个节点就是null
        //至于s.thread != Thread.currentThread()这个条件暂时可以忽略,因为公平锁执行到hasQueuedPredecessors方法时根本还没有入队,
        //这也意味着,只要队列中有其他节点在等候,公平锁就要求其他线程排队等待
        return h != t &&
            ((s = h.next) == null || s.thread != Thread.currentThread());
    }
  • lockInterruptibly
    Como su nombre indica, lockInterruptiblyse puede responder a interrumpir, a la vista de la aplicación del método:
    public void lockInterruptibly() throws InterruptedException {
        sync.acquireInterruptibly(1);
    }

    public final void acquireInterruptibly(int arg)
            throws InterruptedException {
        if (Thread.interrupted())
            throw new InterruptedException();
        //先尝试获取锁,获取失败才执行后面的逻辑
        if (!tryAcquire(arg))
            doAcquireInterruptibly(arg);
    }

    private void doAcquireInterruptibly(int arg)
        throws InterruptedException {
        final Node node = addWaiter(Node.EXCLUSIVE);
        boolean failed = true;
        try {
            for (;;) {
                final Node p = node.predecessor();
                if (p == head && tryAcquire(arg)) {
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return;
                }
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    throw new InterruptedException();
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }

lockInterruptibly()El método es casi acquire()exactamente el mismo método, la única diferencia es el acquire()método, parkAndCheckInterruptporque el hilo interrumpido conjunto de estado para volver truecuando, establece simplemente un poco interruptedvalor del campo, y lockInterruptibly()es un tiro directo.

  • unlockEl método de
    introducción a la lógica de bloqueo, vamos a ver la lógica de desbloqueo:
    public void unlock() {
        sync.release(1);
    }

    public final boolean release(int arg) {
        //如果成功释放了锁,则执行下面的代码块
        if (tryRelease(arg)) {
            Node h = head;
            //如果头节点不为null,请求节点状态不是初始状态,就释放头结点后第一个有效节点
            //问题:这里为什么需要判断头结点的状态呢???
            if (h != null && h.waitStatus != 0)
                unparkSuccessor(h);
            return true;
        }
        return false;
    }
    
    //
    protected final boolean tryRelease(int releases) {
        int c = getState() - releases;
        //线程没有持有锁的情况下,不允许释放锁,否则会抛异常
        if (Thread.currentThread() != getExclusiveOwnerThread())
            throw new IllegalMonitorStateException();
        boolean free = false;
        //可重入性的判断,如果释放了一次锁,使得c=0,就指针释放锁,做法是将记录锁的字段exclusiveOwnerThread重新指向null
        //注意,只有最后一次释放可重入锁,才会返回true
        if (c == 0) {
            free = true;
            setExclusiveOwnerThread(null);
        }
        setState(c);
        return free;
    }

    //唤醒node节点的下一个有效节点,这里的有效指的是状态不是CANCELLED状态的节点
    private void unparkSuccessor(Node node) {

        int ws = node.waitStatus;
        if (ws < 0)
            compareAndSetWaitStatus(node, ws, 0);

        Node s = node.next;
        if (s == null || s.waitStatus > 0) {
            s = null;
            for (Node t = tail; t != null && t != node; t = t.prev)
                if (t.waitStatus <= 0)
                    s = t;
        }
        if (s != null)
            LockSupport.unpark(s.thread);
    }
  • newCondition()
    ReentrantLockPuede enlazar múltiples condición de espera, esta función se newCondition()métodos implementados, cada llamada newCondition()cuando el método generará un nuevo ConditionObjectobjeto, que es AQSuna clase interna, el código es muy largo, no se discute en detalle aquí. Basta con mirar a la fuente del método:
    public Condition newCondition() {
        return sync.newCondition();
    }
    final ConditionObject newCondition() {
        return new ConditionObject();
    }

3. Resumen

En un entorno multiproceso, ReentrantLockel bloqueo injusto que las cerraduras justas presentan un mayor rendimiento debido a la cerradura para evitar bloqueos de rosca injustos cambio de contexto sobrecarga producida, pero el seguro de rosca justo para evitar el hambre, y por lo tanto cada uno tiene su propia escenarios de uso. Desde el punto de vista del origen, J.U.Clos muchos tipos de paquetes dependen de AQSla clase, por lo que es necesario conocer AQS. Mencionó ReentrantLock, siempre encuentra y se synchronizedcomparó. synchronizedTambién reentrada, y en JDK 1.6el futuro, synchronizedel rendimiento se ha mejorado en gran medida, y por lo tanto optar por utilizar ReentrantLocken general, considerar el uso de sus tres ventajas: interrumpibles, se puede lograr un cierre de feria, se puede unir a una serie de condiciones, estas ventajas es synchronizedno disponible.

Supongo que te gusta

Origin www.cnblogs.com/NaLanZiYi-LinEr/p/12508195.html
Recomendado
Clasificación