结合多线程实例谈一谈LinkedBlockingQueue的原理

之前工作时候写了点多线程的东西,当时自己用了LinkedBlockingQueue这个队列,最近看了看这个源码,这里就算做个笔记吧。
一般我们涉及到和服务器交互的时候,比如java和C++服务器进行交互,这时候肯定需要使用socket。通常做法是维护两个队列,三个线程。A队列里面放需要发送给服务器的数据包,B队列里面存放服务器返回过来的数据包。三个线程功能如下:A线程专门负责从A队列里取数据发送到服务端,B线程专门负责从socket通道中读数据放入到B队列。C线程专门负责从B队列中取数据进行分包后将数据包转发给相应的数据处理模块进行数据处理。部分代码如下
两个队列:
    BlockingQueue<byte[]> A = new LinkedBlockingQueue<byte[]>();
    BlockingQueue<byte[]> B = new LinkedBlockingQueue<byte[]>();
线程A:
    Thread A = new Thread(new Runnable()
    {
        @Override
        public void run() {
            try {
                socket = new Socket(IP,Port);
                Log.e("连接服务器成功");
                B.start();
                C.start();
                while(true){
                    byte[] data = A.take();
                    socket.getOutputStream().write(data);
                }
            } catch (IOException e) {
                Log.e("连接服务器失败");
                e.printStackTrace();
            } catch (InterruptedException e) {
                Log.e("队列没有数据");
                e.printStackTrace();
            }

        }
    });
线程B:
    Thread B = new Thread(new Runnable()
    {
        @Override
        public void run() {
            try {
                byte[] receive = new byte[1000];
                InputStream is = socket.getInputStream();
                while(true){
                    is.read(receive);
                    B.put(receive);
                    receive = new byte[1000];
                }
            } catch (IOException e) {
                e.printStackTrace();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    });
线程C:
    Thread C = new Thread(new Runnable()
    {
        @Override
        public void run() {
              while(true){
                    byte[] packet = B.take();
                     //分包,调用业务逻辑接口
               }
        }
    });
这里A线程可以在构造函数中直接开启,由于socket这类最好用单例,所以可以这么写
    private  TCP(){
        A.start();
    }
多线程的逻辑大概就是这样,当然实际情况中可能更加复杂,这个按照业务场景改变即可。上述代码中用了LinkedBlockingQueue这个类,查阅相关资料可以知道它是一个阻塞型队列,put方法在队列满的时候会阻塞直到有队列成员被消费,take方法在队列空的时候会阻塞,直到有队列成员被放进来。相关代码如下(jdk1.8版本):
  private final AtomicInteger count = new AtomicInteger();
  transient Node<E> head;
  private transient Node<E> last;
  private final ReentrantLock takeLock = new ReentrantLock();
  private final Condition notEmpty = this.takeLock.newCondition();
  private final ReentrantLock putLock = new ReentrantLock();
  private final Condition notFull = this.putLock.newCondition();
以上代码可以看出LinkedBlockingQueue含有一个链表,两把重入锁,两个condition。condition其实是监视器接口,类似于object的notify、wait这些方法,需要和lock配合,可以实现等待通知模式,这里有两把锁分别创建两个condition。
put方法源码:
  public void put(E paramE)
    throws InterruptedException
  {
    if (paramE == null) {
      throw new NullPointerException();
    }
    int i = -1;
    Node localNode = new Node(paramE);
    ReentrantLock localReentrantLock = this.putLock;
    AtomicInteger localAtomicInteger = this.count;
    localReentrantLock.lockInterruptibly();
    try
    {
      while (localAtomicInteger.get() == this.capacity) {
        this.notFull.await();
      }
      enqueue(localNode);
      i = localAtomicInteger.getAndIncrement();
      if (i + 1 < this.capacity) {
        this.notFull.signal();
      }
    }
    finally
    {
      localReentrantLock.unlock();
    }
    if (i == 0) {
      signalNotEmpty();
    }
  }
可以看到当往队列里插入元素时,如果队列不可用,会进入this.notFull.await()代码。我们查看await源码可以发现阻塞这一动作主要通过LockSupport.park(this)来实现:
public final void await()
      throws InterruptedException
    {
      if (Thread.interrupted()) {
        throw new InterruptedException();
      }
      AbstractQueuedSynchronizer.Node localNode = addConditionWaiter();
      int i = AbstractQueuedSynchronizer.this.fullyRelease(localNode);
      int j = 0;
      while (!AbstractQueuedSynchronizer.this.isOnSyncQueue(localNode))
      {
        LockSupport.park(this);//这行代码!!!!!!
        if ((j = checkInterruptWhileWaiting(localNode)) != 0) {
          break;
        }
      }
      if ((AbstractQueuedSynchronizer.this.acquireQueued(localNode, i)) && (j != -1)) {
        j = 1;
      }
      if (localNode.nextWaiter != null) {
        unlinkCancelledWaiters();
      }
      if (j != 0) {
        reportInterruptAfterWait(j);
      }
    }
至于这个park这个方法实现原理我现在也不是很懂,以后这块研究有点眉目了再写一写吧(看书上说这个方法是jvm实现的,调用了native方法)。回到put方法,可以看出当队列不满的时候将数据放入到队列中,然后判断count+1是否小于this.capacity,如果小于就唤起生产者线程,最后在finally模块中释放锁,判断队列是否为空,不为空唤起消费者线程,告知可以取元素。
take方法源码:
  public E take()
    throws InterruptedException
  {
    int i = -1;
    AtomicInteger localAtomicInteger = this.count;
    ReentrantLock localReentrantLock = this.takeLock;
    localReentrantLock.lockInterruptibly();
    Object localObject1;
    try
    {
      while (localAtomicInteger.get() == 0) {
        this.notEmpty.await();
      }
      localObject1 = dequeue();
      i = localAtomicInteger.getAndDecrement();
      if (i > 1) {
        this.notEmpty.signal();
      }
    }
    finally
    {
      localReentrantLock.unlock();
    }
    if (i == this.capacity) {
      signalNotFull();
    }
    return localObject1;
  }
take方法和put方法类似,如果为空就是await方法,不为空是让进行出列操作,最后释放锁,返回object。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章