Bloqueio explícito do JAVA

Interface de bloqueio

//获取不到就阻塞,不响应中断
void lock();
//获取不到就阻塞,响应中断
void lockInterruptibly() throws InterruptedException;
//获取到锁立即返回true,否则返回false
boolean tryLock();
//获取不到锁就阻塞,直到获取到锁,返回true;或者时间到,返回false;或者被中断,抛出异常
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
//解锁
void unlock();
//返回一个Condition实例,该实例与lock实例绑定
Condition newCondition();

Interface de condição

condição é condição

//执行此方法后,线程将释放与之相关的锁并阻塞,直到其他线程执行同一个condition的signal或者signall方法
//或者被其他线程中断
//退出此方法,线程将重新获取相关锁
void await() throws InterruptedException;
//剩下这些await方法应该很好猜了,那么我就只标注返回值
//等到了时间终止才返回,则返回false,否则true
boolean await(long time, TimeUnit unit) throws InterruptedException;
//返回剩余纳秒数(估计值)
long awaitNanos(long nanosTimeout) throws InterruptedException;
void awaitUninterruptibly();
boolean awaitUntil(Date deadline) throws InterruptedException;
//唤醒一个等待该condtion的类
void signal();
//唤醒所有等待该condtion的类
void signalAll();

ReentrantLock

Os bloqueios reentrantes implementam a interface Lock e a interface serializável.As
funções relacionadas são concluídas através da classe interna Sync. Esta classe é uma classe abstrata que herda a classe abstrata AbstractQueuedSynchronizer.
Essa classe possui duas subclasses, que também são classes internas do ReentrantLock, que são FairSync , NonfairSync, representa bloqueios justos e bloqueios injustos.Os bloqueios não justos padrão
também são complicados.Vamos dar uma olhada no que acontece quando o método de bloqueio é executado por padrão.

Processo de execução do método de bloqueio

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

sync é uma referência à classe Sync, que por padrão aponta para uma instância do NonfairSync

//ReentrantLock的无参构造器
public ReentrantLock() {
    sync = new NonfairSync();
}

Obviamente, você pode usar um construtor parametrizado para especificar quando é justo

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

Veja como o método de bloqueio do NonfairSync é implementado

final void lock() {
    //使用CAS改变线程状态,如果成功,修改锁的拥有者
    if (compareAndSetState(0, 1))
        setExclusiveOwnerThread(Thread.currentThread());
    else
    //否则,阻塞式获取
        acquire(1);
}

Concentre-se no método de aquisição

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

Da esquerda para a direita, observe primeiro o método tryAcquire.Note
que EXCLUSIVE significa exclusivo, o valor real é nulo
após uma série de chamadas e, finalmente, esse método chama o método nonfairTryAcquire em Sync

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    //获取当前锁的状态,c=0表示当前锁没有被占用,否则表示被占用了
    //CAS是乐观锁(但是并不意味着ReentrantLock就是一个乐观锁)
    //所以第一次失败后再执行一次相当正常
    int c = getState();
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
     //如果CAS再次失败,判断这个锁是不是已经被当前线程持有了(可重入)
    else if (current == getExclusiveOwnerThread()) {
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    //此次获取失败
    return false;
}

Olhando para o método de aquisição, se tryAcquire falhar, o método purchaseQueued será executado, em que addwaiter adiciona o encadeamento atual à fila de espera, que é implementada por uma lista vinculada, e o valor de retorno é o novo nó.

final boolean acquireQueued(final AbstractQueuedSynchronizer.Node node, int arg) {
    boolean failed = true;
    try {
        boolean interrupted = false;
        //一开始以为这个死循环就是所谓的阻塞方式
        //但parkAndCheckInterrupt才是让它阻塞的方式
        for (;;) {
            //注意p是当前节点的前一个节点哦
            final AbstractQueuedSynchronizer.Node p = node.predecessor();
            //如果获取成功,就返回.这里是唯一退出循环的地方
            if (p == head && tryAcquire(arg)) {
                //获取成功,设置当前节点为head节点
                //可以看出,head节点是什么信息都没有的
                setHead(node);
                p.next = null; // help GC
                failed = false;
                return interrupted;
            }
            //判断node是否可阻塞,如果是,则调用parkAndCheckInterrupt
            if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                interrupted = true;
        }
    } finally {
        if (failed)
            cancelAcquire(node);
    }
}

Através dessa série de processos de chamada, parece que não vemos onde está a chamada injustiça
. A fila realizada pela lista vinculada é o primeiro a
entrar, o primeiro a sair. Na verdade, essa injustiça é refletida apenas em nonfairTryAcquire. O novo thread pode tentar obter o bloqueio diretamente.
No entanto, se você falhar, ainda precisará alinhar honestamente

Veja novamente o que o ParkAfterFailedAcquire deve fazer

private static boolean shouldParkAfterFailedAcquire(AbstractQueuedSynchronizer.Node pred, AbstractQueuedSynchronizer.Node node) {
    int ws = pred.waitStatus;
    //这里的SIGNAL就是一个标记,表示下一个节点可阻塞
    if (ws == AbstractQueuedSynchronizer.Node.SIGNAL)
        /*
         * This node has already set status asking a release
         * to signal it, so it can safely park.
         */
        return true;
    //如果任务没有被取消
    if (ws > 0) {
        /*
         * Predecessor was cancelled. Skip over predecessors and
         * indicate retry.
         */
        do {
            node.prev = pred = pred.prev;
        } while (pred.waitStatus > 0);
        pred.next = node;
    } else {
        /*
         * waitStatus must be 0 or PROPAGATE.  Indicate that we
         * need a signal, but don't park yet.  Caller will need to
         * retry to make sure it cannot acquire before parking.
         */
        //否则,设置pred的状态为SIGNAL
        //这意味着,第二次调用这个方法一定会返回true(其实不然,要是有其他线程执行相关操作,那第二次可能还是false,详情去看解锁)
        compareAndSetWaitStatus(pred, ws, AbstractQueuedSynchronizer.Node.SIGNAL);
    }
    return false;
}

Como pode ser visto neste código, se for repetido para a segunda vez, shouldParkAfterFailedAcquire retornará true, o que significa que parkAndCheckInterrupt definitivamente executará
o método chamado parkAndCheckInterrupt. Existem muitos métodos. Um método local deve ser permitir que o thread adquira o bloqueio de bloqueio

Até agora, concluímos aproximadamente o processo de bloqueio. Para resumir ,
para bloqueios injustos, se um segmento quiser bloquear, esse segmento tentará primeiro adquirir o bloqueio usando o CAS. injusta
Se a aquisição falhar, os métodos de chamada adquirem
em adquirir, a primeira chamada de método tryacquire chama o método nonfairTryAcquire, este método aqui primeiro tentar usar o CAS para adquirir o bloqueio, se isso falhar, em seguida, verifique se o bloqueio está a ser o segmento atual tem, Isso mostra que
após a falha na tentativa de reentrada , o encadeamento atual é adicionado à fila de espera e o método AdquirirQueuizado é chamado para iniciar a
espera. O processo de espera é um loop infinito e só sai após a aquisição do
bloqueio. As etapas a seguir estão todas no loop. O thread é o primeiro elemento da fila? Observe que o nó Head não possui nenhuma informação.O primeiro elemento da fila aqui na verdade se refere a um nó após o nó Head.Se
for, use o CAS para bloquear
novamente.Se falhar, chame shouldParkAfterFailedAcquire para determinar Se o encadeamento atual deve ser bloqueado, se não, então Um método será o fio for bloqueado, em seguida, volta para a falsa
Ou seja, a segunda chamada shouldParkAfterFailedAcquire retorna verdadeiro (em geral)
, enquanto shouldParkAfterFailedAcquire retorna true, em seguida, o segmento atual começará a bloquear, saber que você pode obter um bloqueio Até o momento,
observe que, após a saída deste método, ele continuará em loop e o CAS adquirirá o bloqueio

Este é o final do resumo,
mas definitivamente notamos uma linha de código estranha.Na
aquisição, chamamos selfInterrupt () e nos interrompemos.O
que isso está fazendo?
Na verdade, se o processo de bloqueio for realizado normalmente e não tiver sido interrompido, Retornará false, a interrupção automática não será executada.Se
tiver sido interrompida, o PurchaseQueued não responderá e o método interrompido será chamado para redefinir o sinalizador de interrupção relevante,
portanto, chame a auto-interrupção novamente e responda ao estado interrompido.

método de desbloqueio

Desbloquear por lançamento

public void unlock() {
    sync.release(1);
}
public final boolean release(int arg) {
    //尝试解锁
    if (tryRelease(arg)) {
        AbstractQueuedSynchronizer.Node h = head;
        if (h != null && h.waitStatus != 0)
            //让后继节点不在处于可被阻塞装态,可能会影响其他线程shouldParkAfterFailedAcquire方法的返回值哦
            unparkSuccessor(h);
        return true;
    }
    return false;
}

Vamos primeiro olhar para o método tryRelease

protected final boolean tryRelease(int releases) {
    //state其实就是这个lock被执行了几次上锁操作
    //回顾一下可重入锁对于同一个线程上锁的原理
    //立马就可以知道,只有c为0,才能进行真正地解锁操作
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    //真正的解锁
    if (c == 0) {
        free = true;
        setExclusiveOwnerThread(null);
    }
    //只是让state的值减一
    setState(c);
    return free;
}

A seguir, veja o que o método unparkSuccessor está fazendo.Esse
método só será executado depois que o thread atual descartar o bloqueio.

private void unparkSuccessor(AbstractQueuedSynchronizer.Node node) {
    /*
     * If status is negative (i.e., possibly needing signal) try
     * to clear in anticipation of signalling.  It is OK if this
     * fails or if status is changed by waiting thread.
     */
    int ws = node.waitStatus;
    if (ws < 0)
        compareAndSetWaitStatus(node, ws, 0);

    /*
     * Thread to unpark is held in successor, which is normally
     * just the next node.  But if cancelled or apparently null,
     * traverse backwards from tail to find the actual
     * non-cancelled successor.
     */
    AbstractQueuedSynchronizer.Node s = node.next;
    //寻找head节点后第一个被阻塞的节点
    if (s == null || s.waitStatus > 0) {
        s = null;
        for (AbstractQueuedSynchronizer.Node t = tail; t != null && t != node; t = t.prev)
            if (t.waitStatus <= 0)
                s = t;
    }
    if (s != null)
        LockSupport.unpark(s.thread);
}

Percebemos que o processo de localização do primeiro nó é realmente de trás para a frente,
porque se um novo thread for adicionado à fila, ele será adicionado ao final. A mudança faz com que o problema inseguro do encadeamento ocorra ( questionável , pense primeiro).
Se você olhar para trás, o prev não será alterado, portanto não haverá problema. Quando
um novo encadeamento entra na fila de espera, há um detalhe que não tenho espaço limitado , Ou seja, depois de colocar o encadeamento atual no nó, você deve primeiro definir o prev do nó e, em seguida, definir o nó como cauda

Bloqueio justo

Sem o processo anterior de inserção de fila, coloque-o diretamente na fila
e o processo de desbloqueio é exatamente o mesmo que o bloqueio injusto, então este é o caminho a seguir

O método de travamento e destravamento é apresentado aqui.A seguir, a condição

método newCondition

Este método simplesmente chama o construtor ConditionObject para obter uma nova instância de Condition.
Este construtor não faz nada, portanto, neste caso, podemos apenas interpretar essa classe ConditionObject em detalhes.

ConditionObject

Essa classe acabou sendo uma classe interna do AQS, então parece compreensível que o construtor não tenha feito nada.
Sabemos que a Condição deve estar associada a um bloqueio. Se é uma classe interna, está inerentemente relacionada.

Vamos primeiro ver como o método de espera é implementado

public final void await() throws InterruptedException {
    if (Thread.interrupted())
        throw new InterruptedException();
    //新增一个等待节点,这个等待节点就是当前线程,状态是condition
    //addConditionWaiter将新增一个节点,将其置于队列尾,并返回这个新建的节点
    Node node = addConditionWaiter();
    //释放锁,并获取线程状态值
    //状态值就是上了几次锁
    //如果这个方法解锁失败了,将会把node的状态置为CANCEL
    int savedState = fullyRelease(node);
    int interruptMode = 0;
    //判断node是否在同步队列上
    //如果没有其他线程执行signal方法
    //那这里应该在等待队列上
    while (!isOnSyncQueue(node)) {
        //阻塞
        LockSupport.park(this);
        //如果线程被中断过,则退出循环
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
    }
    //重新获取锁成功且没有被中断
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    //去除后方状态为Cancel的Node
    if (node.nextWaiter != null) // clean up if cancelled
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
}

Em seguida é o sinal

private void doSignal(Node first) {
    do {
        //取出第一个node
        if ( (firstWaiter = first.nextWaiter) == null)
            lastWaiter = null;
        first.nextWaiter = null;
    //循环条件是将first置于同步队列失败并且
    //等待队列不为空
    //也就是说,唤醒第一个失败,那就该唤醒第二个了
    //但是失败的那个也没有任何保存措施,直接丢弃了?
    } while (!transferForSignal(first) &&
             (first = firstWaiter) != null);
}

Resumo:
. Método await da condição de execução, o segmento atual será colocado em uma coluna de espera
Essa fila é que cada um tem uma condição, e que fila e bloqueado quando vimos anteriormente não é a mesma fila
na análise A fila de sincronização mencionada no método de espera é a fila aguardando o bloqueio (esse nome é meu absurdo). No
código, há também uma distinção entre essas duas filas.Embora elas usem Node, na fila de sincronização, nos nós frontal e traseiro É prev e next, a fila de espera usa nextWaiter e não registra o nó anterior

Depois de executar a espera, coloque o encadeamento na fila de espera primeiro e solte o bloqueio, aguardando o início de outros encadeamentos. Depois de
executar o sinal, o encadeamento atual ativará o primeiro encadeamento na fila de espera que pode ser acordado e não pode ser descartado diretamente.

Até agora, não vimos como o método de temporização é implementado, vamos dar uma olhada em aguardar (muito tempo, unidade TimeUnit)

public final boolean await(long time, TimeUnit unit)
        throws InterruptedException {
    //先转化为纳秒
    long nanosTimeout = unit.toNanos(time);
    if (Thread.interrupted())
        throw new InterruptedException();

    Node node = addConditionWaiter();
    int savedState = fullyRelease(node);

    //设置截止时间
    final long deadline = System.nanoTime() + nanosTimeout;
    boolean timedout = false;
    int interruptMode = 0;
    while (!isOnSyncQueue(node)) {
        //超时了
        if (nanosTimeout <= 0L) {
            timedout = transferAfterCancelledWait(node);
            break;
        }
        if (nanosTimeout >= spinForTimeoutThreshold)
            LockSupport.parkNanos(this, nanosTimeout);
        if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
            break;
        nanosTimeout = deadline - System.nanoTime();
    }
    if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
        interruptMode = REINTERRUPT;
    if (node.nextWaiter != null)
        unlinkCancelledWaiters();
    if (interruptMode != 0)
        reportInterruptAfterWait(interruptMode);
    return !timedout;
}

Esse método em si não tem foco,
mas devido ao LockSupport.parkNanos

public static void parkNanos(Object blocker, long nanos) {
    if (nanos > 0) {
        Thread t = Thread.currentThread();
        setBlocker(t, blocker);
        UNSAFE.park(false, nanos);
        setBlocker(t, null);
    }
}

O próprio parque de métodos local pode ter parâmetros de tempo,
portanto pode ser cronometrado

AQS (AbstractQueuedSynchronizer)

Na verdade, uma grande parte do código apresentado acima é o código AQS dentro
, mas este artigo não distingue
basicamente fez esta classe abstrata é uma fila de fio, de modo que o código acima é geralmente associada com as aparece fila nesta classe
acrescentar que, embora Essa classe é cara como uma classe abstrata, mas não encontrei um método abstrato

Acho que você gosta

Origin www.cnblogs.com/ZGQblogs/p/12709520.html
Recomendado
Clasificación