다중 스레드 8 개 AbstractQueuedSynchronizer의 소스 코드 분석의 아름다움 <두>

디렉토리

분석 소스 AQS <두>

이 문서는 ConditionObject가의 AQS를 분석, AQS는 내부 클래스가 구현 대기 알림 메커니즘입니다.

1 단으로 큐

조건 대기열 및 AQS 동기화 큐는 다음과 같은 구조가 다르다 :

둘 사이의 차이 :

  • 도 1은 다른 연결리스트 구조, 큐 상태가 단독으로 연결된 목록 동기 큐의 이중 연결리스트이다.
  • 조건 대기 2 개의 상이한 큐의 상태가 잠금, 잠금 해제가 자동 AWAIT 메소드를 호출 취득 큐 스레드 이미, 조건 (예를 들면, IO, MQ 메시지 등), 동기화 큐 스레드를 기다리는 현재 스레드를 정지 잠금이 잠금 실패를 획득 한 후 사용할 수 대기 할, 획득 잠금을 기다리고 있습니다.

모두 접촉 :

다른 신호 스레드의 완료를 대기 조건이 메소드를 호출 할 때, 스레드 보류 통지 큐의 상태는 큐 노드 동기화 큐에 추가, 제거 0으로 Node.CONDITION의 WS의 노드 상태를 조정한다 그리고 락을 대기 시작했다.

2, ConditionObject가

ConditionObject가와 노드, AQS 내부 클래스이다 ConditionObject가이 상태 인터페이스를 구현하는 메인 쓰레드 호출 기다리고 신호의 조건에 newConditon 잠금 서브 클래스를 호출 스레드 차단 통지기구, 상태 객체의 획득 방법을 달성

다음과 같이, 예를 ReentrantLock와 :

ReentrantLock lock  = new ReentrantLock();
Condition condition =  lock.newCondition();

그는 개체를 반환 NewCondition 행 잠금 볼 방법은 ConditionObject가있다

final ConditionObject newCondition() {
    return new ConditionObject();
}

생산자 샘플 코드에 대한 간단한 :

package AQS;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;
/**
 * @author zdd
 * 2019/12/30 下午
 * Description: 利用ReentrantLock和Condition实现生产者消费者
 */
public class ConditionTest {
   static ReentrantLock lock  = new ReentrantLock();
   static Condition condition =  lock.newCondition();
    public static void main(String[] args) {
       //资源类
        Apple apple = new Apple();
     //1.开启生产者线程
        new Thread(()-> {
            for (;;) {
                lock.lock();
                try {
                    //苹果没有被消费,吃完通知我,我再生产哦
                    if (apple.getNumber() > 0) {
                        condition.await();
                    }
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println("生产一个苹果");
                    apple.addNumber();
                    //通知消费线程消费
                    condition.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        },"producer").start();
      //2.开启消费者线程
        new Thread(()-> {
            for (;;) {
                lock.lock();
                try {
                    //苹果数量为0,挂起等待生产苹果,有苹果了会通知
                    if(apple.getNumber() == 0) {
                        condition.await();
                    }
                    TimeUnit.SECONDS.sleep(1);
                    System.out.println("消费一个苹果");
                    apple.decreNumber();
                    //通知生产线程生产
                    condition.signal();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        },"consumer").start();

    }
  //定义苹果内部类 
   static class Apple {
        //记录苹果数量
        private Integer number =0;
        public void addNumber() {
            number++;
            System.out.println(Thread.currentThread().getName() +"当前苹果数量:"+number );
        }
        public void decreNumber() {
            number--;
            System.out.println(Thread.currentThread().getName() +"当前苹果数量:"+number);
        }
        public Integer getNumber() {
            return number;
        }
    }
}

다음도 실행 결과이다 :

2.1 AWAIT () 메소드

잠금을 획득 한 현재의 thread의 경우, 잠금을 해제 할 수있는 주도권을 기다리고 호출, 어떤 조건 (IO, MQ 메시지 등) 잠금을 얻기 위해 경쟁 과정으로 이동, 일어나 기다리고, 현재 스레드를 일시 중단합니다. 이 방법은 노드 스레드 패키지 현재 노드를 설정, 큐, 잠금 해제 자원에 추가 조건 조건 및 현재 스레드를 일시 중단합니다.

다음과 같이 구체적인 구현 단계는 다음과 같습니다

1 스레드 패키지 노드 큐 상태 조건에 추가 WS = -2 Node.CONDITION이다.

2, 잠금을 해제.

3、将自己阻塞挂起,如果线程被唤醒,首先检查自己是被中断唤醒的不。如果是被中断唤醒,跳出while循环;如果是被其他线程signal唤醒,则判断当前线程所在node是否被加入到同步等待队列,已在同步队列中也跳出while循环,否则继续挂起,signal唤醒逻辑会将condition条件队列node 移出,加入到同步队列中,去等待获取锁。

4,线程被唤醒,执行acquireQueued方法,线程会尝试获取锁,若失败则在同步队列中找到安全位置阻塞,成功则从调用await()方法处继续向下执行,返回值表示当前线程是否被中断过。

public final void await() throws InterruptedException {
            if (Thread.interrupted())
                throw new InterruptedException();
            Node node = addConditionWaiter();
            int savedState = fullyRelease(node);
            int interruptMode = 0;
            while (!isOnSyncQueue(node)) {
               //挂起当前线程
                LockSupport.park(this);
              // 被唤醒: 1,被其他线程唤醒,2,中断唤醒,
                if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
                    break;
            }
           //1,如果被signal正常唤醒执行acquireQueued,返回false,如果获取到锁就继续执行调用await后面的代码了,未获取到锁就在同步队列中继续挂起等待锁执行了
           //2,如果被中断唤醒的,acquireQueued 返回true
            if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
               //线程在被signal后,再被中断的
                interruptMode = REINTERRUPT;
         //  后面代码处理的是被中断唤醒的情况
            if (node.nextWaiter != null) // clean up if cancelled
              //如果nextWaiter!=null,则表示还在条件队列中,清理一下所有被取消node
              //什么情况下会进入该if判断中,如果是正常被signal的,会将该node从条件队列移出加入到同步队列中的, nextWaiter 一定为null,那就是被异常中断情况,
                unlinkCancelledWaiters();
            if (interruptMode != 0)
               //响应中断模式
                reportInterruptAfterWait(interruptMode);
        }

第1步,执行addConditionWaiter方法,主要逻辑是将线程封装为Node,并添加到条件队列中

        private Node addConditionWaiter() {
          //1.获取队列中最后一个节点
            Node t = lastWaiter;
            //2.如果最后一个节点被取消,清除出队
            if (t != null && t.waitStatus != Node.CONDITION) {
                unlinkCancelledWaiters();
                t = lastWaiter;
            }
            //3. t 指向最新有效的节点,也可能条件队列为空,t==null
            Node node = new Node(Thread.currentThread(), Node.CONDITION);
            if (t == null)
                firstWaiter = node;
            else
                t.nextWaiter = node;
            lastWaiter = node;
            return node;
        }

第2步,完全释放锁 fullyRelease,将同步状态state 设置为初始值0,这里考虑到有多次重入获取锁情况,state >1,这时需完全释放锁。

  final int fullyRelease(Node node) {
        boolean failed = true;
        try {
            int savedState = getState();
            //1,释放锁
            if (release(savedState)) {
                failed = false;
                return savedState;
            } else {
                throw new IllegalMonitorStateException();
            }
        } finally {
            if (failed)
            //2,释放锁失败,将条件队列中的节点标记为取消
                node.waitStatus = Node.CANCELLED;
        }
    }

isOnSyncQueue 判断node是否在同步队列中

 final boolean isOnSyncQueue(Node node) {
       //1,这2种情况肯定没有在同步队列中
        if (node.waitStatus == Node.CONDITION || node.prev == null)
            return false;
        if (node.next != null) // If has successor, it must be on queue
            return true;
       //3.从同步队列尾节点开始对比,看是否在同步队列中
        return findNodeFromTail(node);
    }

findNodeFromTail 从后向前寻找

  private boolean findNodeFromTail(Node node) {
        Node t = tail;
        for (;;) {
            if (t == node)
                return true;
            if (t == null)
                return false;
            t = t.prev;
        }
    }

在线程被唤醒后,检查挂起期间是否被中断

private int checkInterruptWhileWaiting(Node node) {
    return Thread.interrupted() ?
        (transferAfterCancelledWait(node) ? THROW_IE : REINTERRUPT) :
        0;
}

如果线程被中断了,那就需要将在条件队列中等待的该节点执行 transferAfterCancelledWait

 final boolean transferAfterCancelledWait(Node node) {
       // 判断是否是被signal通知唤醒的,会更新为0,更新成功,执行入队操作(加入同步队列)
        if (compareAndSetWaitStatus(node, Node.CONDITION, 0)) {
            enq(node);
            return true;
        }
        while (!isOnSyncQueue(node))
          //未在同步队列中,让出处理器,线程回到就绪态,等待下一次分配cpu调度
            Thread.yield();
        return false;
    }

最后根据不同的中断值做出相应处理

private void reportInterruptAfterWait(int interruptMode)
    throws InterruptedException {
    if (interruptMode == THROW_IE)
        //1,直接抛出中断异常
        throw new InterruptedException();
    else if (interruptMode == REINTERRUPT)
        //2,中断标志
        selfInterrupt();
}

2.2、signal方法

就是将条件队列中的node移出,加入到同步队列等待获取锁的过程。

流程图如下:

  public final void signal() {
            if (!isHeldExclusively())
                throw new IllegalMonitorStateException();
            Node first = firstWaiter;
            if (first != null)
                doSignal(first);
        }
   private void doSignal(Node first) {
            do {
                if ( (firstWaiter = first.nextWaiter) == null)
                    lastWaiter = null;
              // 1、将first节点执行出队操作
                first.nextWaiter = null;
            } while (!transferForSignal(first) &&
                     (first = firstWaiter) != null);
             //2,如果条件队列中有ws =-2的节点,肯定会移出一个到同步队列中
        }
final boolean transferForSignal(Node node) {
       //1,将node ws更新为0 ,如果node 状态不等于CONDITION,一定是被取消了
        if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
            return false;
       //2,加入到同步队列中,返回的p是node的pre 
        Node p = enq(node);
        int ws = p.waitStatus;
       //3,如果前置节点被取消,或者更新p的 ws =-1 失败,直接唤醒线程,否则等待前置节点唤醒自己
        if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
           //唤醒线程
            LockSupport.unpark(node.thread);
        return true;
    }

3、总结

1、Condition提供的阻塞通知机制与Object类两者对比:

  • 方法不同,Condition提供方法有 await(), signal(),signalAll(), Object类提供的是wait(),notify() , notifyAll()
  • 配合使用对象不同,Condition条件需要和Lock配合使用,Object类需和Synchronized关键字配合。
  • 多条件, Condition可实现多个条件,即创建多个Condition对象,可以每个Condition对象对应一种条件,从而有选择的实现唤醒通知,Object类的要唤醒一个阻塞线程,只能在一个条件队列中,唤醒是随机的,没有Condition使用灵活。

2、注意区别Condition条件队列与同步队列两者的区别,2个队列中线程等待条件不同

추천

출처www.cnblogs.com/flydashpig/p/12122167.html