多线程与高并发(中)

996 阅读3分钟

LockSupport

LockSupport.park() ,LockSupport.unpark(Thread) 可以针对某个线程进行阻塞或者释放。相对比较灵活,并且.unpark(Thread)方法可以提前执行,park()执行到的时候并不会阻塞

面试题

面试题:写一个固定容量同步容器,拥有put和get方法,以及getCount方法, 能够支持2个生产者线程以及10个消费者线程的阻塞调用

public class syncContainer {
  private final LinkedList<T> lists = new LinkedList();
  private final int MAX=10;
  private int count =0;

  public synchronized void put(T t){
      while (lists.size()== MAX){
          try {
              this.wait();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }
          count++;
          lists.add(t);
          this.notifyAll();

      }
  }
  public synchronized T get(){
      while (lists.size()==0){
          try {
              this.wait();
          } catch (InterruptedException e) {
              e.printStackTrace();
          }

      }
      T t = lists.removeFirst();
      count--;
      this.notifyAll();
      return t;

  }
}

深入解读AQS源码

说实话看源码最好是看能够跑起来的源码,点着看很容易走到死胡同,最好是把代码运行起来,我们追着代码看,这样能够更好的知道他的来龙去脉,然后根据自己的记忆画出他的类依赖关系,以及每个类中的所调用的方法加深记忆。

ReentrantLock 加锁解锁流程图

深入剖析每个细节是如何处理的

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() {
                //CAS操作尝试加锁
            if (compareAndSetState(0, 1))
                //加锁成功设置当前线程为独占线程
                setExclusiveOwnerThread(Thread.currentThread());
            else
                acquire(1);
        }
    }
public final void acquire(int arg) {
        //
        if (!tryAcquire(arg) &&
            acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
            selfInterrupt();
    }
    
    protected final boolean tryAcquire(int acquires) {
                    //非公平获取
            return nonfairTryAcquire(acquires);
        }
        
       final boolean nonfairTryAcquire(int acquires) {
            //获取当前线程
            final Thread current = Thread.currentThread();
            //获取当前线程的state状态 0:是无锁状态
            int c = getState();
            //如果没有锁
            if (c == 0) {
                //CAS尝试加锁
                if (compareAndSetState(0, acquires)) {
                    //加锁成功设置为独占线程
                    setExclusiveOwnerThread(current);
                    return true;
                }
            }
            //如果当前线程是独占线程
            else if (current == getExclusiveOwnerThread()) {
                //因为是可重入锁在原来的state上在加1
                int nextc = c + acquires;
                if (nextc < 0) // overflow
                    throw new Error("Maximum lock count exceeded");
                //更新state
                setState(nextc);
                return true;
            }
            //如果没拿到锁返回false
            return false;
        }  
        
        //把线程加入NODE双向链表队列中
        private Node addWaiter(Node mode) {
        Node node = new Node(Thread.currentThread(), mode);
        // Try the fast path of enq; backup to full enq on failure
        // 设置pred 是最后一个node
        Node pred = tail;
        //如果pred不是null
        if (pred != null) {
            node.prev = pred;
            //利用CAS更新尾部node
            if (compareAndSetTail(pred, node)) {
                //把尾部更新成当前node
                pred.next = node;
                return node;
            }
        }
        //如果tail是null
        enq(node);
        return node;
    }
    
    private Node enq(final Node node) {
        for (;;) {
            Node t = tail;
            //如果tail是null 初始化一个node链表
            if (t == null) { // Must initialize
                if (compareAndSetHead(new Node()))
                    tail = head;
            } else {
                node.prev = t;
                //如果tail不是null在他后面追加一个node
                if (compareAndSetTail(t, node)) {
                    t.next = node;
                    return t;
                }
            }
        }
    }
    
    
    final boolean acquireQueued(final Node node, int arg) {
        boolean failed = true;
        try {
            boolean interrupted = false;
            for (;;) {
                //获取队列的前一个node节点
                final Node p = node.predecessor();
                //如果p节点是head,尝试去获取锁
                if (p == head && tryAcquire(arg)) {
                    //如果获取到锁说明head已经处理完了,p节点前移编程head
                    setHead(node);
                    p.next = null; // help GC
                    failed = false;
                    return interrupted;
                }
                 //已经尝试过获取锁了,但还是失败了(当然有可能是因为p != head)
                if (shouldParkAfterFailedAcquire(p, node) &&
                    parkAndCheckInterrupt())
                    interrupted = true;
            }
        } finally {
            if (failed)
                cancelAcquire(node);
        }
    }
    //已经尝试过获取锁了,但还是失败了(当然有可能是因为p != head)
    private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
        int ws = pred.waitStatus;
        if (ws == Node.SIGNAL)
            
            /*
             * 前驱节点已经设置了SIGNAL,闹钟已经设好,现在我可以安心睡觉(阻塞)了。
             * 如果前驱变成了head,并且head的代表线程exclusiveOwnerThread释放了锁,
             * 就会来根据这个SIGNAL来唤醒自己
             */
            
            /*
             * This node has already set status asking a release
             * to signal it, so it can safely park.
             */
            return true;
        if (ws > 0) {
          /*
             * 发现传入的前驱的状态大于0,即CANCELLED。说明前驱节点已经因为超时或响应了中断,
             * 而取消了自己。所以需要跨越掉这些CANCELLED节点,直到找到一个<=0的节点
             */
             
             
            /*
             * Predecessor was cancelled. Skip over predecessors and
             * indicate retry.
             */
            do {
                node.prev = pred = pred.prev;
            } while (pred.waitStatus > 0);
            pred.next = node;
        } else {
            
            *
             * 进入这个分支,ws只能是0或PROPAGATE。
             * CAS设置ws为SIGNAL
             */
            
            /*
             * 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.
             */
            compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
        }
        return false;
    }

解锁的代码自己分析一下,有一个重要的就是可能被加了多个锁,我们要一个一个的去解锁。

VarHandler

JDK9才出现的变量句柄-VarHandler,说白了这个VarHandler能够直接操作二进制代码,能够高效,有兴趣的同学可以自己查阅一下。

引用

  • 强引用:只要某个对象与强引用关联,那么JVM在内存不足的情况下,宁愿抛出outOfMemoryError错误,也不会回收此类对象。
  • 软引用:java中使用SoftRefence来表示软引用,如果某个对象与软引用关联,那么JVM只会在内存不足的情况下回收该对象。那么利用这个特性,软引用可以用来做缓存。
  • 弱引用:java中使用WeakReference来表示弱引用。如果某个对象与弱引用关联,那么当JVM在进行垃圾回收时,无论内存是否充足,都会回收此类对象。
  • 虚引用:java中使用PhantomReference来表示虚引用。虚引用,虚引用,引用就像形同虚设一样,就像某个对象没有引用与之关联一样。若某个对象与虚引用关联,那么在任何时候都可能被JVM回收掉。虚引用不能单独使用,必须配合引用队列一起使用。