Loading

Java 锁

synchronized 关键字实现锁:

在 Java 中 synchronized 关键字将会隐式的获取锁, 但是它将锁的获取和释放固化了, 也就是先获取在释放. synchronized 关键字经过编译后会在同步代码块的前后分别形成 monitorentermonitorexit 两个字节码指令, 这两个字节码指令都需要一个 reference 类型的参数来指明要锁定和解锁的对象.

在虚拟机执行 monitorenter 指令时, 首先要尝试获取对象的锁. 如果这个对象没有被锁定, 或者当前对象已经拥有了哪个对象的锁, 把锁的计数器加 1, 相应的, 在执行 monitorexit 指定时会将锁计数器减 1, 当计数器为 0 时, 锁就被释放. 如果获取对象锁失败, 那当前线程就要阻塞等待, 直到对象锁被另外一个线程释放为止.

synchronized 同步块对同一条线程来说是可重入的, 不会出现把自己锁死的情况; 同步块在已进入的线程执行完之前, 会阻塞后面其它线程的进入.

lock(), lockInterruptlly() 的区别
public final void acquireInterruptibly(int arg) throws InterruptedException {
    // 这里会响应中断信号
	if (Thread.interrupted())
		throw new InterruptedException();
    if (!tryAcquire(arg))
        doAcquireInterruptibly(arg);
}

队列同步器实现锁:

队列同步器使用一个 int 类型的成员变量表示同步状态. 实现原理是依赖硬件的原子性 CAS 指令来完成对状态的更新, 成功更新该状态即可表示对锁的获取(太尼玛机智了), 对于没能获取到锁的线程则将它们阻塞掉, 使它们无法被调度器调度. 当占有线程的锁释放锁之后, 将阻塞的线程唤醒, 又让它们去更改状态.

  • 独占锁

    • 自定义独占锁: 只能有一个线程获取到锁, 并且不可重入

      class Mutext implements Lock {
          private final Sync sync = new Sync();
      
          public void lock() {
          	sync.acquire(1);
          }
      
          public boolean tryLock() {
          	return sync.tryAcquire(1);
          }
      
          public void unlock() {
          	sync.release(1);
          }
      
          publc Condition newCondition() {
          	return sync.newCondition();
          }
      
          public boolean isLocked() {
          	return sync.isHeldExclusively();
          }
      
          public boolean hasQueuedThreads() {
          	return sync.hasQueuedThreads();
          }
      
          public void lockInterruptibly throws InterruptedException {
          	sync.acquireInterruptibly(1);
          }
      
          public boolean tryLock(long timeOut, TimeUnit unit) throws InterruptedException {
          	return sync.tryAcquireNanos(1, unit.toNanos(timeout));
          }
      
          private static class Sync extends AbstractQueuedSynchronizer {
              // 锁是否处于占用状态
              protected boolean isHeldExclusively() {
              	return getState() == 1;
              }
      
              public boolean tryAcquire(int acquires) {
                  /* 
                   * 这里通过底层的原子性 CAS 指令来更新状态, 一旦状态更新成功, 
                   * 则将当前线程设置为占有锁的线程
                   */
                  if (compareAndSetState(0, 1)) {
                      setExclusiveOwnerThread(Thread.currentThread());
                      return true;
                  }
      			return false;
              }
      
              protected boolean tryRelease(int releases) {
                  if (getState() == 0) {
                      throw new IllegalMonitorStateException();
                  }
                  setExclusiveOwnerThread(null);
                  // 这里不要调用 compareAndSetState() 方法, 
                  // 因为永远只能有一个线程获取到锁, 只会有一个线程调用该方法,
                  // 不存在多线程竞争的关系
                  setState(0);
      			return true;
              }
      
              Condition newCondition() {
                  return new ConditionObject();
              }
          }
      }
      
    • 实现原理:

      • 获取锁:

        public final void acquire(int arg) {
            // 这里调用的 tryAcquire() 方法就是我们子类所重写的方法
            if (!tryAcquire(arg) &&
                acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
                selfInterrupt();
        }
        
        private Node addWaiter(Node mode) {
            // 以当前线程构建节点
            Node node = new Node(Thread.currentThread(), mode);
            // 这里快速尝试一下能不能在尾部添加节点
            Node pred = tail;
            if (pred != null) {
                node.prev = pred;
                if (compareAndSetTail(pred, node)) {
                    pred.next = node;
                    return node;
                }
            }
            // 若快速添加节点失败了, 则调用 enq() 方法将节点添加到队列尾部
            enq(node);
            return node;
        }
        
        private Node enq(final Node node) {
            /* 
             * 这里通过死循环将节点添加到队列尾部, 该方法保证了节点一定会添加成功
             * TODO 如果在该循环添加还未成功时, 占有锁的线程已经释放了锁怎么办
             */
            for (;;) {
                Node t = tail;
                if (t == null) { 
                    // 这里会创建一个空的节点
                    Node h = new Node();
                    h.next = node;
                    node.prev = h;
                    if (compareAndSetHead(h)) {
                        tail = node;
                        return h;
                    }
                }
                else {
                    // 建立前向的指向关系
                    node.prev = t;
                    // 利用底层的原子性 CAS 指令设置尾节点
                    if (compareAndSetTail(t, node)) {
                        // 尾节点设置成功后再建立后向的指向关系
                        t.next = node;
                        return t;
                    }
                }
            }
        }
        
        final boolean acquireQueued(final Node node, int arg) {
            try {
                boolean interrupted = false;
                for (;;) {
                    final Node p = node.predecessor();
                    // 当前节点的前一个节点是头节点则尝试获取锁
                    if (p == head && tryAcquire(arg)) {
                        setHead(node);
                        p.next = null; 
                        return interrupted;
                    }
                    // 未能获取到锁
                    if (shouldParkAfterFailedAcquire(p, node) &&
                        parkAndCheckInterrupt())
                        interrupted = true;
                }
            } catch (RuntimeException ex) {
                cancelAcquire(node);
                throw ex;
            }
        }
        
        private static boolean shouldParkAfterFailedAcquire(Node pred, Node node) {
            int ws = pred.waitStatus;
            if (ws == Node.SIGNAL)
                return true;
            if (ws > 0) {
                do {
                    // 前驱节点被取消或中断, 将其从等待队列中移除
                    node.prev = pred = pred.prev;
                } while (pred.waitStatus > 0);
                pred.next = node;
            } else {
                /* 
                 * 将当前节点的前一个节点的状态设置为 Node.SIGNAL
                 * TODO 不明白为什么这里需要使用 compareAndSetWaitStatus()
                 * 按理说不会出现多线程设置的情况
                 */
                compareAndSetWaitStatus(pred, ws, Node.SIGNAL);
            } 
            return false;
        }
        
        private final boolean parkAndCheckInterrupt() {
            // 将当前线程阻塞, 也就是将未能获取到锁的线程阻塞
            LockSupport.park(this);
            return Thread.interrupted();
        }
        
      • 释放锁:

        public final boolean release(int arg) {
            // 这里调用自定的 tryRelease() 方法
            if (tryRelease(arg)) {
                Node h = head;
                if (h != null && h.waitStatus != 0) {
                    // 释放锁
                    unparkSuccessor(h);
                }
                return true;
            }
            return false;
        }
        
        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);
        }
        
  • 共享锁: 共享锁允许多个线程获取锁

    • 自定义共享锁:

      public class TwinsLock implements Lock {
          private final Sync sync = new Sync(2);
          
          public void lock() {
              sync.accquireShared(1);
          }
          
          public void unlock() {
              sync.releaseShared(1);
          }
          
          private static final class Sync extends AbstractQueuedSynchronizer {
              Sync(int count) {
                  if (count <= 0) {
                      throw new IllegalArgumentException("count must be larger than zero");
                  }
                  setState(count);
              }
              // 获取共享锁
              public int tryAcquireShared(int reduceCount) {
                  for (;;) {
                      int current = getState();
                      int newCount = current - reduceCount;
                      if (newCount < 0 || compareAndSetState(current, newCount)) {
                          return newCount;
                      }
                  }
              }
              
              // 释放共享锁
              public boolean tryReleaseShared(int returnCount) {
                  for (;;) {
                      int current = getState();
                      int newCount = current + returnCount;
                      /* 
                       * 这里需要使用底层的原子性 CAS 指令, 因为可能同时
                       * 存在多个线程释放锁
                       */
                      if (compareAndSetState(current, newCount)) {
                          return true;
                      }
                  }
              }
          }
      }
      
  • 排它重入锁: 该锁能够支持一个线程对资源的重复加锁. 先对锁进行获取的请求一定先被满足, 则锁是公平的; 反之, 是不公平的. ReentrantLock 是 Java 提供的可重入锁.

    • 非公平获取锁:

      final boolean nonfairTryAcquire(int acquires) {
          final Thread current = Thread.currentThread();
          int c = getState();
          if (c == 0) {
              /*
               * 为什么说这个实现是不公平的呢? 因为当一个线程释放完锁之后,
               * 首先应该唤醒等待队列中的第一个线程来获取锁, 但是这里并没有
               * 这样处理, 而是直接让当前获取锁的线程来获取锁, 当前线程插队了,
               * 这称之为抢锁.
               */
              if (compareAndSetState(0, acquires)) {
                  setExclusiveOwnerThread(current);
                  return true;
              }
          } else if (current == getExclusiveOwnerThread()) {
              int nextc = c + acquires;
              if (nextc < 0) {
                  throw new Error("Maxium lock count exceeded");
              }
              setState(nextc);
              return true;
          }
          return false;
      }
      
    • 公平获取锁:

      protected final boolean tryAcquire(int acquires) {
          final Thread current = Thread.currentThread();
          int c = getState();
          if (c == 0) {
              // 是等待队列中的第一个线程才能获取锁
              if (isFirst(current) &&
                  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;
      }
      
    • 释放锁:

      proteced final boolean tryRelease(int releases) {
          int c = getState() - releases;
          if (Thread.currentThread() != getExclusiveOwnerThread()) {
              throw new IllegalMonitorStateException();
          }
          boolean free = false;
          // 当所有锁释放完成之后才会返回 true
          if (c == 0) {
              free = true;
              setExclusiveOwnerThread(null);
          }
          setState(c);
          return free;
      }
      
  • 读写锁:

    读写锁维护一个读锁和一个写锁. 读锁是一个支持重进入的共享锁, 写锁是一个支持重进入的排它锁. Java 提供了 ReentrantReadWriteLock.

    • 使用示例:

      public class Cache {
          static Map<String, Object> map = new HashMap<>();
          static ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
          static Lock rLock = rwlock.readLock();
          static Lock wLock = rwlock.writeLock();
          
          public static final Object get(String key) {
              rLock.lock();
              try {
                  return map.get(key);
              } finally {
                  rLock.unlock();
              }
          }
          
          public static final Object put(String key, Object value) {
              wLock.lock();
              try {
                 return map.put(key, value); 
              } finally {
                  wLock.unlock();
              }
          }
          
          public static final void clear() {
              wLock.lock();
              try {
                  map.clear();
              } finally {
                  wLock.unlock();
              }
          }
      }
      
    • 实现原理:

      将状态变量进行分割, 高 16 位表示读锁状态, 低 16 位表示写锁状态.

      • 写锁的获取: 若当前线程已经获取了写锁, 则增加写状态; 若读锁已经被获取或者该线程不是已经获取写锁的线程, 则进入等待状态.

        protected final boolean tryAcquire(int acquires) {
            Thread current = Thread.currentThread();
            int c = getState();
            // 通过位运算获取写锁状态
            int w = exclusiveCount(c);
            if (c != 0) {
                // 存在读锁或者当前线程不是已经获取写锁的线程
                if (w == 0 || current != getExclusiveOwnerThread()) {
                    return false;
                }
                if (w + exclusiveCount(acquires) > MAX_COUNT) {
                    throw new Error("Maximum lock count exceeded");
                }
                // 当前线程是已经获取写锁的线程, 重入
                setState(c + acquires);
                return true;
            }
            // 第一次竞争获取写锁
            if (writerShouldBlock() || !compareAndSetState(c, c + acquires)) {
                return false;
            }
            setExclusivgeOwnerThread(current);
            return true;
        }
        
      • 读锁的获取:

        protected final int tryAcquireShared(int unused) {
        	for(;;) {
                int c = getState();
                int nextc = c + (1 << 16);
                if (nextc < c) {
                    throw new Error("Maximum lock count exceeded");
                }
                if (exclusiveCount(c) != 0 && owner != Thread.currentThread()) {
                    return -1;
                }
                // 当前线程获取了写锁或者写锁未被获取, 则成功获取读锁
                if (compareAndSetState(cm nextc)) {
                    return 1;
                }
            }
        }
        
  • 锁降级: 把持住当前拥有的写锁, 在获取到读锁, 随后释放写锁的过程

    public void processData() {
        readLock.lock();
        if (!update) {
            readLock.unlock();
            // 获取写锁
            writeLock.lock();
            try {
                if (!update) {
                    // 准备数据
                    update = true;
                }
                readLock.lock();
            } finally {
                writeLock.unlock();
            }
            // 写锁降级为读锁
        }
        try {
            // 使用数据
        } finally {
            readLock.unlock();
        }
    }
    
  • Condition 实现原理

    • 等待队列: 线程调用 Condition#await() 方法导致该线程释放锁, 进入等待队列(从队列角度看是同步队列的头结点进入了等待队列中).
    • 并发包中的 Lock 拥有一个同步队列和多个等待队列(因为 Condition 可能有多个)
    • 调用 Condition#signal() 方法 将会唤醒在等待队列中等待时间最长的结点, 在唤醒结点之前, 会将结点移动到同步队列中.
    • await() 方法中醒来的线程已经成功获取了锁.
  • 阻塞队列实现原理

    • ArrayBlockingQueue: 由数组结构组成的有界阻塞队列
    • LinkedBlockingQueue: 由链表结构组成的有界阻塞队列
    • PriorityBlockingQueue: 一个支持优先级排序的无界阻塞队列
    • DelayQueue: 支持延时获取元素的无界阻塞队列, 队列中的元素必须实现 Delayed 接口, 在创建元素时可以指定多久才能从当前队列获取元素.
      • 缓存系统的设计: 可以用 DelayQueue 保存缓存元素, 使用线程循环查询 DelayQueue, 一旦能获取元素时, 表示缓存有效期到了.
      • 定时任务调度: 使用 DelayQueue 保存当天将会执行的任务和执行时间, 一旦从 DelayQueue 中获取到任务就开始执行.
    public ArrayBlockingQueue(int capacity, boolean fair) {
        if (capacity <= 0)
            throw new IllegalArgumentException();
        this.items = new Object[capacity];
        lock = new ReentrantLock(fair);
        notEmpty = lock.newCondition();
        notFull =  lock.newCondition();
    }
    
    public void put(E e) throws InterruptedException {
        checkNotNull(e);
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == items.length)
                // 队列满等待
                notFull.await();
            enqueue(e);
        } finally {
            lock.unlock();
        }
    }
    
    private void enqueue(E x) {
        // assert lock.getHoldCount() == 1;
        // assert items[putIndex] == null;
        final Object[] items = this.items;
        items[putIndex] = x;
        if (++putIndex == items.length)
            putIndex = 0;
        count++;
        // 存放了数据, 通知等待的 take() 方法可以取数据了
        notEmpty.signal();
    }
    
    public E take() throws InterruptedException {
        final ReentrantLock lock = this.lock;
        lock.lockInterruptibly();
        try {
            while (count == 0)
                /// 队列空等待
                notEmpty.await();
            return dequeue();
        } finally {
            lock.unlock();
        }
    }
    
    private E dequeue() {
        // assert lock.getHoldCount() == 1;
        // assert items[takeIndex] != null;
        final Object[] items = this.items;
        @SuppressWarnings("unchecked")
        E x = (E) items[takeIndex];
        items[takeIndex] = null;
        if (++takeIndex == items.length)
            takeIndex = 0;
        count--;
        if (itrs != null)
            itrs.elementDequeued();
        // 取走了元素, 通知等待的 put() 方法可以存放数据了
        notFull.signal();
        return x;
    }
    
  • Fork/Join 实现原理

    任务分割出的子任务会添加到当前工作线程所维护的双端队列中, 进入队列的头部. 当一个工作线程的队列暂时没有任务时, 它会随机从其它工作线程的队列的尾部获取一个任务.

    • ForkJoin 关键是在于它需要实现 compute() 方法, 在该方法中需要对任务进行判断, 如果任务足够小则直接执行; 反之则对任务进行拆分.

    • ForkJoinWorkerThread: 内部有所属的 ForkJoinPool 和 ForkJoinPool.WorkQueue; 在构造方法中将自己注册到 ForkJoinPool 中

      this.pool = pool;
      this.workQueue = pool.registerWorker(this);
      // 返回一个工作队列, 属于该线程
      WorkQueue w = new WorkQueue(this, wt);
      // 将该工作队列保存到 ForkJoinPool 的队列数组里面
      ws[i] = w;
      
    • ForkJoinTask#fork()

      Thread t;
      if ((t = Thread.currentThread()) instanceof ForkJoinWorkerThread)
          // 后续调用
      	((ForkJoinWorkerThread)t).workQueue.push(this);
      else
          // 第一次调用
      	ForkJoinPool.common.externalPush(this);
      return this;
      
posted @ 2020-03-05 19:32  javadaydayup  阅读(220)  评论(0编辑  收藏  举报