Throwable资料

本文主要介绍Throwable资料 方法和在新技术下所面对的“挑战”,方便大家深入理解Throwable资料 过程。本文也将分享Throwable资料 所遇到的问题和应对策略,怎么解决怎么做的问题。
通过深入本文可以理解代码原理,进行代码文档的下载,也可以查看相应 Demo 部署效果。

前提

并发编程大师Doug Lea在编写JUCjava.util.concurrent)包的时候引入了java.util.concurrent.locks.AbstractQueuedSynchronizer,其实是Abstract Queued Synchronizer,也就是"基于队列实现的抽象同步器",一般我们称之为AQS。其实Doug Lea大神编写AQS是有严谨的理论基础的,他的个人博客上有一篇论文《The java.util.concurrent Synchronizer Framewor》,可以在互联网找到相应的译文《JUC同步器框架》,如果想要深入研究AQS必须要理解一下该论文的内容,然后结合论文内容详细分析一下AQS的源码实现。本文在阅读AQS源码的时候选用的JDK版本是JDK11

出于写作习惯,下文会把AbstractQueuedSynchronizer称为AQS、JUC同步器框或者同步器框架。

AQS的主要功能

AQSJUC包中用于构建锁或者其他同步组件(信号量、事件等)的基础框架类。AQS从它的实现上看主要提供了下面的功能:

  • 同步状态的原子性管理。
  • 线程的阻塞和解除阻塞。
  • 提供阻塞线程的存储队列。

基于这三大功能,衍生出下面的附加功能:

  • 通过中断实现的任务取消,此功能基于线程中断实现。
  • 可选的超时设置,也就是调用者可以选择放弃等待任务执行完毕直接返回。
  • 定义了Condition接口,用于支持管程形式的await/signal/signalAll操作,代替了Object类基于JNI提供的wait/notify/notifyAll

AQS还根据同步状态的不同管理方式区分为两种不同的实现:独占状态的同步器共享状态的同步器

同步器框架基本原理

《The java.util.concurrent Synchronizer Framework》一文中其实有提及到同步器框架的伪代码:

// acquire操作如下: while (synchronization state does not allow acquire) {     enqueue current thread if not already queued;     possibly block current thread; } dequeue current thread if it was queued;  //release操作如下: update synchronization state; if (state may permit a blocked thread to acquire){     unblock one or more queued threads; } 

撇脚翻译一下:

// acquire操作如下: while(同步状态申请获取失败){     if(当前线程未进入等待队列){         当前线程放入等待队列;     }     尝试阻塞当前线程; } 当前线程移出等待队列  //release操作如下: 更新同步状态 if(同步状态足够允许一个阻塞的线程申请获取){     解除一个或者多个等待队列中的线程的阻塞状态; } 

为了实现上述操作,需要下面三个基本环节的相互协作:

  • 同步状态的原子性管理。
  • 等待队列的管理。
  • 线程的阻塞与解除阻塞。

其实基本原理很简单,但是为了应对复杂的并发场景和并发场景下程序执行的正确性,同步器框架在上面的acquire操作和release操作中使用了大量的死循环和CAS等操作,再加上Doug Lea喜欢使用单行复杂的条件判断代码,如一个if条件语句会包含大量操作AQS很多时候会让人感觉实现逻辑过于复杂。

同步状态管理

AQS内部内部定义了一个32位整型的state变量用于保存同步状态:

/**  * The synchronization state.(同步状态值)  */ private volatile int state;  // 获取state protected final int getState() {     return state; }  // 直接覆盖设置state protected final void setState(int newState) {     state = newState; }  // CAS设置state protected final boolean compareAndSetState(int expect, int update) {     return STATE.compareAndSet(this, expect, update); } 

同步状态state在不同的实现中可以有不同的作用或者表示意义,这里其实不能单纯把它理解为中文意义上的"状态",它可以代表资源数、锁状态等等,下文遇到具体的场景我们再分析它表示的意义。

CLH队列与变体

CLH锁即Craig, Landin, and Hagersten (CLH) locks,因为它底层是基于队列实现,一般也称为CLH队列锁。CLH锁也是一种基于链表的可扩展、高性能、公平的自旋锁,申请线程仅仅在本地变量上自旋,它不断轮询前驱的状态,假设发现前驱释放了锁就结束自旋。从实现上看,CLH锁是一种自旋锁,能确保无饥饿性,提供先来先服务的公平性。先看简单的CLH锁的一个简单实现:

public class CLHLock implements Lock {      AtomicReference<QueueNode> tail = new AtomicReference<>(new QueueNode());      ThreadLocal<QueueNode> pred;     ThreadLocal<QueueNode> current;      public CLHLock() {         current = ThreadLocal.withInitial(QueueNode::new);         pred = ThreadLocal.withInitial(() -> null);     }      @Override     public void lock() {         QueueNode node = current.get();         node.locked = true;         QueueNode pred = tail.getAndSet(node);         this.pred.set(pred);         while (pred.locked) {         }     }      @Override     public void unlock() {         QueueNode node = current.get();         node.locked = false;         current.set(this.pred.get());     }      static class QueueNode {          boolean locked;     }      // 忽略其他接口方法的实现 }	 

上面是一个简单的CLH队列锁的实现,内部类QueueNode只使用了一个简单的布尔值locked属性记录了每个线程的状态,如果该属性为true,则相应的线程要么已经获取到锁,要么正在等待锁,如果该属性为false,则相应的线程已经释放了锁。新来的想要获取锁的线程必须对tail属性调用getAndSet()方法,使得自身成为队列的尾部,同时得到一个指向前驱节点的引用pred,最后线程所在节点在其前驱节点的locked属性上自旋,直到前驱节点释放锁。上面的实现是无法运行的,因为一旦自旋就会进入死循环导致CPU飙升,可以尝试使用下文将要提到的LockSupport进行改造。

CLH队列锁本质是使用队列(实际上是单向链表)存放等待获取锁的线程,等待的线程总是在其所在节点的前驱节点的状态上自旋,直到前驱节点释放资源。从实际来看,过度自旋带来的CPU性能损耗比较大,并不是理想的线程等待队列的实现

Throwable

基于原始的CLH队列锁中提供的等待队列的基本原理,AQS实现一种了CLH锁队列的变体(Variant)AQS类的protected修饰的构造函数里面有一大段注释用于说明AQS实现的等待队列的细节事项,这里列举几点重要的:

  • AQS实现的等待队列没有直接使用CLH锁队列,但是参考了其设计思路,等待节点会保存前驱节点中线程的信息,内部也会维护一个控制线程阻塞的状态值。
  • 每个节点都设计为一个持有单独的等待线程并且"带有具体的通知方式"的监视器,这里所谓通知方式就是自定义唤醒阻塞线程的方式而已。
  • 一个线程是等待队列中的第一个等待节点的持有线程会尝试获取锁,但是并不意味着它一定能够获取锁成功(这里的意思是存在公平和非公平的实现),获取失败就要重新等待。
  • 等待队列中的节点通过prev属性连接前驱节点,通过next属性连接后继节点,简单来说,就是双向链表的设计
  • CLH队列本应该需要一个虚拟的头节点,但是在AQS中没有直接提供虚拟的头节点,而是延迟到第一次竞争出现的时候懒创建虚拟的头节点(其实也会创建尾节点,初始化时头尾节点是同一个节点)。
  • Condition(条件)等待队列中的阻塞线程使用的是相同的Node结构,但是提供了另一个链表用来存放,Condition等待队列的实现比非Condition等待队列复杂。

线程阻塞与唤醒

线程的阻塞和唤醒在JDK1.5之前,一般只能依赖于Object类提供的wait()notify()notifyAll()方法,它们都是JNI方法,由JVM提供实现,并且它们必须运行在获取监视器锁的代码块内(synchronized代码块中),这个局限性先不谈性能上的问题,代码的简洁性和灵活性是比较低的。JDK1.5引入了LockSupport类,底层是基于Unsafe类的park()unpark()方法,提供了线程阻塞和唤醒的功能,它的机制有点像只有一个允许使用资源的信号量java.util.concurrent.Semaphore,也就是一个线程只能通过park()方法阻塞一次,只能调用unpark()方法解除调用阻塞一次,线程就会唤醒(多次调用unpark()方法也只会唤醒一次),可以想象是内部维护了一个0-1的计数器。

LockSupport类如果使用得好,可以提供更灵活的编码方式,这里举个简单的使用例子:

public class LockSupportMain implements Runnable {      private static final DateTimeFormatter FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");      private Thread thread;      private void setThread(Thread thread) {         this.thread = thread;     }      public static void main(String[] args) throws Exception {         LockSupportMain main = new LockSupportMain();         Thread thread = new Thread(main, "LockSupportMain");         main.setThread(thread);         thread.start();         Thread.sleep(2000);         main.unpark();         Thread.sleep(2000);     }      @Override     public void run() {         System.out.println(String.format("%s-步入run方法,线程名称:%s", FORMATTER.format(LocalDateTime.now()),                 Thread.currentThread().getName()));         LockSupport.park();         System.out.println(String.format("%s-解除阻塞,线程继续执行,线程名称:%s", FORMATTER.format(LocalDateTime.now()),                 Thread.currentThread().getName()));     }      private void unpark() {         LockSupport.unpark(thread);     } } // 某个时刻的执行结果如下: 2019-02-25 00:39:57.780-步入run方法,线程名称:LockSupportMain 2019-02-25 00:39:59.767-解除阻塞,线程继续执行,线程名称:LockSupportMain 

LockSupportpark()方法也有带超时的变体版本方法,遇到带超时期限阻塞等待场景下不妨可以使用LockSupport#parkNanos()

独占线程的保存

AbstractOwnableSynchronizerAQS的父类,一个同步器框架有可能在一个时刻被某一个线程独占,AbstractOwnableSynchronizer就是为所有的同步器实现和锁相关实现提供了基础的保存、获取和设置独占线程的功能,这个类的源码很简单:

public abstract class AbstractOwnableSynchronizer     implements java.io.Serializable {      private static final long serialVersionUID = 3737899427754241961L;      protected AbstractOwnableSynchronizer() { }          // 当前独占线程的瞬时实例 - 提供Getter和Setter方法     private transient Thread exclusiveOwnerThread;      protected final void setExclusiveOwnerThread(Thread thread) {         exclusiveOwnerThread = thread;     }      protected final Thread getExclusiveOwnerThread() {         return exclusiveOwnerThread;     } } 

它就提供了一个保存独占线程的变量对应的SetterGetter方法,方法都是final修饰的,子类只能使用不能覆盖。

CLH队列变体的实现

这里先重点分析一下AQS中等待队列的节点AQS的静态内部类Node的源码:

static final class Node {    // 标记一个节点处于共享模式下的等待    static final Node SHARED = new Node();    // 标记一个节点处于独占模式下的等待    static final Node EXCLUSIVE = null;    // 取消状态    static final int CANCELLED =  1;    // 唤醒状态    static final int SIGNAL    = -1;    // 条件等待状态    static final int CONDITION = -2;    // 传播状态    static final int PROPAGATE = -3;    // 等待状态,初始值为0,其他可选值是上面的4个值    volatile int waitStatus;    // 当前节点前驱节点的引用    volatile Node prev;    // 当前节点后继节点的引用    volatile Node next;    // 当前节点持有的线程,可能是阻塞中等待唤醒的线程    volatile Thread thread;    // 下一个等待节点    Node nextWaiter;    // 当前操作的节点是否处于共享模式    final boolean isShared() {       return nextWaiter == SHARED;    }    // 获取当前节点的前驱节点,确保前驱节点必须存在,否则抛出NPE      final Node predecessor() {         Node p = prev;         if (p == null)             throw new NullPointerException();         else             return p;     }          // 空节点,主要是首次创建队列的时候创建的头和尾节点使用     Node() {}      // 设置下一个等待节点,设置持有线程为当前线程     Node(Node nextWaiter) {         this.nextWaiter = nextWaiter;         THREAD.set(this, Thread.currentThread());     }      // 设置waitStatus,设置持有线程为当前线程     Node(int waitStatus) {         WAITSTATUS.set(this, waitStatus);         THREAD.set(this, Thread.currentThread());     }      // CAS更新waitStatus       final boolean compareAndSetWaitStatus(int expect, int update) {         return WAITSTATUS.compareAndSet(this, expect, update);     }     // CAS设置后继节点     final boolean compareAndSetNext(Node expect, Node update) {         return NEXT.compareAndSet(this, expect, update);     }     // 设置前驱节点     final void setPrevRelaxed(Node p) {         PREV.set(this, p);     }      // 下面是变量句柄的实现,在VarHandle出现之前使用的是Unsafe,其实底层还是照样使用Unsafe     private static final VarHandle NEXT;     private static final VarHandle PREV;     private static final VarHandle THREAD;     private static final VarHandle WAITSTATUS;     static {         try {             MethodHandles.Lookup l = MethodHandles.lookup();             NEXT = l.findVarHandle(Node.class, "next", Node.class);             PREV = l.findVarHandle(Node.class, "prev", Node.class);             THREAD = l.findVarHandle(Node.class, "thread", Thread.class);             WAITSTATUS = l.findVarHandle(Node.class, "waitStatus", int.class);         } catch (ReflectiveOperationException e) {             throw new ExceptionInInitializerError(e);         }     }	   }	 

其中,变量句柄(VarHandle)是JDK9引入的新特性,其实底层依赖的还是Unsafe的方法,笔者认为可以简单理解它为Unsafe的门面类,而定义的方法基本都是面向变量属性的操作。这里需要
Throwable资料部分资料来自网络,侵权毕设源码联系删除

区块链毕设网(www.qklbishe.com)全网最靠谱的原创区块链毕设代做网站
部分资料来自网络,侵权联系删除!
资源收费仅为搬运整理打赏费用,用户自愿支付 !
qklbishe.com区块链毕设代做网专注|以太坊fabric-计算机|java|毕业设计|代做平台 » Throwable资料

提供最优质的资源集合

立即查看 了解详情