diff --git a/week_05/51/DelayQueue_51.md b/week_05/51/DelayQueue_51.md new file mode 100644 index 0000000000000000000000000000000000000000..6dabe353ed3922be25ab6316bb22134f89a71a50 --- /dev/null +++ b/week_05/51/DelayQueue_51.md @@ -0,0 +1,286 @@ +## 问题 + +(1)DelayQueue是阻塞队列吗? + +(2)DelayQueue的实现方式? + +(3)DelayQueue主要用于什么场景? + +## 简介 + +DelayQueue是java并发包下的延时阻塞队列,常用于实现定时任务。 + +## 继承体系 + +![qrcode](https://gitee.com/alan-tang-tt/yuan/raw/master/%E6%AD%BB%E7%A3%95%20java%E9%9B%86%E5%90%88%E7%B3%BB%E5%88%97/resource/DelayQueue.png) + +从继承体系可以看到,DelayQueue实现了BlockingQueue,所以它是一个阻塞队列。 + +另外,DelayQueue还组合了一个叫做Delayed的接口,DelayQueue中存储的所有元素必须实现Delayed接口。 + +那么,Delayed是什么呢? + +``` +public interface Delayed extends Comparable { + + long getDelay(TimeUnit unit); +} +``` + +Delayed是一个继承自Comparable的接口,并且定义了一个getDelay()方法,用于表示还有多少时间到期,到期了应返回小于等于0的数值。 + +## 源码分析 + +### 主要属性 + +``` +// 用于控制并发的锁 +private final transient ReentrantLock lock = new ReentrantLock(); +// 优先级队列 +private final PriorityQueue q = new PriorityQueue(); +// 用于标记当前是否有线程在排队(仅用于取元素时) +private Thread leader = null; +// 条件,用于表示现在是否有可取的元素 +private final Condition available = lock.newCondition(); +``` + +从属性我们可以知道,延时队列主要使用优先级队列来实现,并辅以重入锁和条件来控制并发安全。 + +因为优先级队列是无界的,所以这里只需要一个条件就可以了。 + +还记得优先级队列吗?点击链接直达【[死磕 java集合之PriorityQueue源码分析](https://mp.weixin.qq.com/s/kGKS7WXWbf-ME1_Hr3Fpgw)】 + +### 主要构造方法 + +``` +public DelayQueue() {} + +public DelayQueue(Collection c) { + this.addAll(c); +} +``` + +构造方法比较简单,一个默认构造方法,一个初始化添加集合c中所有元素的构造方法。 + +### 入队 + +因为DelayQueue是阻塞队列,且优先级队列是无界的,所以入队不会阻塞不会超时,因此它的四个入队方法是一样的。 + +``` +public boolean add(E e) { + return offer(e); +} + +public void put(E e) { + offer(e); +} + +public boolean offer(E e, long timeout, TimeUnit unit) { + return offer(e); +} + +public boolean offer(E e) { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + q.offer(e); + if (q.peek() == e) { + leader = null; + available.signal(); + } + return true; + } finally { + lock.unlock(); + } +} +``` + +入队方法比较简单: + +(1)加锁; + +(2)添加元素到优先级队列中; + +(3)如果添加的元素是堆顶元素,就把leader置为空,并唤醒等待在条件available上的线程; + +(4)解锁; + +### 出队 + +因为DelayQueue是阻塞队列,所以它的出队有四个不同的方法,有抛出异常的,有阻塞的,有不阻塞的,有超时的。 + +我们这里主要分析两个,poll()和take()方法。 + +``` +public E poll() { + final ReentrantLock lock = this.lock; + lock.lock(); + try { + E first = q.peek(); + if (first == null || first.getDelay(NANOSECONDS) > 0) + return null; + else + return q.poll(); + } finally { + lock.unlock(); + } +} +``` + +poll()方法比较简单: + +(1)加锁; + +(2)检查第一个元素,如果为空或者还没到期,就返回null; + +(3)如果第一个元素到期了就调用优先级队列的poll()弹出第一个元素; + +(4)解锁。 + +``` +public E take() throws InterruptedException { + final ReentrantLock lock = this.lock; + lock.lockInterruptibly(); + try { + for (;;) { + // 堆顶元素 + E first = q.peek(); + // 如果堆顶元素为空,说明队列中还没有元素,直接阻塞等待 + if (first == null) + available.await(); + else { + // 堆顶元素的到期时间 + long delay = first.getDelay(NANOSECONDS); + // 如果小于0说明已到期,直接调用poll()方法弹出堆顶元素 + if (delay <= 0) + return q.poll(); + + // 如果delay大于0 ,则下面要阻塞了 + + // 将first置为空方便gc,因为有可能其它元素弹出了这个元素 + // 这里还持有着引用不会被清理 + first = null; // don't retain ref while waiting + // 如果前面有其它线程在等待,直接进入等待 + if (leader != null) + available.await(); + else { + // 如果leader为null,把当前线程赋值给它 + Thread thisThread = Thread.currentThread(); + leader = thisThread; + try { + // 等待delay时间后自动醒过来 + // 醒过来后把leader置空并重新进入循环判断堆顶元素是否到期 + // 这里即使醒过来后也不一定能获取到元素 + // 因为有可能其它线程先一步获取了锁并弹出了堆顶元素 + // 条件锁的唤醒分成两步,先从Condition的队列里出队 + // 再入队到AQS的队列中,当其它线程调用LockSupport.unpark(t)的时候才会真正唤醒 + // 关于AQS我们后面会讲的^^ + available.awaitNanos(delay); + } finally { + // 如果leader还是当前线程就把它置为空,让其它线程有机会获取元素 + if (leader == thisThread) + leader = null; + } + } + } + } + } finally { + // 成功出队后,如果leader为空且堆顶还有元素,就唤醒下一个等待的线程 + if (leader == null && q.peek() != null) + // signal()只是把等待的线程放到AQS的队列里面,并不是真正的唤醒 + available.signal(); + // 解锁,这才是真正的唤醒 + lock.unlock(); + } +} +``` + +take()方法稍微要复杂一些: + +(1)加锁; + +(2)判断堆顶元素是否为空,为空的话直接阻塞等待; + +(3)判断堆顶元素是否到期,到期了直接调用优先级队列的poll()弹出元素; + +(4)没到期,再判断前面是否有其它线程在等待,有则直接等待; + +(5)前面没有其它线程在等待,则把自己当作第一个线程等待delay时间后唤醒,再尝试获取元素; + +(6)获取到元素之后再唤醒下一个等待的线程; + +(7)解锁; + +## 使用方法 + +说了那么多,是不是还是不知道怎么用呢?那怎么能行,请看下面的案例: + +``` +public class DelayQueueTest { + public static void main(String[] args) { + DelayQueue queue = new DelayQueue<>(); + + long now = System.currentTimeMillis(); + + // 启动一个线程从队列中取元素 + new Thread(()->{ + while (true) { + try { + // 将依次打印1000,2000,5000,7000,8000 + System.out.println(queue.take().deadline - now); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + }).start(); + + // 添加5个元素到队列中 + queue.add(new Message(now + 5000)); + queue.add(new Message(now + 8000)); + queue.add(new Message(now + 2000)); + queue.add(new Message(now + 1000)); + queue.add(new Message(now + 7000)); + } +} + +class Message implements Delayed { + long deadline; + + public Message(long deadline) { + this.deadline = deadline; + } + + @Override + public long getDelay(TimeUnit unit) { + return deadline - System.currentTimeMillis(); + } + + @Override + public int compareTo(Delayed o) { + return (int) (getDelay(TimeUnit.MILLISECONDS) - o.getDelay(TimeUnit.MILLISECONDS)); + } + + @Override + public String toString() { + return String.valueOf(deadline); + } +} +``` + +是不是很简单,越早到期的元素越先出队。 + +## 总结 + +(1)DelayQueue是阻塞队列; + +(2)DelayQueue内部存储结构使用优先级队列; + +(3)DelayQueue使用重入锁和条件来控制并发安全; + +(4)DelayQueue常用于定时任务; + +## 彩蛋 + +java中的线程池实现定时任务是直接用的DelayQueue吗? + +当然不是,ScheduledThreadPoolExecutor中使用的是它自己定义的内部类DelayedWorkQueue,其实里面的实现逻辑基本都是一样的,只不过DelayedWorkQueue里面没有使用现成的PriorityQueue,而是使用数组又实现了一遍优先级队列,本质上没有什么区别。 \ No newline at end of file diff --git "a/week_05/51/DelayQueue\344\275\277\347\224\250\345\234\272\346\231\257\345\222\214\346\235\241\344\273\266.md" "b/week_05/51/DelayQueue\344\275\277\347\224\250\345\234\272\346\231\257\345\222\214\346\235\241\344\273\266.md" new file mode 100644 index 0000000000000000000000000000000000000000..877c3f0e118ecfad1abfec44bdb85491b5c3cc74 --- /dev/null +++ "b/week_05/51/DelayQueue\344\275\277\347\224\250\345\234\272\346\231\257\345\222\214\346\235\241\344\273\266.md" @@ -0,0 +1,22 @@ +#### 使用场景: + +- 缓存系统设计:使用DelayQueue保存缓存元素的有效期,用一个线程循环查询DelayQueue,一旦从DelayQueue中取出元素,就表示有元素到期。 +- 定时任务调度:使用DelayQueue保存当天要执行的任务和执行的时间,一旦从DelayQueue中获取到任务,就开始执行,比如Timer,就是基于DelayQueue实现的。 + +#### 使用条件: + +- 存放DelayQueue的元素,必须继承Delay接口,Delay接口使对象成为延迟对象。 +- 该接口强制实现两个方法: + 1.**CompareTo(Delayed o)**:用于比较延时,队列里元素的排序依据,这个是Comparable接口的方法,因为Delay实现了Comparable接口,所以需要实现。 + 2.**getDelay(TimeUnit unit)**:这个接口返回到激活日期的--剩余时间,时间单位由单位参数指定。 +- 此队列不允许使用null元素。 + + + +### BlockingQueue中take、offer、put、add + +- offer:插入到队列,成功返回true,如果当前没有可用空间,返回false。 +- add:入队,如果没有可用空间,抛出异常,IllegalStateException。 +- put:插入队列,等待可用空间,阻塞,直到能够有空间插入元素。 +- take:获取并移除队列头部,在元素变的可用之前一直等待。 + diff --git a/week_05/51/Executors_51.md b/week_05/51/Executors_51.md new file mode 100644 index 0000000000000000000000000000000000000000..0cc0c36fa5147d9644c3ebfa8c87bbd3f12007c8 --- /dev/null +++ b/week_05/51/Executors_51.md @@ -0,0 +1,168 @@ +Executors为快速创建线程池的工具类,其可以创建基于ThreadPoolExecutor、ScheduledThreadPoolExecutor、ForkJoinPool及包装的不可配置的线程池。 + +# 1、newFixedThreadPool()创建固定数量的普通线程池 + +**有如下实现:** + + + +```cpp +//nThread为线程池中线程的数量 +public static ExecutorService newFixedThreadPool(int nThreads) { + //通过ThreadPoolExecutor实现类,创建核心线程数及最大线程数都为nThreads的线程池 + //空闲超时时间为0,且由于核心线程数和最大线程数一样,核心线程不会空闲超时后被回收 + //LinkedBlockingQueue为任务队列实现 + return new ThreadPoolExecutor(nThreads, nThreads, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue()); +} + +//nThread为线程池中线程的数量,线程创建的工厂类实现类为threadFactory +public static ExecutorService newFixedThreadPool(int nThreads, ThreadFactory threadFactory) { + return new ThreadPoolExecutor(nThreads, nThreads, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(), + threadFactory); +} +``` + +# 2、newWorkStealingPool()创建可任务窃取的线程池 + +**有如下实现:** + + + +```csharp +//parallelism并行任务数 +public static ExecutorService newWorkStealingPool(int parallelism) { + //基于ForkJoinPool创建线程池; + //使用默认线程创建工厂类 + //执行任务异常处理类为null + //任务队列模型为FIFO,true为FIFI,false为FILO + return new ForkJoinPool + (parallelism, + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, true); +} + +public static ExecutorService newWorkStealingPool() { + //并行数为处理器的并行数 + return new ForkJoinPool + (Runtime.getRuntime().availableProcessors(), + ForkJoinPool.defaultForkJoinWorkerThreadFactory, + null, true); +} +``` + +# 3、newSingleThreadExecutor()创建单个普通线程池 + +**有如下实现:** + + + +```cpp +//创建单个线程的线程池 +public static ExecutorService newSingleThreadExecutor() { + //用FinalizableDelegatedExecutorService进行封装,其会在 + //finalize()方法中调用线程池的shutdown()方法,对线程池进行关闭 + return new FinalizableDelegatedExecutorService + (new ThreadPoolExecutor(1, 1, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue())); +} + + +public static ExecutorService newSingleThreadExecutor(ThreadFactory threadFactory) { + return new FinalizableDelegatedExecutorService + (new ThreadPoolExecutor(1, 1, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue(), + threadFactory)); +} +``` + +# 4、newCachedThreadPool()创建线程数量不限的普通线程池 + +**有如下实现:** + + + +```cpp +//创建缓存的线程池,即线程池数量不限 +public static ExecutorService newCachedThreadPool() { + //核心线程数量为0,最大线程数量为Integer.MAX_VALUE + //阻塞队列为SynchronousQueue + return new ThreadPoolExecutor(0, Integer.MAX_VALUE, + 60L, TimeUnit.SECONDS, + new SynchronousQueue()); +} + +public static ExecutorService newCachedThreadPool(ThreadFactory threadFactory) { + return new ThreadPoolExecutor(0, Integer.MAX_VALUE, + 60L, TimeUnit.SECONDS, + new SynchronousQueue(), + threadFactory); +} +``` + +# 5、newSingleThreadScheduledExecutor()创建单个基于时间调度的线程池 + +**有如下实现:** + + + +```cpp +//创建单个可进行时间调度的线程池 +public static ScheduledExecutorService newSingleThreadScheduledExecutor() { + //利用DelegatedScheduledExecutorService对线程池进行包装 + //DelegatedScheduledExecutorService利用门面模式包装了ScheduledExecutorService + //使得用户无法修改ScheduledExecutorService中的一些参数 + return new DelegatedScheduledExecutorService + (new ScheduledThreadPoolExecutor(1)); +} + +public static ScheduledExecutorService newSingleThreadScheduledExecutor(ThreadFactory threadFactory) { + return new DelegatedScheduledExecutorService + (new ScheduledThreadPoolExecutor(1, threadFactory)); +} +``` + +# 6、newScheduledThreadPool()创建多个基于时间调度的线程池 + +**有如下实现:** + + + +```cpp +//创建固定线程池的可基于时间调度的线程池,corePoolSize为核心线程池测数量 +public static ScheduledExecutorService newScheduledThreadPool(int corePoolSize) { + return new ScheduledThreadPoolExecutor(corePoolSize); +} + +public static ScheduledExecutorService newScheduledThreadPool( + int corePoolSize, ThreadFactory threadFactory) { + return new ScheduledThreadPoolExecutor(corePoolSize, threadFactory); +} +``` + +# 7、包装配置不可更改的线程池 + +**有如下实现:** + + + +```csharp +//对ExecutorService类型线程池进行包装,使用户无法对一些参数进行修改 +public static ExecutorService unconfigurableExecutorService(ExecutorService executor) { + if (executor == null) + throw new NullPointerException(); + return new DelegatedExecutorService(executor); +} + +//对ScheduledExecutorService类型线程池进行包装,使用户无法对一些参数进行修改 +public static ScheduledExecutorService unconfigurableScheduledExecutorService(ScheduledExecutorService executor) { + if (executor == null) + throw new NullPointerException(); + return new DelegatedScheduledExecutorService(executor); +} +``` \ No newline at end of file diff --git "a/week_05/51/Java\347\272\277\347\250\213\346\250\241\345\236\213_51.md" "b/week_05/51/Java\347\272\277\347\250\213\346\250\241\345\236\213_51.md" new file mode 100644 index 0000000000000000000000000000000000000000..8a57166e6b2ea7fa5629c7cb159d908158f5839f --- /dev/null +++ "b/week_05/51/Java\347\272\277\347\250\213\346\250\241\345\236\213_51.md" @@ -0,0 +1,104 @@ +# Java线程模型 + +> 本文将从线程类型、线程通信、线程调度三个方面分析Java中的线程模型。 + +## 什么是线程? + +线程就是进程的切片。因为CPU速度太快,进程切换时cpu都要进行进程上下文的加载、保存操作。对CPU来说是巨大的性能浪费。所以引入线程的目的就是为了细化进程对CPU时间占用粒度,更加充分的利用CPU。 + +因为线程是对进程的切片,所以线程是共享进程资源的,同一进程里线程的切换不会影响进程的切换。 + +#### 与进程的区别 + +1. 进程是资源分配的最小单位,线程是调度的最小单位。 +2. 进程独占资源,线程共享进程资源。 + +## 线程的类型 + +### 内核线程(KLT) + +内核线程就是直接由操作系统内核支持的线程,这种线程由内核来完成线程切换,内核通过操纵调度器对线程进行调度,并负责将线程的任务映射到各个处理器上。每个内核线程可以视为内核的一个分身,这样操作系统就有能力同时处理多件事情,支持多线程的内核就叫做多线程内核。 + +### 轻量级进程(LWP) + +轻量级进程是由系统提供给用户的操作内核线程的接口的实现。即轻量级进程是内核线程的一个替身。 + +### 用户线程(UT) + +用户线程建立在用户空间的线程库上,系统内核不能感知线程存在的实现。用户线程的建立、同步、销毁和调度完全在用户态中完成,不需要内核的帮助。如果程序实现得当,这种线程不需要切换到内核态,因此操作可以是非常快速且低消耗的,也可以支持规模更大的线程数量,部分高性能数据库中的多线程就是由用户线程实现的。 + +## 线程模型 + +### 一对一模型 + +![img](https:////upload-images.jianshu.io/upload_images/18113429-66797c1687996174.png?imageMogr2/auto-orient/strip|imageView2/2/w/549/format/webp) + +一对一模型即轻量级进程的实现模式。一个LWP对应一个KLT。 + **优点**:每个LWP都是独立的调度单元,一个线程阻塞不影响其他线程。 + **缺点**:因为与KLT一对一。而KLT创建,调度需要一定内核资源,因此创建数量有限。 + +### 一对多模型 + +![img](https:////upload-images.jianshu.io/upload_images/18113429-d2a0cd0e813798ef.png?imageMogr2/auto-orient/strip|imageView2/2/w/419/format/webp) + +一对多模型及用户线程实现方式。一个进程对应多个UT。 + +**优点**:线程的管理在用户空间进行。比较高效。 + +**缺点**:需要用户自己考虑线程的调度相关问题,因为系统对UT无感知,所以一个线程阻塞会导致进程阻塞。 + +### 多对多模型 + +![img](https:////upload-images.jianshu.io/upload_images/18113429-9a96e5796dc55f2a.png?imageMogr2/auto-orient/strip|imageView2/2/w/550/format/webp) + +在一对多的基础上引入KLT。即一个进程管理多个KLT,KLT对应多个UT。 + 将 n 个UT映射到m个KLT上,要求 m <= n。 + +**优点**: 集合了前两种模型的优点,去掉了他们的缺点。 + **缺点**:全是优点。 + +## 线程的调度方式 + +### 抢占式调度 + +每个线程由系统分配CPU时间,线程本身无法控制使用多次CPU时间。好处是线程的执行时间是可控的,不会造成因为一个线程导致进程长时间阻塞问题。 + +### 协同式调度 + +线程自己控制CPU时间。并且当前线程执行完毕后需要通知系统切换另外一个线程。最主要的问题是线程的切换取决于线程本身,若线程存在Bug导致切换线程不成功则会一直阻塞。 + +## 线程间通信与同步 + +线程虽然可以独立的执行。但是总会有需要不同的线程互相配合的情况,这就涉及到线程的通信与同步。目前有两种方式。 + +### 共享内存 + +共享内存的并发模型里线程通过显示的同步即通过互斥实现对公共空间的读写,将需要共享的数据同步到公共的内存中,这样便实现了间接的通信。即共享内存是显示同步,隐式通信。 + +### 消息传递 + +消息传递模型即消息传递,线程间无公共内存,因为消息通信天然具有先后关系所以间接实现类数据的同步。所以消息传递模型是显示通信,隐式同步。 + +## Java中的线程 + +### 线程模型 + +目前Java默认使用的是一对一模型。即LWP方案。不过在支持多对多的平台上可以通过JVM参数控制使用多对多模型。 + +#### 相关参数 + +-XX:+UseLWPSynchronization(默认值) + -XX:+UseBoundThreads + +### 调度方式 + +Java采用抢占式调度。同时Java定义了10中线程优先级。可以在一定程度上调整线程的CPU时间。 + +### 通信机制 + +Java采用的是共享内存并发模型。具体可参考Java内存模型。 + +## 参考 + +1. <<深入理解Java虚拟机: JVM高级特性与最佳实践>> +2. <> \ No newline at end of file diff --git "a/week_05/51/ThreadLocal\346\272\220\347\240\201\350\247\243\346\236\220_51.md" "b/week_05/51/ThreadLocal\346\272\220\347\240\201\350\247\243\346\236\220_51.md" new file mode 100644 index 0000000000000000000000000000000000000000..fd68542bb54529298642667875859585651d1f6b --- /dev/null +++ "b/week_05/51/ThreadLocal\346\272\220\347\240\201\350\247\243\346\236\220_51.md" @@ -0,0 +1,898 @@ +**目录** + +- 一、ThreadLocal + - [1.1源码注释](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label0_0) + - [1.2 源码剖析](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label0_1) + - [散列算法-魔数0x61c88647](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label0_2) + - [set操作](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label0_3) + - [ get操作](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label0_4) + - [remove操作](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label0_5) + - [1.3 功能测试](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label0_6) + - [1.4 应用场景](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label0_7) +- 二、变量可继承的ThreadLocal==》InheritableThreadLocal + - [2.1 源码注释:](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label1_0) + - [2.2 源码剖析](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label1_1) + - [2.3 功能测试](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label1_2) + - [2.4 应用场景](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label1_3) +- [三、总结](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_label2) + + + +**正文** + +本文较深入的分析了ThreadLocal和InheritableThreadLocal,从4个方向去分析:源码注释、源码剖析、功能测试、应用场景。 + +[回到顶部](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_labelTop) + +## 一、ThreadLocal + +我们使用**ThreadLocal解决线程局部变量统一定义问题,**多线程数据不能共享。(InheritableThreadLocal特例除外)不能解决并发问题。解决了:**基于类级别的变量定义,每一个线程单独维护自己线程内的变量值(\**存、取、删的功能\**)** + +根据源码,画出原理图如下: + +![img](https://images2017.cnblogs.com/blog/584866/201712/584866-20171205182857659-1065584819.png) + +注意点: + +**1.ThreadLocal类封装了getMap()、Set()、Get()、Remove()4个核心方法。** + +**2.通过\**getMap()获取\**每个子线程Thread持有自己的ThreadLocalMap实例, 因此它们是不存在并发竞争的。可以理解为每个线程有自己的变量副本。 +** + +**3.ThreadLocalMap中Entry[]数组存储数据,初始化长度16,后续每次都是2倍扩容。主线程中定义了几个变量,Entry[]才有几个key。** + +**4.`Entry`的key是对ThreadLocal的弱引用,当抛弃掉ThreadLocal对象时,垃圾收集器会忽略这个key的引用而清理掉ThreadLocal对象, 防止了内存泄漏。** + + + +### 1.1源码注释 + +理解原理最好的方法是看源码注释: + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +``` +1 This class provides thread-local variables. These variables differ from their normal counterparts in that each thread that accesses one (via its get or set method) has its own, independently initialized copy of the variable. ThreadLocal instances are typically private static fields in classes that wish to associate state with a thread (e.g., a user ID or Transaction ID). +2 +3 For example, the class below generates unique identifiers local to each thread. A thread's id is assigned the first time it invokes ThreadId.get() and remains unchanged on subsequent calls. +``` + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +这个类提供线程局部变量。这些变量与正常的变量不同,每个线程访问一个(通过它的get或set方法)都有它自己的、独立初始化的变量副本。ThreadLocal实例通常是类中的私有静态字段,希望将状态与线程关联(例如,用户ID或事务ID)。 + +**注释中的示例代码:** + +下图ThreadId类会在每个线程中生成唯一标识符。线程的id在第一次调用threadid.get()时被分配,在随后的调用中保持不变。 + + ThreadId类利用AtomicInteger原子方法getAndIncrement,为每个线程创建一个threadId变量,例如第一个线程是1,第二个线程是2...,并提供一个类静态get方法用以获取当前线程ID。: + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +``` + 1 import java.util.concurrent.atomic.AtomicInteger; + 2 + 3 public class ThreadId { + 4 // Atomic integer containing the next thread ID to be assigned + 5 private static final AtomicInteger nextId = new AtomicInteger(0); + 6 + 7 // Thread local variable containing each thread's ID + 8 private static final ThreadLocal threadId = + 9 new ThreadLocal() { +10 @Override protected Integer initialValue() { +11 return nextId.getAndIncrement(); +12 } +13 }; +14 +15 // Returns the current thread's unique ID, assigning it if necessary +16 public static int get() { +17 return threadId.get(); +18 } +19 } +``` + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +如上图,有一个注意点是:**用户可以自定义initialValue()初始化方法,来初始化threadLocal的值。** + + + +### 1.2 源码剖析 + +我们来追踪一下ThreadLocal源码: + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +```java + 1 public T get() { + 2 Thread t = Thread.currentThread(); + 3 ThreadLocalMap map = getMap(t); + 4 if (map != null) { + 5 ThreadLocalMap.Entry e = map.getEntry(this); + 6 if (e != null) { + 7 @SuppressWarnings("unchecked") + 8 T result = (T)e.value; + 9 return result; +10 } +11 } +12 return setInitialValue(); +13 } +14 +21 private T setInitialValue() { +22 T value = initialValue(); +23 Thread t = Thread.currentThread(); +24 ThreadLocalMap map = getMap(t); +25 if (map != null) +26 map.set(this, value); +27 else +28 createMap(t, value); +29 return value; +30 } +31 +41 public void set(T value) { +42 Thread t = Thread.currentThread(); +43 ThreadLocalMap map = getMap(t); +44 if (map != null) +45 map.set(this, value); +46 else +47 createMap(t, value); +48 } +49 +61 public void remove() { +62 ThreadLocalMap m = getMap(Thread.currentThread()); +63 if (m != null) +64 m.remove(this); +65 } +66 +74 ThreadLocalMap getMap(Thread t) { +75 return t.threadLocals; +76 } +``` + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +看源码我们知道不管是set、get、remove操作的都是ThreadLocalMap,key=当前线程,value=线程局部变量缓存值。 + +上图getMap最终调用的Thread的成员变量 ThreadLocal.ThreadLocalMap threadLocals,如下图: + +![img](https://images2017.cnblogs.com/blog/584866/201712/584866-20171205112952409-991189955.png) + +ThreadLocalMap是ThreadLocal的一个内部类,源码注释: + +ThreadLocalMap是一个定制的哈希映射,仅适用于维护线程本地值。ThreadLocalMap类是包私有的,允许在Thread类中声明字段。为了帮助处理非常大且长时间的使用,哈希表entry使用了对键的弱引用。有助于GC回收。 + + + +### 散列算法-魔数`0x61c88647` + + ThreadLocal中定义了一个AtomicInteger,一个魔数0x61c88647,利用一定算法实现了元素的完美散列。 + +**源码中元素散列算法如下:** + +``` +1.求hashCode = i*HASH_INCREMENT+HASH_INCREMENT每次新增一个元素(threadLocal)进Entry[],自增0x61c88647 +2.元素散列位置(数组下标)= hashCode & (length-1), +``` + +![img](https://images2017.cnblogs.com/blog/584866/201712/584866-20171207092024003-1488472900.png) + +下面校验算法的散列性: + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +```java + 1 /** + 2 * + 3 * @ClassName:MagicHashCode + 4 * @Description:ThreadLocalMap使用“开放寻址法”中最简单的“线性探测法”解决散列冲突问题 + 5 * @author diandian.zhang + 6 * @date 2017年12月6日上午10:53:28 + 7 */ + 8 public class MagicHashCode { + 9 //ThreadLocal中定义的hash魔数 +10 private static final int HASH_INCREMENT = 0x61c88647; +11 +12 public static void main(String[] args) { +13 hashCode(16);//初始化16 +14 hashCode(32);//后续2倍扩容 +15 hashCode(64); +16 } +17 +18 /** +19 * +20 * @Description 寻找散列下标(对应数组小标) +21 * @param length table长度 +22 * @author diandian.zhang +23 * @date 2017年12月6日上午10:36:53 +24 * @since JDK1.8 +25 */ +26 private static void hashCode(Integer length){ +27 int hashCode = 0; +28 for(int i=0;i firstKey, Object firstValue) { +37 table = new Entry[INITIAL_CAPACITY];//初始化容量16 +38 int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); +39 table[i] = new Entry(firstKey, firstValue); +40 size = 1; +41 setThreshold(INITIAL_CAPACITY);//设置阈值 +42 } +43 //阈值设置为容量的*2/3,即负载因子为2/3,超过就进行再哈希 +44 private void setThreshold(int len) { +45 threshold = len * 2 / 3; +46 } +``` + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +总结get步骤: + +1)从当前线程中获取ThreadLocalMap,查询当前ThreadLocal变量实例对应的Entry,如果不为null,获取value,返回 + +2)如果map为null,即还没有初始化,走初始化方法 + + + +### remove操作 + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +``` + 1 public void remove() { + 2 ThreadLocalMap m = getMap(Thread.currentThread()); + 3 if (m != null) + 4 m.remove(this);//调用ThreadLocalMap删除变量 + 5 } + 6 + 7 private void remove(ThreadLocal key) { + 8 Entry[] tab = table; + 9 int len = tab.length; +10 int i = key.threadLocalHashCode & (len-1); +11 for (Entry e = tab[i]; +12 e != null; +13 e = tab[i = nextIndex(i, len)]) { +14 if (e.get() == key) { +15 e.clear();//调用Entry的clear方法 +16 expungeStaleEntry(i);//清除陈旧数据 +17 return; +18 } +19 } +20 } +``` + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +看一下Entry的clear方法,Entry ==extends==》 WeakReference>==extends==》 Reference,clear方法是抽象类Reference定义的方法。 + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +``` +1 static class Entry extends WeakReference> { +2 /** The value associated with this ThreadLocal. */ +3 Object value; +4 +5 Entry(ThreadLocal k, Object v) { +6 super(k); +7 value = v; +8 } +9 } +``` + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +``` +追一下clear方法如下:把弱引用的对象置null。有利于GC回收内存。关于引用,预留飞机票 +public void clear() { + this.referent = null; +} +``` + + + +### 1.3 功能测试 + +开启2个线程,每个个线程都使用类级别的threadLocal,往里面递增数字,i=0,时,set(0),i=1,2,3时 值+1, + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +``` + 1 /** + 2 * + 3 * @ClassName:MyThreadLocal + 4 * @Description:ThreadLocal线程本地变量 + 5 * @author diandian.zhang + 6 * @date 2017年12月4日上午9:40:52 + 7 */ + 8 public class MyThreadLocal{ + 9 //线程本地共享变量 +10 private static final ThreadLocal threadLocal = new ThreadLocal(){ +11 /** +12 * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值 +13 */ +14 @Override +15 protected Object initialValue() +16 { +17 System.out.println("[线程"+Thread.currentThread().getName()+"]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!"); +18 return null; +19 } +20 }; +21 +22 public static void main(String[] args){ +23 //1.开启任务1线程 +24 new Thread(new MyIntegerTask("IntegerTask1")).start(); +25 //2.中间休息3秒,用以测试数据差异 +26 try { +27 Thread.sleep(3000); +28 } catch (InterruptedException e) { +29 e.printStackTrace(); +30 } +31 //3.开启任务2线程 +32 new Thread(new MyIntegerTask("IntegerTask2")).start(); +33 } +34 +35 /** +36 * +37 * @ClassName:MyIntegerTask +38 * @Description:整形递增线程 +39 * @author diandian.zhang +40 * @date 2017年12月4日上午10:00:41 +41 */ +42 public static class MyIntegerTask implements Runnable{ +43 private String name; +44 +45 MyIntegerTask(String name) +46 { +47 this.name = name; +48 } +49 +50 @Override +51 public void run() +52 { +53 for(int i = 0; i < 5; i++) +54 { +55 // ThreadLocal.get方法获取线程变量 +56 if(null == MyThreadLocal.threadLocal.get()) +57 { +58 // ThreadLocal.set方法设置线程变量 +59 MyThreadLocal.threadLocal.set(0); +60 System.out.println("i="+i+"[线程" + name + "]当前线程不存在缓存,set 0"); +61 } +62 else +63 { +64 int num = (Integer)MyThreadLocal.threadLocal.get(); +65 MyThreadLocal.threadLocal.set(num + 1); +66 System.out.println("i="+i+"[线程" + name + "]往threadLocal中set: " + MyThreadLocal.threadLocal.get()); +67 //当i=3即循环4次时,移除当前线程key +68 if(i == 3) +69 { +70 System.out.println("i="+i+"[线程" + name + "],threadLocal移除当前线程" ); +71 MyThreadLocal.threadLocal.remove(); +72 } +73 } +74 try +75 { +76 Thread.sleep(1000); +77 } +78 catch (InterruptedException e) +79 { +80 e.printStackTrace(); +81 } +82 } +83 } +84 } +85 } +``` + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +运行结果如下: + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +``` +[线程Thread-0]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值! +i=0[线程IntegerTask1]当前线程不存在缓存,set 0 +i=1[线程IntegerTask1]往threadLocal中set: 1 +i=2[线程IntegerTask1]往threadLocal中set: 2 +[线程Thread-1]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值! +i=0[线程IntegerTask2]当前线程不存在缓存,set 0 +i=3[线程IntegerTask1]往threadLocal中set: 3 +i=3[线程IntegerTask1],threadLocal移除当前线程 +i=1[线程IntegerTask2]往threadLocal中set: 1 +[线程Thread-0]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值! +i=4[线程IntegerTask1]当前线程不存在缓存,set 0 +i=2[线程IntegerTask2]往threadLocal中set: 2 +i=3[线程IntegerTask2]往threadLocal中set: 3 +i=3[线程IntegerTask2],threadLocal移除当前线程 +[线程Thread-1]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值! +i=4[线程IntegerTask2]当前线程不存在缓存,set 0 +``` + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +结果验证: + +1.2个线程,2个threadLocal变量互不影响。 + +2.调用get方法时,对应ThreadLocalMap为null会调用initialValue()方法,初始化threadLocal的值。 + + + +### 1.4 应用场景 + +ThreadLocal的实际应用场景: + +1**)数据结构**:用Map来避免创建多个ThreadLocal变量的麻烦。只需根据map的key就可以获取想要的value + +private static final ThreadLocal> loginContext = new ThreadLocal<>(); + +2)**业务**:线程级别,维护session,维护用户登录信息userID(登陆时插入,多个地方获取)等,尤其适合使用在WEB项目中(Tomcat容器,工作线程隔离) + +[回到顶部](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_labelTop) + +## 二、变量可继承的ThreadLocal==》InheritableThreadLocal + + + +### 2.1 源码注释: + +这个类扩展ThreadLocal,以提供从父线程到子线程的值的继承:当创建子线程时,子线程会接收父元素所具有值的所有可继承线程局部变量的初始值。正常情况下,子线程的变量值与父线程的相同;然而,子线程可复写childValue方法来自定义获取父类变量。 +当变量(例如,用户ID、事务ID)中维护的每个线程属性必须自动传输到创建的任何子线程时,使用InheritableThreadLocal优于ThreadLocal。 + + + +### 2.2 源码剖析 + +**1.子线程启动时,调用init方法,如果父线程有InheritableThreadLocal变量,则在子线程也生成一份** + +下图是Thread类在init时执行的逻辑: + +![img](https://images2017.cnblogs.com/blog/584866/201712/584866-20171207173649206-64534934.png) + +调用createInheritedMap方法,并调用childValue方法复制一份变量给子线程 + +``` + +``` + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +``` + 1 static ThreadLocalMap createInheritedMap(ThreadLocalMap parentMap) { + 2 return new ThreadLocalMap(parentMap); + 3 } + 4 + 5 private ThreadLocalMap(ThreadLocalMap parentMap) { + 6 Entry[] parentTable = parentMap.table; + 7 int len = parentTable.length; + 8 setThreshold(len); + 9 table = new Entry[len]; +10 +11 for (int j = 0; j < len; j++) { +12 Entry e = parentTable[j]; +13 if (e != null) { +14 @SuppressWarnings("unchecked") +15 ThreadLocal key = (ThreadLocal) e.get(); +16 if (key != null) { +17 Object value = key.childValue(e.value); +18 Entry c = new Entry(key, value); +19 int h = key.threadLocalHashCode & (len - 1); +20 while (table[h] != null) +21 h = nextIndex(h, len); +22 table[h] = c; +23 size++; +24 } +25 } +26 } +27 } +``` + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +**2.支持用户自定义childValue函数,用以子类获取父类变量值的转换:父类变量----childValue转换函数-----》子类变量** + +**InheritableThreadLocal默认childValue函数是直接返回: +** + +``` +protected T childValue(T parentValue) { + return parentValue; +} +``` + +用户可在创建InheritableThreadLocal变量时,覆盖childValue函数,见3.3测试 + + + +### 2.3 功能测试 + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +``` + 1 package threadLocal; + 2 + 3 + 4 /** + 5 * + 6 * @ClassName:MyInheritableThreadLocal + 7 * @Description:可继承线程本地变量 + 8 * @author denny.zhang + 9 * @date 2017年12月7日下午5:24:40 +10 */ +11 public class MyInheritableThreadLocal{ +12 //线程本地共享变量 +13 private static final InheritableThreadLocal threadLocal = new InheritableThreadLocal(){ +14 /** +15 * ThreadLocal没有被当前线程赋值时或当前线程刚调用remove方法后调用get方法,返回此方法值 +16 */ +17 @Override +18 protected Object initialValue() +19 { +20 System.out.println("[线程"+Thread.currentThread().getName()+"]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值!"); +21 return null; +22 } +23 +24 @Override +25 protected Object childValue(Object parentValue) { +26 return (Integer)parentValue*2; +27 } +28 +29 }; +30 +31 public static void main(String[] args){ +32 //主线程设置1 +33 threadLocal.set(1); +34 //1.开启任务1线程 +35 new Thread(new MyIntegerTask("IntegerTask1")).start(); +36 //2.中间休息3秒,用以测试数据差异 +37 try { +38 Thread.sleep(3000); +39 } catch (InterruptedException e) { +40 e.printStackTrace(); +41 } +42 //开启任务2线程 +43 new Thread(new MyIntegerTask("IntegerTask2")).start(); +44 } +45 +46 /** +47 * +48 * @ClassName:MyIntegerTask +49 * @Description:整形递增线程 +50 * @author diandian.zhang +51 * @date 2017年12月4日上午10:00:41 +52 */ +53 public static class MyIntegerTask implements Runnable{ +54 private String name; +55 +56 MyIntegerTask(String name) +57 { +58 this.name = name; +59 } +60 +61 @Override +62 public void run() +63 { +64 for(int i = 0; i < 5; i++) +65 { +66 // ThreadLocal.get方法获取线程变量 +67 if(null == MyInheritableThreadLocal.threadLocal.get()) +68 { +69 // ThreadLocal.set方法设置线程变量 +70 MyInheritableThreadLocal.threadLocal.set(0); +71 System.out.println("i="+i+"[线程" + name + "]当前线程不存在缓存,set 0"); +72 } +73 else +74 { +75 int num = (Integer)MyInheritableThreadLocal.threadLocal.get(); +76 System.out.println("i="+i+"[线程" + name + "]get=" + num); +77 MyInheritableThreadLocal.threadLocal.set(num + 1); +78 System.out.println("i="+i+"[线程" + name + "]往threadLocal中set: " + MyInheritableThreadLocal.threadLocal.get()); +79 //当i=3即循环4次时,移除当前线程key +80 if(i == 3) +81 { +82 System.out.println("i="+i+"[线程" + name + "],remove" ); +83 MyInheritableThreadLocal.threadLocal.remove(); +84 } +85 } +86 try +87 { +88 Thread.sleep(1000); +89 } +90 catch (InterruptedException e) +91 { +92 e.printStackTrace(); +93 } +94 } +95 } +96 } +97 } +``` + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +运行结果: + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +``` +主线程变量值=1-----》主线程中变量值1i=0[线程IntegerTask1]get=2-----》子线程1中变量值=2*1=2,验证通过! +i=0[线程IntegerTask1]往threadLocal中set: 3 +i=1[线程IntegerTask1]get=3 +i=1[线程IntegerTask1]往threadLocal中set: 4 +i=2[线程IntegerTask1]get=4 +i=2[线程IntegerTask1]往threadLocal中set: 5 +i=0[线程IntegerTask2]get=2-----》主线程2中变量值=2*1=2,验证通过! +i=0[线程IntegerTask2]往threadLocal中set: 3 +i=3[线程IntegerTask1]get=5 +i=3[线程IntegerTask1]往threadLocal中set: 6 +i=3[线程IntegerTask1],remove +i=1[线程IntegerTask2]get=3 +i=1[线程IntegerTask2]往threadLocal中set: 4 +[线程Thread-0]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值! +i=4[线程IntegerTask1]当前线程不存在缓存,set 0 +i=2[线程IntegerTask2]get=4 +i=2[线程IntegerTask2]往threadLocal中set: 5 +i=3[线程IntegerTask2]get=5 +i=3[线程IntegerTask2]往threadLocal中set: 6 +i=3[线程IntegerTask2],remove +[线程Thread-1]调用get方法时,当前线程共享变量没值,调用initialValue获取默认值! +i=4[线程IntegerTask2]当前线程不存在缓存,set 0 +``` + +[![复制代码](https://common.cnblogs.com/images/copycode.gif)](javascript:void(0);) + +如上图,分析结果我们可知, + +1.子线程根据childValue函数获取到了父线程的变量值。 + +2.多线程InheritableThreadLocal变量各自维护,无竞争关系。 + + + +### 2.4 应用场景 + +**子线程变量数据依赖父线程变量,且自定义赋值函数。** + +例如: + +开启多线程执行任务时,总任务名称叫mainTask 子任务名称依次递增mainTask-subTask1、mainTask-subTask2、mainTask-subTaskN等等 + +[回到顶部](https://www.cnblogs.com/dennyzhangdd/p/7978455.html#_labelTop) + +## 三、总结 + +本文分析了ThreadLocal原理、set(散列算法原理和测试验证,再哈希扩容)、get、remove源码,实际中的应用场景以及功能测试验证。最后又分析了InheritableThreadLocal,使用该类子线程会继承父线程变量,并自定义赋值函数。 +读完本文,相信大家对ThreadLocal一点也不担心了哈哈! + +需要注意2点: + +***1.ThreadLocal不是用来解决线程安全问题的,多线程不共享,不存在竞争!目的是线程本地变量且只能单个线程内维护使用。*** + +***2.InheritableThreadLocal对比ThreadLocal唯一不同是子线程会继承父线程变量,并自定义赋值函数。*** + +***3.项目如果使用了线程池,那么小心线程回收后ThreadLocal、\**\*InheritableThreadLocal\**\*变量要remove,否则线程池回收后,变量还在内存中,后果不堪设想!(例如Tomcat容器的线程池,可以在拦截器中处理:extends HandlerInterceptorAdapter,然后复写\**\*after\**\*Completion方法,remove掉变量!!!)*** \ No newline at end of file diff --git "a/week_05/51/ThreadLocal\347\224\250\351\200\224\345\217\212\344\275\277\347\224\250\345\234\272\346\231\257.md" "b/week_05/51/ThreadLocal\347\224\250\351\200\224\345\217\212\344\275\277\347\224\250\345\234\272\346\231\257.md" new file mode 100644 index 0000000000000000000000000000000000000000..c7205e43f7bd09b47f2d731e68c8d12368207f77 --- /dev/null +++ "b/week_05/51/ThreadLocal\347\224\250\351\200\224\345\217\212\344\275\277\347\224\250\345\234\272\346\231\257.md" @@ -0,0 +1,92 @@ +ThreadLocal,即线程变量,是一个以 ThreadLocal 对象为键、任意对象为值的存储结构。 + +概括起来说,对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而 ThreadLocal 采用了“以空间换时间”的方式。前者仅提供一份变量,让不同的线程排队访问;后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。 + +ThreadLocal 类主要有如下方法: + +protected T initialValue():设置初始值,默认为null +public void set(T value):设置一个要保存的值,并会覆盖原来的值 +public T get():获得保存的值,如果没有用过set方法,会获取到初始值 +public void remove():移除保存的值 +一般 ThreadLocal 作为全局变量使用,示例如下: +public class ConnectionManager { + + // 线程内共享Connection,ThreadLocal通常是全局的,支持泛型 + private static ThreadLocal threadLocal = new ThreadLocal(); + + public static Connection getCurrentConnection() { + Connection conn = threadLocal.get(); + try { + if(conn == null || conn.isClosed()) { + String url = "jdbc:mysql://localhost:3306/test" ; + conn = DriverManager.getConnection(url , "root" , "root") ; + threadLocal.set(conn); + } + } catch (SQLException e) { + } + return conn; + } + + public static void close() { + Connection conn = threadLocal.get(); + try { + if(conn != null && !conn.isClosed()) { + conn.close(); + threadLocal.remove(); + conn = null; + } + } catch (SQLException e) { + } + } +} + +ThreadLocal解决共享参数 +ThreadLocal 也常用在多线程环境中,某个方法处理一个业务,需要递归依赖其他方法时,而要在这些方法中共享参数的问题。 + +例如有方法 a(),在该方法中调用了方法b(),而在方法 b() 中又调用了方法 c(),即 a–>b—>c。如果 a,b,c 现在都需要使用一个字符串参数 args。 + +常用的做法是 a(String args)–>b(String args)—c(String args)。但是使用ThreadLocal,可以用另外一种方式解决: + +在某个接口中定义一个ThreadLocal 对象 +方法 a()、b()、c() 所在的类实现该接口 +在方法 a()中,使用 threadLocal.set(String args) 把 args 参数放入 ThreadLocal 中 +方法 b()、c() 可以在不用传参数的前提下,在方法体中使用 threadLocal.get() 方法就可以得到 args 参数 +示例如下: + +interface ThreadArgs { + ThreadLocal threadLocal = new ThreadLocal(); +} + +class A implements ThreadArgs { + public static void a(String args) { + threadLocal.set(args); + } +} + +class B implements ThreadArgs { + public static void b() { + System.out.println(threadLocal.get()); + } +} + +class C implements ThreadArgs { + public static void c() { + System.out.println(threadLocal.get()); + } +} + +public class ThreadLocalDemo { + public static void main(String[] args) { + A.a(“hello”); + B.b(); + C.c(); + } +} +输出结果: + +hello +hello +1 +2 +关于 InheritableThreadLocal +InheritableThreadLocal 类是 ThreadLocal 类的子类。ThreadLocal 中每个线程拥有它自己的值,与 ThreadLocal 不同的是,InheritableThreadLocal 允许一个线程以及该线程创建的所有子线程都可以访问它保存的值。 \ No newline at end of file diff --git a/week_05/51/ThreadPoolExecutor_51.md b/week_05/51/ThreadPoolExecutor_51.md new file mode 100644 index 0000000000000000000000000000000000000000..48402885ced552bcc0dad69ea9f56ac987637342 --- /dev/null +++ b/week_05/51/ThreadPoolExecutor_51.md @@ -0,0 +1,653 @@ +在 Java 中,如果每个请求到达就创建一个新线程,创建和销毁线程花费的时间和消耗的系统资源都相当大,甚至可能要比在处理实际的用户请求的时间和资源要多的多。 + 如果在一个 Jvm 里创建太多的线程,可能会使系统由于过度消耗内存或“切换过度”而导致系统资源不足。 + 为了解决这个问题,就有了线程池的概念,线程池的核心逻辑是提前创建好若干个线程放在一个容器中。如果有任务需要处理,则将任务直接分配给线程池中的线程来执行就行,任务处理完以后这个线程不会被销毁,而是等待后续分配任务。同时通过线程池来重复管理线程还可以避免创建大量线程增加开销。 + +线程池的优势: + +1. 降低创建线程和销毁线程的性能开销 +2. 提高响应速度,当有新任务需要执行是不需要等待线程创建就可以立马执行 +3. 合理的设置线程池大小可以避免因为线程数超过硬件资源瓶颈带来的问题 + +# Java 中提供的线程池 Api + +为了方便对于线程池的使用,在 Executors 里面提供了几个线程池的工厂方法,这样,很多新手就不需要了解太多关于 ThreadPoolExecutor 的知识了,他们只需要直接使用Executors 的工厂方法,就可以使用线程池: + +- newFixedThreadPool:该方法返回一个固定数量的线程池,线程数不变,当有一个任务提交时,若线程池中空闲,则立即执行,若没有,则会被暂缓在一个任务队列中,等待有空闲的线程去执行。 +- newSingleThreadExecutor: 创建一个线程的线程池,若空闲则执行,若没有空闲线程则暂缓在任务队列中。 +- newCachedThreadPool:返回一个可根据实际情况调整线程个数的线程池,不限制最大线程数量,若用空闲的线程则执行任务,若无任务则不创建线程。并且每一个空闲线程会在 60 秒后自动回收 +- newScheduledThreadPool: 创建一个可以指定线程的数量的线程池,但是这个线程池还带有延迟和周期性执行任务的功能,类似定时器。 + +## ThreadpoolExecutor + +ThreadpoolExecutor 有多个重载的构造方法,我们可以基于它最完整的构造方法来分析 + 先来解释一下每个参数的作用,稍后我们在分析源码的过程中再来详细了解参数的意义。 + + + +```cpp +public ThreadPoolExecutor(int corePoolSize, //核心线程数量 + int maximumPoolSize, //最大线程数 + long keepAliveTime, //超时时间,超出核心线程数量以外的线程空余存活时间 + TimeUnit unit, //存活时间单位 + BlockingQueue workQueue, //保存执行任务的队列 + ThreadFactory threadFactory,//创建新线程使用的工厂 + RejectedExecutionHandler handler //当任务无法执行的时候的处理方式) +``` + +线程池初始化时是没有创建线程的,线程池里的线程的初始化与其他线程一样,但是在完成任务以后,该线程不会自行销毁,而是以挂起的状态返回到线程池。直到应用程序再次向线程池发出请求时,线程池里挂起的线程就会再度激活执行任务。这样既节省了建立线程所造成的性能损耗,也可以让多个任务反复重用同一线程,从而在应用程序生存期内节约大量开销 + +## newFixedThreadPool + + + +```cpp +public static ExecutorService newFixedThreadPool(int nThreads) { + return new ThreadPoolExecutor(nThreads, nThreads, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue()); +} +``` + +FixedThreadPool 的核心线程数和最大线程数都是**指定值**,也就是说当线程池中的线程数超过核心线程数后,任务都会被放到阻塞队列中。另外 **keepAliveTime 为 0**,也就是超出核心线程数量以外的线程空余存活时间 + 而这里选用的阻塞队列是**LinkedBlockingQueue**,使用的是默认容量 Integer.MAX_VALUE,相当于**没有上限** + 这个线程池执行任务的流程如下: + +1. 线程数少于核心线程数,也就是设置的线程数时,新建线程执行任务 +2. 线程数等于核心线程数后,将任务加入阻塞队列 +3. 由于队列容量非常大,可以一直添加 +4. 执行完任务的线程反复去队列中取任务执行 + 用途:FixedThreadPool 用于负载比较大的服务器,为了资源的合理利用,需要限制当前线程数量 + +## newCachedThreadPool + + + +```cpp +public static ExecutorService newCachedThreadPool() { + return new ThreadPoolExecutor(0, Integer.MAX_VALUE, + 60L, TimeUnit.SECONDS, + new SynchronousQueue()); +} +``` + +CachedThreadPool 创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程; 并且**没有核心线程,非核心线程数无上限**,但是每个空闲的时间**只有 60 秒**,超过后就会被回收。 + 它的执行流程如下: + +1. 没有核心线程,直接向 SynchronousQueue 中提交任务 +2. 如果有空闲线程,就去取出任务执行;如果没有空闲线程,就新建一个 +3. 执行完任务的线程有 60 秒生存时间,如果在这个时间内可以接到新任务,就可以继续活下去,否则就被回收 + +## newSingleThreadExecutor + + + +```cpp +public static ExecutorService newSingleThreadExecutor() { + return new FinalizableDelegatedExecutorService + (new ThreadPoolExecutor(1, 1, + 0L, TimeUnit.MILLISECONDS, + new LinkedBlockingQueue())); + } +``` + +创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行 + +# 线程池的实现原理分析 + +ThreadPoolExecutor 是线程池的核心,提供了线程池的实现。 + ScheduledThreadPoolExecutor 继承了 ThreadPoolExecutor,并另外提供一些调度方法以支持定时和周期任务。Executers 是工具类,主要用来创建线程池对象 + 我们把一个任务提交给线程池去处理的时候,线程池的处理过程是什么样的呢? + +以newFixedThreadPool为例,我们分析它的流程图 + + + +![img](https:////upload-images.jianshu.io/upload_images/16701032-f779cca486715981.png?imageMogr2/auto-orient/strip|imageView2/2/w/1152/format/webp) + +image.png + +# 源码分析 + +基于源码入口进行分析,先看 execute 方法 + + + +```csharp + public void execute(Runnable command) { + if (command == null) + throw new NullPointerException(); + int c = ctl.get(); + if (workerCountOf(c) < corePoolSize) {//1.当前池中线程比核心数少,新建一个线程执行任务 + if (addWorker(command, true)) + return; + c = ctl.get(); + } + if (isRunning(c) && workQueue.offer(command)) {//2.核心池已满,但任务队列未满,添加到队列中 + int recheck = ctl.get(); + //任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了 + if (! isRunning(recheck) && remove(command)) + reject(command);//如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务 + else if (workerCountOf(recheck) == 0)//如果之前的线程已被销毁完,新建一个线程 + addWorker(null, false); + } + else if (!addWorker(command, false)) //3.核心池已满,队列已满,试着创建一个新线程 + reject(command); //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务 + } +``` + +## ctl 的作用 + +在线程池中,ctl 贯穿在线程池的整个生命周期中 + + + +```java +private final AtomicInteger ctl = new AtomicInteger(ctlOf(RUNNING, 0)); +``` + +它是一个原子类,主要作用是用来保存线程数量和线程池的状态。我们来分析一下这段代码,其实比较有意思,他用到了位运算 + 一个 int 数值是 32 个 bit 位,这里采用高 3 位来保存运行状态,低 29 位来保存线程数量。 + 我们来分析默认情况下,也就是 ctlOf(RUNNING)运行状态,调用了 ctlOf(int rs,int wc)方法; + + + +```cpp +private static int ctlOf(int rs, int wc) { return rs | wc; } +``` + +其中 RUNNING =-1 << COUNT_BITS ; -1 左移 29 位. -1 的二进制是 + 32 个 1(1111 1111 1111 1111 1111 1111 1111 1111) + +*-1 的二进制计算方法 + 原码是 1000…001 . 高位 1 表示符号位。 + 然后对原码取反,高位不变得到 1111…110 + 然后对反码进行+1 ,也就是补码操作, 最后得到 1111…1111* + +那么-1 <<左移 29 位, 也就是 【111】 表示; rs | wc 。二进制的 111 | 000 。得到的结果仍然是 111 + 那么同理可得其他的状态的 bit 位表示 + + + +```java +private static final int COUNT_BITS = Integer.SIZE - 3; //32-3 +private static final int CAPACITY = (1 << COUNT_BITS) - 1; //将 1 的二进制向右位移 29 位,再减 1 表示最大线程容量 +//运行状态保存在 int 值的高 3 位 (所有数值左移 29 位) +private static final int RUNNING = -1 << COUNT_BITS;// 接收新任务,并执行队列中的任务 +private static final int SHUTDOWN = 0 << COUNT_BITS;// 不接收新任务,但是执行队列中的任务 +private static final int STOP = 1 << COUNT_BITS;// 不接收新任务,不执行队列中的任务,中断正在执行中的任务 +private static final int TIDYING = 2 << COUNT_BITS; //所有的任务都已结束,线程数量为 0,处于该状态的线程池即将调用 terminated()方法 +private static final int TERMINATED = 3 << COUNT_BITS;// terminated()方法执行完成 +``` + +状态转化 + + + +![img](https:////upload-images.jianshu.io/upload_images/16701032-73219168097cd1bc.png?imageMogr2/auto-orient/strip|imageView2/2/w/1094/format/webp) + +image.png + +## addWorker + +如果工作线程数小于核心线程数的话,会调用 addWorker,顾名思义,其实就是要创建一个工作线程。我们来看看源码的实现 + 源码比较长,其实就做了两件事。 + +- 才用循环 CAS 操作来将线程数加 1; +- 新建一个线程并启用。 + + + +```java + private boolean addWorker(Runnable firstTask, boolean core) { + retry: //goto 语句,避免死循环 + for (;;) { + int c = ctl.get(); + int rs = runStateOf(c); + // Check if queue empty only if necessary. + /*如果线程处于非运行状态,并且 rs 不等于 SHUTDOWN 且 firstTask 不等于空且且 + workQueue 为空,直接返回 false(表示不可添加 work 状态) + 1. 线程池已经 shutdown 后,还要添加新的任务,拒绝 + 2. (第二个判断)SHUTDOWN 状态不接受新任务,但仍然会执行已经加入任务队列的任 + 务,所以当进入 SHUTDOWN 状态,而传进来的任务为空,并且任务队列不为空的时候,是允许添加 + 新线程的,如果把这个条件取反,就表示不允许添加 worker*/ + if (rs >= SHUTDOWN && + ! (rs == SHUTDOWN && + firstTask == null && + ! workQueue.isEmpty())) + return false; + for (;;) { //自旋 + int wc = workerCountOf(c);//获得 Worker 工作线程数 + //如果工作线程数大于默认容量大小或者大于核心线程数大小,则直接返回 false 表示不 + 能再添加 worker。 + if (wc >= CAPACITY || + wc >= (core ? corePoolSize : maximumPoolSize)) + return false; + if (compareAndIncrementWorkerCount(c))//通过 cas 来增加工作线程数,如果 cas 失败,则直接重试 + break retry; + c = ctl.get(); // Re-read ctl //再次获取 ctl 的值 + if (runStateOf(c) != rs) //这里如果不想等,说明线程的状态发生了变化,继续重试 + continue retry; + // else CAS failed due to workerCount change; retry inner loop + } + } + + + //上面这段代码主要是对 worker 数量做原子+1 操作,下面的逻辑才是正式构建一个 worker + + + boolean workerStarted = false; //工作线程是否启动的标识 + boolean workerAdded = false; //工作线程是否已经添加成功的标识 + Worker w = null; + try { + w = new Worker(firstTask); //构建一个 Worker,这个 worker 是什么呢?我们可以看到构造方法里面传入了一个 Runnable 对象 + final Thread t = w.thread; //从 worker 对象中取出线程 + if (t != null) { + final ReentrantLock mainLock = this.mainLock; + mainLock.lock(); //这里有个重入锁,避免并发问题 + try { + // Recheck while holding lock. + // Back out on ThreadFactory failure or if + // shut down before lock acquired. + int rs = runStateOf(ctl.get()); + //只有当前线程池是正在运行状态,[或是 SHUTDOWN 且 firstTask 为空],才能添加到 workers 集合中 + if (rs < SHUTDOWN || + (rs == SHUTDOWN && firstTask == null)) { + //任务刚封装到 work 里面,还没 start,你封装的线程就是 alive,几个意思?肯定是要抛异常出去的 + if (t.isAlive()) // precheck that t is startable + throw new IllegalThreadStateException(); + workers.add(w); //将新创建的 Worker 添加到 workers 集合中 + int s = workers.size(); + //如果集合中的工作线程数大于最大线程数,这个最大线程数表示线程池曾经出现过的最大线程数 + if (s > largestPoolSize) + largestPoolSize = s; //更新线程池出现过的最大线程数 + workerAdded = true;//表示工作线程创建成功了 + } + } finally { + mainLock.unlock(); //释放锁 + } + if (workerAdded) {//如果 worker 添加成功 + t.start();//启动线程 + workerStarted = true; + } + } + } finally { + if (! workerStarted) + addWorkerFailed(w); //如果添加失败,就需要做一件事,就是递减实际工作线程数(还记得我们最开始的时候增加了工作线程数吗) + } + return workerStarted;//返回结果 + } +``` + +我们发现 addWorker 方法只是构造了一个 Worker,并且把 firstTask 封装到 worker 中,它是做什么的呢?我们来看看 + + + +```java +private final class Worker extends AbstractQueuedSynchronizer implements Runnable { + private static final long serialVersionUID = 6138294804551838833L; + /** + * Thread this worker is running in. Null if factory fails. + */ + final Thread thread; //注意了,这才是真正执行 task 的线程,从构造函数可知是由ThreadFactury 创建的 + /** + * Initial task to run. Possibly null. + */ + Runnable firstTask; //这就是需要执行的 task + /** + * Per-thread task counter + */ + volatile long completedTasks; //完成的任务数,用于线程池统计 + + Worker(Runnable firstTask) { + setState(-1); //初始状态 -1,防止在调用 runWorker(),也就是真正执行 task前中断 thread。 + this.firstTask = firstTask; + this.thread = getThreadFactory().newThread(this); + } + + public void run() { + runWorker(this); + } + + ... + } +``` + +1. 每个 worker,都是一条线程,同时里面包含了一个 firstTask,即初始化时要被首先执行的任务. +2. 最终执行任务的,是 runWorker()方法 + +Worker 类继承了 AQS,并实现了 Runnable 接口,注意其中的 firstTask 和 thread 属性: + firstTask 用它来保存传入的任务;thread 是在调用构造方法时通过 ThreadFactory 来创建的线程,是用来处理任务的线程。 + +在调用构造方法时,需要传入任务,这里通过 getThreadFactory().newThread(this);来新建一个线程,newThread 方法传入的参数是 this,因为 Worker 本身继承了 Runnable 接口,也就是一个线程,所以一个 Worker 对象在启动的时候会调用 Worker 类中的 run 方法。 + +Worker 继承了 AQS,使用 AQS 来实现独占锁的功能。为什么不使用 ReentrantLock 来实现呢?可以看到 tryAcquire 方法,它是不允许重入的,而 ReentrantLock 是允许重入的: + +lock 方法一旦获取了独占锁,表示当前线程正在执行任务中;那么它会有以下几个作用 + +1. 如果正在执行任务,则不应该中断线程; +2. 如果该线程现在不是独占锁的状态,也就是空闲的状态,说明它没有在处理任务,这时可以对该线程进行中断; +3. 线程池在执行 shutdown 方法或 tryTerminate 方法时会调用 interruptIdleWorkers 方法来中断空闲的线程,interruptIdleWorkers 方法会使用 tryLock 方法来判断线程池中的线程是否是空闲状态 +4. 之所以设置为不可重入,是因为我们不希望任务在调用像 setCorePoolSize 这样的线程池控制方法时重新获取锁,这样会中断正在运行的线程 + +## addWorkerFailed + +addWorker 方法中,如果添加 Worker 并且启动线程失败,则会做失败后的处理。 + + + +```csharp + private void addWorkerFailed(Worker w) { + final ReentrantLock mainLock = this.mainLock; + mainLock.lock(); + try { + if (w != null) + workers.remove(w); + decrementWorkerCount(); + tryTerminate(); + } finally { + mainLock.unlock(); + } + } +``` + +这个方法主要做两件事 + +1. 如果 worker 已经构造好了,则从 workers 集合中移除这个 worker +2. 原子递减核心线程数(因为在 addWorker 方法中先做了原子增加) +3. 尝试结束线程池 + +## runWorker 方法 + +前面已经了解了 ThreadPoolExecutor 的核心方法 addWorker,主要作用是增加工作线程,而 Worker 简单理解其实就是一个线程,里面重新了 run 方法,这块是线程池中执行任务的真正处理逻辑,也就是 runWorker 方法,这个方法主要做几件事 + +1. 如果 task 不为空,则开始执行 task +2. 如果 task 为空,则通过 getTask()再去取任务,并赋值给 task,如果取到的 Runnable 不为空,则执行该任务 +3. 执行完毕后,通过 while 循环继续 getTask()取任务 +4. 如果 getTask()取到的任务依然是空,那么整个 runWorker()方法执行完毕 + + + +```csharp +final void runWorker(Worker w) { + Thread wt = Thread.currentThread(); + Runnable task = w.firstTask; + w.firstTask = null; + //unlock,表示当前 worker 线程允许中断,因为 new Worker 默认的 state=-1,此处是调用 + //Worker 类的 tryRelease()方法,将 state 置为 0, + //而 interruptIfStarted()中只有 state>=0 才允许调用中断 + w.unlock(); // allow interrupts + boolean completedAbruptly = true; + try { + //注意这个 while 循环,在这里实现了 [线程复用] // 如果 task 为空,则通过getTask 来获取任务 + while (task != null || (task = getTask()) != null) { + w.lock(); //上锁,不是为了防止并发执行任务,为了在 shutdown()时不终止正在运行的 worker + //线程池为 stop 状态时不接受新任务,不执行已经加入任务队列的任务,还中断正在执行的任务 + //所以对于 stop 状态以上是要中断线程的 + //(Thread.interrupted() &&runStateAtLeast(ctl.get(), STOP)确保线程中断标志位为 true 且是 stop 状态以上,接着清除了中断标志 + //!wt.isInterrupted()则再一次检查保证线程需要设置中断标志位 + if ((runStateAtLeast(ctl.get(), STOP) || + (Thread.interrupted() && + runStateAtLeast(ctl.get(), STOP))) && + !wt.isInterrupted()) + wt.interrupt(); + try { + beforeExecute(wt, task);//这里默认是没有实现的,在一些特定的场景中我们可以自己继承 ThreadpoolExecutor 自己重写 + Throwable thrown = null; + try { + task.run(); //执行任务中的 run 方法 + } catch (RuntimeException x) { + thrown = x; throw x; + } catch (Error x) { + thrown = x; throw x; + } catch (Throwable x) { + thrown = x; throw new Error(x); + } finally { + afterExecute(task, thrown); //这里默认默认而也是没有实现 + } + } finally { + //置空任务(这样下次循环开始时,task 依然为 null,需要再通过 getTask()取) + 记录该 Worker 完成任务数量 + 解锁 + task = null; + w.completedTasks++; + w.unlock(); + } + } + completedAbruptly = false; + } finally { + processWorkerExit(w, completedAbruptly); + //1.将入参 worker 从数组 workers 里删除掉; + //2.根据布尔值 allowCoreThreadTimeOut 来决定是否补充新的 Worker 进数组workers + } + } +``` + +## getTask + +worker 线程会从阻塞队列中获取需要执行的任务,这个方法不是简单的 take 数据,我们来分析下他的源码实现 + +*你也许好奇是怎样判断线程有多久没有活动了,是不是以为线程池会启动一个监控线程,专门监控哪个线程正在偷懒?想太多,其实只是在线程从工作队列 poll 任务时,加上了超时限制,如果线程在 keepAliveTime 的时间内 poll 不到任务,那我就认为这条线程没事做,可以干掉了,看看这个代码片段你就清楚了* + + + +```java +private Runnable getTask() { + boolean timedOut = false; // Did the last poll() time out? + for (;;) {//自旋 + int c = ctl.get(); + int rs = runStateOf(c); + /* 对线程池状态的判断,两种情况会 workerCount-1,并且返回 null + 1. 线程池状态为 shutdown,且 workQueue 为空(反映了 shutdown 状态的线程池还是要执行 workQueue 中剩余的任务的) + 2. 线程池状态为 stop(shutdownNow()会导致变成 STOP)(此时不用考虑 workQueue的情况)*/ + // Check if queue empty only if necessary. + if (rs >= SHUTDOWN && (rs >= STOP || workQueue.isEmpty())) { + decrementWorkerCount(); + return null;//返回 null,则当前 worker 线程会退出 + } + int wc = workerCountOf(c); + // timed 变量用于判断是否需要进行超时控制。 + // allowCoreThreadTimeOut 默认是 false,也就是核心线程不允许进行超时; + // wc > corePoolSize,表示当前线程池中的线程数量大于核心线程数量; + // 对于超过核心线程数量的这些线程,需要进行超时控制 + boolean timed = allowCoreThreadTimeOut || wc > corePoolSize; + /*1. 线程数量超过 maximumPoolSize 可能是线程池在运行时被调用了 setMaximumPoolSize() + 被改变了大小,否则已经 addWorker()成功不会超过 maximumPoolSize + 2. timed && timedOut 如果为 true,表示当前操作需要进行超时控制,并且上次从阻塞队列中 + 获取任务发生了超时.其实就是体现了空闲线程的存活时间*/ + if ((wc > maximumPoolSize || (timed && timedOut)) + && (wc > 1 || workQueue.isEmpty())) { + if (compareAndDecrementWorkerCount(c)) + return null; + continue; + } + try { + /*根据 timed 来判断,如果为 true,则通过阻塞队列 poll 方法进行超时控制,如果在 + keepaliveTime 时间内没有获取到任务,则返回 null. + 否则通过 take 方法阻塞式获取队列中的任务*/ + Runnable r = timed ? + workQueue.poll(keepAliveTime, TimeUnit.NANOSECONDS) : + workQueue.take(); + if (r != null)//如果拿到的任务不为空,则直接返回给 worker 进行处理 + return r; + timedOut = true;//如果 r==null,说明已经超时了,设置 timedOut=true,在下次自旋的时候进行回收 + } catch (InterruptedException retry) { + timedOut = false;// 如果获取任务时当前线程发生了中断,则设置 timedOut 为false 并返回循环重试 + } + } + } +``` + +这里重要的地方是第二个 if 判断,目的是控制线程池的有效线程数量。由上文中的分析可以知道,在执行 execute 方法时,如果当前线程池的线程数量超过了 corePoolSize 且小于maximumPoolSize,并且 workQueue 已满时,则可以增加工作线程,但这时如果超时没有获取到任务,也就是 timedOut 为 true 的情况,说明 workQueue 已经为空了,也就说明了当前线程池中不需要那么多线程来执行任务了,可以把多于 corePoolSize 数量的线程销毁掉,保持线程数量在 corePoolSize 即可。 + 什么时候会销毁?当然是 runWorker 方法执行完之后,也就是 Worker 中的 run 方法执行完,由 JVM 自动回收。 + getTask 方法返回 null 时,在 runWorker 方法中会跳出 while 循环,然后会执行processWorkerExit 方法。 + +## processWorkerExit + +runWorker 的 while 循环执行完毕以后,在 finally 中会调用 processWorkerExit,来销毁工作线程。 + 到目前为止,我们已经从 execute 方法中输入了 worker 线程的创建到执行以及最后到销毁的全部过程。那么我们继续回到 execute 方法.我们只分析完addWorker 这段逻辑,继续来看后面的判断 + +## execute 后续逻辑分析 + +如果核心线程数已满,说明这个时候不能再创建核心线程了,于是走第二个判断 + 第二个判断逻辑比较简单,如果线程池处于运行状态并且任务队列没有满,则将任务添加到队列中 + 第三个判断,核心线程数满了,队列也满了,那么这个时候创建新的线程也就是(非核心线程) + 如果非核心线程数也达到了最大线程数大小,则直接拒绝任务 + + + +```csharp + public void execute(Runnable command) { + ... + if (isRunning(c) && workQueue.offer(command)) {//2.核心池已满,但任务队列未满,添加到队列中 + int recheck = ctl.get(); + //任务成功添加到队列以后,再次检查是否需要添加新的线程,因为已存在的线程可能被销毁了 + if (! isRunning(recheck) && remove(command)) + reject(command);//如果线程池处于非运行状态,并且把当前的任务从任务队列中移除成功,则拒绝该任务 + else if (workerCountOf(recheck) == 0)//如果之前的线程已被销毁完,新建一个线程 + addWorker(null, false); + } + else if (!addWorker(command, false)) //3.核心池已满,队列已满,试着创建一个新线程 + reject(command); //如果创建新线程失败了,说明线程池被关闭或者线程池完全满了,拒绝任务 + } +``` + +## 拒绝策略 + +1、AbortPolicy:直接抛出异常,默认策略; + 2、CallerRunsPolicy:用调用者所在的线程来执行任务; + 3、DiscardOldestPolicy:丢弃阻塞队列中靠最前的任务,并执行当前任务; + 4、DiscardPolicy:直接丢弃任务; + 当然也可以根据应用场景实现 RejectedExecutionHandler 接口,自定义饱和策略,如记录日志或持久化存储不能处理的任务 + +## 线程池的注意事项 + +分析完线程池以后,我们再来了解一下线程池的注意事项 + +阿里开发手册不建议使用线程池,手册上是说**线程池的构建不允许使用 Executors 去创建,而是通过 ThreadPoolExecutor 的方式。** + 分析完原理以后,大家自己一定要有一个答案。我来简单分析下,用 Executors 使得用户不需要关心线程池的参数配置,意味着大家对于线程池的运行规则也会慢慢的忽略。这会导致一个问题,比如我们用 newFixdThreadPool 或者 singleThreadPool.允许的队列长度为Integer.MAX_VALUE,如果使用不当会导致大量请求堆积到队列中导致 OOM 的风险而 newCachedThreadPool,允许创建线程数量为 Integer.MAX_VALUE,也可能会导致大量线程的创建出现 CPU 使用过高或者 OOM 的问题。而如果我们通过 ThreadPoolExecutor 来构造线程池的话,我们势必要了解线程池构造中每个参数的具体含义,使得开发者在配置参数的时候能够更加谨慎。 + +## 如何合理配置线程池的大小 + +如何合理配置线程池大小,线程池大小不是靠猜,也不是说越多越好。 + 在遇到这类问题时,先冷静下来分析 + +1. 需要分析线程池执行的任务的特性: CPU 密集型还是 IO 密集型 +2. 每个任务执行的平均时长大概是多少,这个任务的执行时长可能还跟任务处理逻辑是否涉及到网络传输以及底层系统资源依赖有关系 + +如果是 CPU 密集型,主要是执行计算任务,响应时间很快,cpu 一直在运行,这种任务 cpu的利用率很高,那么线程数的配置应该根据 CPU 核心数来决定,CPU 核心数=最大同时执行线程数,加入 CPU 核心数为 4,那么服务器最多能同时执行 4 个线程。过多的线程会导致上下文切换反而使得效率降低。那线程池的最大线程数可以配置为 cpu 核心数+1 如果是 IO 密集型,主要是进行 IO 操作,执行 IO 操作的时间较长,这是 cpu 出于空闲状态,导致 cpu 的利用率不高,这种情况下可以增加线程池的大小。这种情况下可以结合线程的等待时长来做判断,等待时间越高,那么线程数也相对越多。一般可以配置 cpu 核心数的 2 倍。 + +一个公式:线程池设定最佳线程数目 = ((线程池设定的线程等待时间+线程 CPU 时间)/ + 线程 CPU 时间 )* CPU 数目 + +这个公式的线程 cpu 时间是预估的程序单个线程在 cpu 上运行的时间(通常使用 loadrunner测试大量运行次数求出平均值) + +## 线程池中的线程初始化 + +默认情况下,创建线程池之后,线程池中是没有线程的,需要提交任务之后才会创建线程。 + 在实 际中如果需要 线程池创建之 后立即创建线 程,可以通过 以下两个方法 办到: + prestartCoreThread():初始化一个核心线程; prestartAllCoreThreads():初始化所有核心线程 + + + +```undefined +ThreadPoolExecutor tpe=(ThreadPoolExecutor)service; +tpe.prestartAllCoreThreads(); +``` + +## 线程池的关闭 + +ThreadPoolExecutor 提 供 了 两 个 方 法 , 用 于 线 程 池 的 关 闭 , 分 别 是 shutdown() 和shutdownNow(),其中:shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务 shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务 + +## 线程池容量的动态调整 + +ThreadPoolExecutor 提 供 了 动 态 调 整 线 程 池 容 量 大 小 的 方 法 : setCorePoolSize() 和 + setMaximumPoolSize(),setCorePoolSize:设置核心池大小 setMaximumPoolSize:设置线程池最大能创建的线程数目大小 + 任务缓存队列及排队策略 + 在前面我们提到了任务缓存队列,即 workQueue,它用来存放等待执行的任务。 + workQueue 的类型为 BlockingQueue,通常可以取下面三种类型: + +1. ArrayBlockingQueue:基于数组的先进先出队列,此队列创建时必须指定大小; +2. LinkedBlockingQueue:基于链表的先进先出队列,如果创建时没有指定此队列大小,则默认为 Integer.MAX_VALUE; +3. SynchronousQueue:这个队列比较特殊,它不会保存提交的任务,而是将直接新建一个线程来执行新来的任务。 + +## 线程池的监控 + +如果在项目中大规模的使用了线程池,那么必须要有一套监控体系,来指导当前线程池的状态,当出现问题的时候可以快速定位到问题。而线程池提供了相应的扩展方法,我们通过重写线程池的 beforeExecute、afterExecute 和 shutdown 等方式就可以实现对线程的监控,简单给大家演示一个案例 + + + +```php +public class Demo1 extends ThreadPoolExecutor { + // 保存任务开始执行的时间,当任务结束时,用任务结束时间减去开始时间计算任务执行时间 + private ConcurrentHashMap startTimes; + + public Demo1(int corePoolSize, int maximumPoolSize, long + keepAliveTime, TimeUnit unit, BlockingQueue workQueue) { + super(corePoolSize, maximumPoolSize, keepAliveTime, unit, + workQueue); + this.startTimes = new ConcurrentHashMap<>(); + } + + @Override + public void shutdown() { + System.out.println("已经执行的任务数: + "+this.getCompletedTaskCount()+", " + + "当前活动线程数:" + this.getActiveCount() + ",当前排队线程数:"+this.getQueue().size()); + System.out.println(); + super.shutdown(); + } + + //任务开始之前记录任务开始时间 + @Override + protected void beforeExecute(Thread t, Runnable r) { + startTimes.put(String.valueOf(r.hashCode()), new Date()); + super.beforeExecute(t, r); + } + + @Override + protected void afterExecute(Runnable r, Throwable t) { + Date startDate = startTimes.remove(String.valueOf(r.hashCode())); + Date finishDate = new Date(); + long diff = finishDate.getTime() - startDate.getTime(); + // 统计任务耗时、初始线程数、核心线程数、正在执行的任务数量、 + // 已完成任务数量、任务总数、队列里缓存的任务数量、 + // 池中存在的最大线程数、最大允许的线程数、线程空闲时间、线程池是否关闭、线程池是否终止 + System.out.print("任务耗时:" + diff + "\n"); + System.out.print("初始线程数:" + this.getPoolSize() + "\n"); + System.out.print("核心线程数:" + this.getCorePoolSize() + "\n"); + System.out.print("正在执行的任务数量:" + this.getActiveCount() + "\n"); + System.out.print("已经执行的任务数:"+this.getCompletedTaskCount()+"\n "); + System.out.print("任务总数:" + this.getTaskCount() + "\n"); + System.out.print("最大允许的线程数:" + this.getMaximumPoolSize() + "\n"); + System.out.print("线程空闲时间:"+this.getKeepAliveTime(TimeUnit.MILLISECONDS)+"\n "); + System.out.println(); + super.afterExecute(r, t); + } + + public static ExecutorService newCachedThreadPool() { + return new Demo1(0, Integer.MAX_VALUE, 60L, TimeUnit.SECONDS, new + SynchronousQueue()); + } +``` + +测试 + + + +```java +public class Test implements Runnable{ + private static ExecutorService es =Demo1.newCachedThreadPool(); + @Override + public void run() { + try { + Thread.sleep(1000); + } catch (InterruptedException e) { + e.printStackTrace(); + } + } + public static void main(String[] args) throws Exception { + for (int i = 0; i < 100; i++) { + es.execute(new Test()); + } + es.shutdown(); + } +} +``` \ No newline at end of file diff --git a/week_05/51/Thread_2_51.md b/week_05/51/Thread_2_51.md new file mode 100644 index 0000000000000000000000000000000000000000..f84ab714080e3069e62ec62dc8ef6d9f1cebd189 --- /dev/null +++ b/week_05/51/Thread_2_51.md @@ -0,0 +1,877 @@ +![img](https://upload-images.jianshu.io/upload_images/18104193-417e49e2d521dc28.png?imageMogr2/auto-orient/strip|imageView2/2/w/459/format/webp) + +Thread变量&构造方法.png + +![img](https://upload-images.jianshu.io/upload_images/18104193-afde27fb7a9371e3.png?imageMogr2/auto-orient/strip|imageView2/2/w/792/format/webp) + +Thread类图.png + +## Thread变量解析 + + + +```java + /* Make sure registerNatives is the first thing does. */ + private static native void registerNatives(); + static { + registerNatives(); + } + //线程名的字符数组表示(线程初始化时使用) + private char name[]; + //线程的优先级,值的范围1-10,(线程初始化时会使用) + private int priority; + //这个没有显示引用,不太清楚 + private Thread threadQ; + //这个没有显示引用,不太清楚 + private long eetop; + + /* Whether or not to single_step this thread. */ + //是否单步执行此线程,可能跟调试有关 + private boolean single_step; + + /* Whether or not the thread is a daemon thread. */ + //是否是守护线程,默认是false,除非显示设置为true + private boolean daemon = false; + + /* JVM state */ + //JVM的状态, + private boolean stillborn = false; + + /* What will be run. */ + //要执行的runnable对象,(线程初始化时会使用) + private Runnable target; + + /* The group of this thread */ + //当前线程属于哪个线程组,(线程初始化时会使用) + private ThreadGroup group; + + /* The context ClassLoader for this thread */ + //当前线程的上下文类加载器,也就是这个属性是破坏双亲委派模型的决定性变量 + private ClassLoader contextClassLoader; + + /* The inherited AccessControlContext of this thread */ + //当前线程继承到的访问控制上下文,线程初始化的时候会进行初始化 + private AccessControlContext inheritedAccessControlContext; + + /* For autonumbering anonymous threads. */ + //对匿名线程进行自动编号默认从0开始 + private static int threadInitNumber; + //使用了同步机制,保障threadInitNumber不会重复 + private static synchronized int nextThreadNum() { + return threadInitNumber++; + } + + /* ThreadLocal values pertaining to this thread. This map is maintained + * by the ThreadLocal class. */ + //当前线程拥有的本地变量副本,通过ThreadLocal的内部类进行引用 + ThreadLocal.ThreadLocalMap threadLocals = null; + + /* + * InheritableThreadLocal values pertaining to this thread. This map is + * maintained by the InheritableThreadLocal class. + */ + //InheritableThreadLocal类的本地变量副本,InheritableThreadLocal是ThreadLocal的子类 + ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; + + /* + * The requested stack size for this thread, or 0 if the creator did + * not specify a stack size. It is up to the VM to do whatever it + * likes with this number; some VMs will ignore it. + */ + //当前线程请求栈的大小,具体由虚拟机决定怎么用他 + private long stackSize; + + /* + * JVM-private state that persists after native thread termination. + */ + //这个变量应该跟虚拟机底层监控有关,不太清楚 + private long nativeParkEventPointer; + + /* + * Thread ID + */ + //当前线程的线程id + private long tid; + + /* For generating thread ID */ + //当前线程的线程id种子序列号,默认是0L + private static long threadSeqNumber; + + /* Java thread status for tools, + * initialized to indicate thread 'not yet started' + */ + //线程的状态,这里使用volatile修饰是为了让其他线程或者虚拟机尽早知道线程的状态 + //初始化是0,默认是还没有启动 + private volatile int threadStatus = 0; + + + private static synchronized long nextThreadID() { + return ++threadSeqNumber; + } + + /** + * The argument supplied to the current call to + * java.util.concurrent.locks.LockSupport.park. + * Set by (private) java.util.concurrent.locks.LockSupport.setBlocker + * Accessed using java.util.concurrent.locks.LockSupport.getBlocker + */ + //这个参数标识锁是否是阻塞状态,通过LockSupport的api方法设置这个值,Thread本身不会设置这个值 + //如果设置阻塞状态应该会被挂起 + volatile Object parkBlocker; + + /* The object in which this thread is blocked in an interruptible I/O + * operation, if any. The blocker's interrupt method should be invoked + * after setting this thread's interrupt status. + */ + //当线程处于中断状态的话这个接口(Interruptible)的interrupt方法会被调用,并中断线程 + private volatile Interruptible blocker; + //当前线程拥有的中断锁对象 + private final Object blockerLock = new Object(); + + /* Set the blocker field; invoked via sun.misc.SharedSecrets from java.nio code + */ + //提供中断方法,Thread本身不会调用,由其他底层API使用 + void blockedOn(Interruptible b) { + // + synchronized (blockerLock) { + blocker = b; + } + } + + /** + * The minimum priority that a thread can have. + */ + //线程的最小优先级是1 + public final static int MIN_PRIORITY = 1; + + /** + * The default priority that is assigned to a thread. + */ + 线程的默认优先级是5 + public final static int NORM_PRIORITY = 5; + + /** + * The maximum priority that a thread can have. + */ + 线程的最大优先级是10,优先级越大,使用CPU时间越长 + public final static int MAX_PRIORITY = 10; + + + //当前线程执行的栈数组 + private static final StackTraceElement[] EMPTY_STACK_TRACE + = new StackTraceElement[0]; + + // null unless explicitly set + //线程异常处理器 + private volatile UncaughtExceptionHandler uncaughtExceptionHandler; + + //线程异常处理器 + private static volatile UncaughtExceptionHandler defaultUncaughtExceptionHandler; + + + + + + // The following three initially uninitialized fields are exclusively + // managed by class java.util.concurrent.ThreadLocalRandom. These + // fields are used to build the high-performance PRNGs in the + // concurrent code, and we can not risk accidental false sharing. + // Hence, the fields are isolated with @Contended. + //下面三个参数用于构造高性能的随机数生成器 + /** The current seed for a ThreadLocalRandom */ + @sun.misc.Contended("tlr") + long threadLocalRandomSeed; + + /** Probe hash value; nonzero if threadLocalRandomSeed initialized */ + @sun.misc.Contended("tlr") + int threadLocalRandomProbe; + + /** Secondary seed isolated from public ThreadLocalRandom sequence */ + @sun.misc.Contended("tlr") + int threadLocalRandomSecondarySeed; +``` + +## Thread构造函数&初始化 + + + +```dart + /** + * Allocates a new {@code Thread} object. This constructor has the same + * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} + * {@code (null, null, gname)}, where {@code gname} is a newly generated + * name. Automatically generated names are of the form + * {@code "Thread-"+}n, where n is an integer. + */ + //总体来说Thread的构造方法中调用的是init方法,但是 + //Thread 的构造方法很多,因此需要大概的翻一下, + //但是基本上需要指定线程名,其他可以走默认值 + public Thread() { + init(null, null, "Thread-" + nextThreadNum(), 0); + } + + /** + * Allocates a new {@code Thread} object. This constructor has the same + * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} + * {@code (null, target, gname)}, where {@code gname} is a newly generated + * name. Automatically generated names are of the form + * {@code "Thread-"+}n, where n is an integer. + * + * @param target + * the object whose {@code run} method is invoked when this thread + * is started. If {@code null}, this classes {@code run} method does + * nothing. + */ + public Thread(Runnable target) { + init(null, target, "Thread-" + nextThreadNum(), 0); + } + + /** + * Creates a new Thread that inherits the given AccessControlContext. + * This is not a public constructor. + */ + Thread(Runnable target, AccessControlContext acc) { + init(null, target, "Thread-" + nextThreadNum(), 0, acc); + } + + /** + * Allocates a new {@code Thread} object. This constructor has the same + * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} + * {@code (group, target, gname)} ,where {@code gname} is a newly generated + * name. Automatically generated names are of the form + * {@code "Thread-"+}n, where n is an integer. + * + * @param group + * the thread group. If {@code null} and there is a security + * manager, the group is determined by {@linkplain + * SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}. + * If there is not a security manager or {@code + * SecurityManager.getThreadGroup()} returns {@code null}, the group + * is set to the current thread's thread group. + * + * @param target + * the object whose {@code run} method is invoked when this thread + * is started. If {@code null}, this thread's run method is invoked. + * + * @throws SecurityException + * if the current thread cannot create a thread in the specified + * thread group + */ + public Thread(ThreadGroup group, Runnable target) { + init(group, target, "Thread-" + nextThreadNum(), 0); + } + + /** + * Allocates a new {@code Thread} object. This constructor has the same + * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} + * {@code (null, null, name)}. + * + * @param name + * the name of the new thread + */ + public Thread(String name) { + init(null, null, name, 0); + } + + /** + * Allocates a new {@code Thread} object. This constructor has the same + * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} + * {@code (group, null, name)}. + * + * @param group + * the thread group. If {@code null} and there is a security + * manager, the group is determined by {@linkplain + * SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}. + * If there is not a security manager or {@code + * SecurityManager.getThreadGroup()} returns {@code null}, the group + * is set to the current thread's thread group. + * + * @param name + * the name of the new thread + * + * @throws SecurityException + * if the current thread cannot create a thread in the specified + * thread group + */ + public Thread(ThreadGroup group, String name) { + init(group, null, name, 0); + } + + /** + * Allocates a new {@code Thread} object. This constructor has the same + * effect as {@linkplain #Thread(ThreadGroup,Runnable,String) Thread} + * {@code (null, target, name)}. + * + * @param target + * the object whose {@code run} method is invoked when this thread + * is started. If {@code null}, this thread's run method is invoked. + * + * @param name + * the name of the new thread + */ + public Thread(Runnable target, String name) { + init(null, target, name, 0); + } + + /** + * Allocates a new {@code Thread} object so that it has {@code target} + * as its run object, has the specified {@code name} as its name, + * and belongs to the thread group referred to by {@code group}. + * + *

If there is a security manager, its + * {@link SecurityManager#checkAccess(ThreadGroup) checkAccess} + * method is invoked with the ThreadGroup as its argument. + * + *

In addition, its {@code checkPermission} method is invoked with + * the {@code RuntimePermission("enableContextClassLoaderOverride")} + * permission when invoked directly or indirectly by the constructor + * of a subclass which overrides the {@code getContextClassLoader} + * or {@code setContextClassLoader} methods. + * + *

The priority of the newly created thread is set equal to the + * priority of the thread creating it, that is, the currently running + * thread. The method {@linkplain #setPriority setPriority} may be + * used to change the priority to a new value. + * + *

The newly created thread is initially marked as being a daemon + * thread if and only if the thread creating it is currently marked + * as a daemon thread. The method {@linkplain #setDaemon setDaemon} + * may be used to change whether or not a thread is a daemon. + * + * @param group + * the thread group. If {@code null} and there is a security + * manager, the group is determined by {@linkplain + * SecurityManager#getThreadGroup SecurityManager.getThreadGroup()}. + * If there is not a security manager or {@code + * SecurityManager.getThreadGroup()} returns {@code null}, the group + * is set to the current thread's thread group. + * + * @param target + * the object whose {@code run} method is invoked when this thread + * is started. If {@code null}, this thread's run method is invoked. + * + * @param name + * the name of the new thread + * + * @throws SecurityException + * if the current thread cannot create a thread in the specified + * thread group or cannot override the context class loader methods. + */ + public Thread(ThreadGroup group, Runnable target, String name) { + init(group, target, name, 0); + } + + + + /** + * Initializes a Thread with the current AccessControlContext. + * @see #init(ThreadGroup,Runnable,String,long,AccessControlContext) + */ + private void init(ThreadGroup g, Runnable target, String name, + long stackSize) { + init(g, target, name, stackSize, null); + } + + /** + * Initializes a Thread. + * + * @param g the Thread group + * @param target the object whose run() method gets called + * @param name the name of the new Thread + * @param stackSize the desired stack size for the new thread, or + * zero to indicate that this parameter is to be ignored. + * @param acc the AccessControlContext to inherit, or + * AccessController.getContext() if null + */ + private void init(ThreadGroup g, Runnable target, String name, + long stackSize, AccessControlContext acc) { + if (name == null) { + throw new NullPointerException("name cannot be null"); + } + //设置线程名 + this.name = name.toCharArray(); + //当前线程的创建过程中有些属性是从父类线程中继承过来 + Thread parent = currentThread(); + SecurityManager security = System.getSecurityManager(); + //设置当前线程所属的线程组 + if (g == null) { + /* Determine if it's an applet or not */ + + /* If there is a security manager, ask the security manager + what to do. */ + if (security != null) { + g = security.getThreadGroup(); + } + + /* If the security doesn't have a strong opinion of the matter + use the parent thread group. */ + if (g == null) { + g = parent.getThreadGroup(); + } + } + + /* checkAccess regardless of whether or not threadgroup is + explicitly passed in. */ + g.checkAccess(); + + /* + * Do we have the required permissions? + */ + //检查安全访问权限 + if (security != null) { + if (isCCLOverridden(getClass())) { + security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); + } + } + //这里说明每个线程肯定属于某个线程组,而且创建之后在线程组中会记个数代表没有启动的线程数量 + g.addUnstarted(); + + this.group = g; + //如果父类是守护线程那么子类也是 + this.daemon = parent.isDaemon(); + //父类跟子类享有相同的线程优先级 + this.priority = parent.getPriority(); + //通过父类线程获取线程上下文类加载器 + if (security == null || isCCLOverridden(parent.getClass())) + this.contextClassLoader = parent.getContextClassLoader(); + else + this.contextClassLoader = parent.contextClassLoader; + //如果访问控制上下文是空,则使用当前的访问上下文 + this.inheritedAccessControlContext = + acc != null ? acc : AccessController.getContext(); + this.target = target; + //设置线程优先级,线程优先级不可以比他所属线程组中其他的线程的优先级高,可以进去看这个方法的源码 + setPriority(priority); + if (parent.inheritableThreadLocals != null) + this.inheritableThreadLocals = + ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); + /* Stash the specified stack size in case the VM cares */ + //设置线程栈大小 + this.stackSize = stackSize; + + /* Set thread ID */ + //设置线程id + tid = nextThreadID(); + } +``` + +## Thread的native方法说明 + + + +```java + /** + * Returns a reference to the currently executing thread object. + * + * @return the currently executing thread. + */ + //返回当前正在执行的线程对象的引用 + public static native Thread currentThread(); + + /** + * A hint to the scheduler that the current thread is willing to yield + * its current use of a processor. The scheduler is free to ignore this + * hint. + * + *

Yield is a heuristic attempt to improve relative progression + * between threads that would otherwise over-utilise a CPU. Its use + * should be combined with detailed profiling and benchmarking to + * ensure that it actually has the desired effect. + * + *

It is rarely appropriate to use this method. It may be useful + * for debugging or testing purposes, where it may help to reproduce + * bugs due to race conditions. It may also be useful when designing + * concurrency control constructs such as the ones in the + * {@link java.util.concurrent.locks} package. + */ + //这个接口有点绕,通过其他博客和注释可以知道,执行这个方法可以让当前线程让出对cpu的执行权限, + //从执行状态转换为就绪状态,但是下次获取cpu执行权限的线程是比较随机的,也就是说当前线程和其他优先级更高或者更低的线程都 + //有机会获取下次cpu执行的时间片 + //通过注释可以知道操作系统的调度器有时候会忽略这个方法的调用,所以这个方法一般用于多线程并发调试或者进行调度控制,避免cpu + //被同一个线程占有很长时间 + public static native void yield(); + + /** + * Causes the currently executing thread to sleep (temporarily cease + * execution) for the specified number of milliseconds, subject to + * the precision and accuracy of system timers and schedulers. The thread + * does not lose ownership of any monitors. + * + * @param millis + * the length of time to sleep in milliseconds + * + * @throws IllegalArgumentException + * if the value of {@code millis} is negative + * + * @throws InterruptedException + * if any thread has interrupted the current thread. The + * interrupted status of the current thread is + * cleared when this exception is thrown. + */ + //调用这个方法会让当前线程让出millis时间的cpu执行权限,但是不会进入就绪状态,准确的来说是被操作系统挂起或者阻塞状态, + //虽然被挂起或者阻塞,那当前线程也不会失去对其拥有的监视器对象,睡眠时间一过就会通过操作系统的调度机制变为就绪状态 + public static native void sleep(long millis) throws InterruptedException; + + + /** + * Returns true if and only if the current thread holds the + * monitor lock on the specified object. + * + *

This method is designed to allow a program to assert that + * the current thread already holds a specified lock: + *

+     *     assert Thread.holdsLock(obj);
+     * 
+ * + * @param obj the object on which to test lock ownership + * @throws NullPointerException if obj is null + * @return true if the current thread holds the monitor lock on + * the specified object. + * @since 1.4 + */ + //这个方法其实有很多底层方法被使用,用来判断当前执行的线程是否拥有指定的对象锁的权限,如果有就返回true,否则返回false + + public static native boolean holdsLock(Object obj); + + + /** + * Tests if this thread is alive. A thread is alive if it has + * been started and has not yet died. + * + * @return true if this thread is alive; + * false otherwise. + */ + //线程是否存活,也就是说除了线程死了或者被回收了之外的状态都是存活包括挂起,阻塞,就绪等 + public final native boolean isAlive(); + + /** + * Tests if some Thread has been interrupted. The interrupted state + * is reset or not based on the value of ClearInterrupted that is + * passed. + */ + //判断线程是否被中断了,这个中断状态依据ClearInterrupted的值来重置 + private native boolean isInterrupted(boolean ClearInterrupted); + + + + //私有的本地方法 + private native static StackTraceElement[][] dumpThreads(Thread[] threads); + private native static Thread[] getThreads(); + + + /* Some private helper methods */ + //私有辅助类 + private native void setPriority0(int newPriority); + private native void stop0(Object o); + private native void suspend0(); + private native void resume0(); + private native void interrupt0(); + private native void setNativeName(String name); + private native void start0(); +``` + +## Thread的状态码说明 + + + +```dart +/** + * A thread state. A thread can be in one of the following states: + *
    + *
  • {@link #NEW}
    + + * A thread that has not yet started is in this state. + *
  • + *
  • {@link #RUNNABLE}
    + * A thread executing in the Java virtual machine is in this state. + *
  • + *
  • {@link #BLOCKED}
    + * A thread that is blocked waiting for a monitor lock + * is in this state. + *
  • + *
  • {@link #WAITING}
    + * A thread that is waiting indefinitely for another thread to + * perform a particular action is in this state. + *
  • + *
  • {@link #TIMED_WAITING}
    + * A thread that is waiting for another thread to perform an action + * for up to a specified waiting time is in this state. + *
  • + *
  • {@link #TERMINATED}
    + * A thread that has exited is in this state. + *
  • + *
+ * + *

+ * A thread can be in only one state at a given point in time. + * These states are virtual machine states which do not reflect + * any operating system thread states. + * + * @since 1.5 + * @see #getState + */ + public enum State { + /** + * Thread state for a thread which has not yet started. + */ + //线程已经创建,但是还没有启动 + NEW, + + /** + * Thread state for a runnable thread. A thread in the runnable + * state is executing in the Java virtual machine but it may + * be waiting for other resources from the operating system + * such as processor. + */ + //执行状态 线程正在执行,但是可能也在等一些操作系统的资源 + RUNNABLE, + + /** + * Thread state for a thread blocked waiting for a monitor lock. + * A thread in the blocked state is waiting for a monitor lock + * to enter a synchronized block/method or + * reenter a synchronized block/method after calling + * {@link Object#wait() Object.wait}. + */ + //阻塞状态,在等一个监视器锁对象,等待进入同步方法或者同步代码块 + BLOCKED, + + /** + * Thread state for a waiting thread. + * A thread is in the waiting state due to calling one of the + * following methods: + *

    + *
  • {@link Object#wait() Object.wait} with no timeout
  • + *
  • {@link #join() Thread.join} with no timeout
  • + *
  • {@link LockSupport#park() LockSupport.park}
  • + *
+ * + *

A thread in the waiting state is waiting for another thread to + * perform a particular action. + * + * For example, a thread that has called Object.wait() + * on an object is waiting for another thread to call + * Object.notify() or Object.notifyAll() on + * that object. A thread that has called Thread.join() + * is waiting for a specified thread to terminate. + */ + //等待状态, + WAITING, + + /** + * Thread state for a waiting thread with a specified waiting time. + * A thread is in the timed waiting state due to calling one of + * the following methods with a specified positive waiting time: + *

    + *
  • {@link #sleep Thread.sleep}
  • + *
  • {@link Object#wait(long) Object.wait} with timeout
  • + *
  • {@link #join(long) Thread.join} with timeout
  • + *
  • {@link LockSupport#parkNanos LockSupport.parkNanos}
  • + *
  • {@link LockSupport#parkUntil LockSupport.parkUntil}
  • + *
+ */ + 带有指定等待执行时间的等待状态 + TIMED_WAITING, + + /** + * Thread state for a terminated thread. + * The thread has completed execution. + */ + //线程终止状态,说明线程已经完成了执行的工作 + //也有可能是因为异常终止了 + TERMINATED; + } + + /** + * Returns the state of this thread. + * This method is designed for use in monitoring of the system state, + * not for synchronization control. + * 获取当前线程的状态 + * @return this thread's state. + * @since 1.5 + */ + public State getState() { + // get current thread state + return sun.misc.VM.toThreadState(threadStatus); + } +``` + +## Thread的其他方法说明(start,join) + + + +```dart + /** + * Causes this thread to begin execution; the Java Virtual Machine + * calls the run method of this thread. + *

+ * The result is that two threads are running concurrently: the + * current thread (which returns from the call to the + * start method) and the other thread (which executes its + * run method). + *

+ * It is never legal to start a thread more than once. + * In particular, a thread may not be restarted once it has completed + * execution. + * + * @exception IllegalThreadStateException if the thread was already + * started. + * @see #run() + * @see #stop() + */ + //线程启动的方法入口,不能重复启动,启动之后虚拟机会调用run方法执行run方法的代码 + + public synchronized void start() { + /** + * This method is not invoked for the main method thread or "system" + * group threads created/set up by the VM. Any new functionality added + * to this method in the future may have to also be added to the VM. + * + * A zero status value corresponds to state "NEW". + */ + if (threadStatus != 0) + throw new IllegalThreadStateException(); + + /* Notify the group that this thread is about to be started + * so that it can be added to the group's list of threads + * and the group's unstarted count can be decremented. */ + //这里如果线程已经执行了,那么线程组中的就绪状态的数量就会减1,然后被加入执行线程列表中 + group.add(this); + + boolean started = false; + try { + start0(); + started = true; + } finally { + try { + if (!started) { + group.threadStartFailed(this); + } + } catch (Throwable ignore) { + /* do nothing. If start0 threw a Throwable then + it will be passed up the call stack */ + } + } + } + + + /** + * Waits at most {@code millis} milliseconds for this thread to + * die. A timeout of {@code 0} means to wait forever. + * + *

This implementation uses a loop of {@code this.wait} calls + * conditioned on {@code this.isAlive}. As a thread terminates the + * {@code this.notifyAll} method is invoked. It is recommended that + * applications not use {@code wait}, {@code notify}, or + * {@code notifyAll} on {@code Thread} instances. + * + * @param millis + * the time to wait in milliseconds + * + * @throws IllegalArgumentException + * if the value of {@code millis} is negative + * + * @throws InterruptedException + * if any thread has interrupted the current thread. The + * interrupted status of the current thread is + * cleared when this exception is thrown. + */ + //使用join方法等待其他线程同步执行,比如主线程等待多少millis时间让子线程执行完毕 + public final synchronized void join(long millis) + throws InterruptedException { + long base = System.currentTimeMillis(); + long now = 0; + + if (millis < 0) { + throw new IllegalArgumentException("timeout value is negative"); + } + + if (millis == 0) { + while (isAlive()) { + wait(0); + } + } else { + while (isAlive()) { + long delay = millis - now; + if (delay <= 0) { + break; + } + wait(delay); + now = System.currentTimeMillis() - base; + } + } + } + + /** + * Waits at most {@code millis} milliseconds plus + * {@code nanos} nanoseconds for this thread to die. + * + *

This implementation uses a loop of {@code this.wait} calls + * conditioned on {@code this.isAlive}. As a thread terminates the + * {@code this.notifyAll} method is invoked. It is recommended that + * applications not use {@code wait}, {@code notify}, or + * {@code notifyAll} on {@code Thread} instances. + * + * @param millis + * the time to wait in milliseconds + * + * @param nanos + * {@code 0-999999} additional nanoseconds to wait + * + * @throws IllegalArgumentException + * if the value of {@code millis} is negative, or the value + * of {@code nanos} is not in the range {@code 0-999999} + * + * @throws InterruptedException + * if any thread has interrupted the current thread. The + * interrupted status of the current thread is + * cleared when this exception is thrown. + */ + public final synchronized void join(long millis, int nanos) + throws InterruptedException { + + if (millis < 0) { + throw new IllegalArgumentException("timeout value is negative"); + } + + if (nanos < 0 || nanos > 999999) { + throw new IllegalArgumentException( + "nanosecond timeout value out of range"); + } + + if (nanos >= 500000 || (nanos != 0 && millis == 0)) { + millis++; + } + + join(millis); + } + + /** + * Waits for this thread to die. + * + *

An invocation of this method behaves in exactly the same + * way as the invocation + * + *

+ * {@linkplain #join(long) join}{@code (0)} + *
+ * + * @throws InterruptedException + * if any thread has interrupted the current thread. The + * interrupted status of the current thread is + * cleared when this exception is thrown. + */ + public final void join() throws InterruptedException { + join(0); + } + +``` + +Thread总结: + +- 创建线程有两种方式继承Thread,实现Runnable,Thread类就是实现了Runnable,当然也可以实现Callable接口 +- 线程是虚拟机或者操作系统执行的最小单元 +- Thread类中包含了当前类加载上下文,也就是说在类加载过程中通过上下文类加载器进行加载而不是走双亲委派模型,当然这其实也是符合需求的。 +- 线程有很多状态,状态之间通过显示的调用或者隐式的挂起阻塞进行转换 +- Thread类中包含了ThreadLocal中的线程本地变量容器,这样的话就好理解ThreadLocal的作用了 +- Thread类中很多方法都过时了,这里没有进行分析,主要是线程终止,通过interrupt方法进行终止是相对合理的。 \ No newline at end of file diff --git a/week_05/51/Thread_51.md b/week_05/51/Thread_51.md new file mode 100644 index 0000000000000000000000000000000000000000..bc21cf91f9b89114fbaaceb79cd6cb4a929dbecd --- /dev/null +++ b/week_05/51/Thread_51.md @@ -0,0 +1,1071 @@ +jdk中的方法与jvm源码方法是具有对应关系的,通俗理解为注册表,具体可在openjdk\jdk\src\share\native\java\lang\Thread.c,现摘出部分映射关系: + + + +```cpp +static JNINativeMethod methods[] = { + {"start0", "()V", (void *)&JVM_StartThread}, + {"stop0", "(" OBJ ")V", (void *)&JVM_StopThread}, + {"isAlive", "()Z", (void *)&JVM_IsThreadAlive}, + {"suspend0", "()V", (void *)&JVM_SuspendThread}, + {"resume0", "()V", (void *)&JVM_ResumeThread}, + {"setPriority0", "(I)V", (void *)&JVM_SetThreadPriority}, + {"yield", "()V", (void *)&JVM_Yield}, + {"sleep", "(J)V", (void *)&JVM_Sleep}, + {"currentThread", "()" THD, (void *)&JVM_CurrentThread}, + {"countStackFrames", "()I", (void *)&JVM_CountStackFrames}, + {"interrupt0", "()V", (void *)&JVM_Interrupt}, + {"isInterrupted", "(Z)Z", (void *)&JVM_IsInterrupted}, + {"holdsLock", "(" OBJ ")Z", (void *)&JVM_HoldsLock}, + {"getThreads", "()[" THD, (void *)&JVM_GetAllThreads}, + {"dumpThreads", "([" THD ")[[" STE, (void *)&JVM_DumpThreads}, + {"setNativeName", "(" STR ")V", (void *)&JVM_SetNativeThreadName}, +}; +``` + +#### 一 线程创建与初始化 + +下面的代码片段已经加了详细的注释,在此就不说了,直接上干货吧 + + + +```csharp +//名称 + private volatile String name; + //优先级 + private int priority; + //守护线程标识 + private boolean daemon = false; + //线程执行的目标对象 + private Runnable target; + //线程组 + private ThreadGroup group; + //当前线程的指定栈大小,默认值为0,设置似乎意义不大,具体栈分配由jvm决定 + private long stackSize; + //线程序列号,为0 + private static long threadSeqNumber; + //线程id:由threadSeqNumber++生成 + private long tid; + //标识线程状态,默认是线程未启动 + /** + * 线程状态有如下几种:NEW RUNNABLE WAITING TIMED_WAITING TERMINATED + * NEW时对应的threadStatus为0; + * */ + private int threadStatus = 0; + //存储当前线程的局部变量 + ThreadLocal.ThreadLocalMap threadLocals = null; + /** + * 在创建子线程时,子线程会接收所有可继承的线程局部变量的初始值,以获得父线程所具有的值 + * 为子线程提供从父线程那里继承的值 + * */ + ThreadLocal.ThreadLocalMap inheritableThreadLocals = null; + //为LockSupport提供的变量,具体可查看LockSupport源码 + volatile Object parkBlocker; + //阻塞器锁,主要用于处理阻塞情况 + private volatile Interruptible blocker; + //阻断锁 + private Object blockerLock = new Object(); + //最低优先级 + public final static int MIN_PRIORITY = 1; + //默认优先级 + public final static int NORM_PRIORITY = 5; + //最高优先级 + public final static int MAX_PRIORITY = 10; + + /** + * Thread有多种构造器,这里只列出最全的构造方法 + * 所有构造器均调用init方法 + * */ + public Thread(ThreadGroup group, Runnable target, String name, long stackSize) { + init(group, target, name, stackSize, null, true); + } + + /** + * 底层init方法,用于初始化thread对象 + * 参数已在上述属性中做了说明 + * */ + private void init(ThreadGroup g, Runnable target, String name, long stackSize, + AccessControlContext acc, boolean inheritThreadLocals) { + if (name == null) { + throw new NullPointerException("name cannot be null"); + } + this.name = name; + /** + * 获取当前线程,即创建线程的线程 + * 这里体现了父子线程的关系 + * */ + Thread parent = currentThread(); + //获得系统的安全管理器 + SecurityManager security = System.getSecurityManager(); + if (g == null) { + if (security != null) { + g = security.getThreadGroup(); + } + //设置线程组,如果子线程未指定,则取父线程的 + if (g == null) { + g = parent.getThreadGroup(); + } + } + g.checkAccess(); + //线程组未启动线程个数++ + g.addUnstarted(); + //线程组 + this.group = g; + //守护线程继承性,子线程的是否守护取决于父线程 + this.daemon = parent.isDaemon(); + //优先级继承性,子线程的优先级取决于父线程 + this.priority = parent.getPriority(); + //Runnable对象 + this.target = target; + //设置优先级 + setPriority(priority); + if (inheritThreadLocals && parent.inheritableThreadLocals != null) + //为子线程提供从父线程那里继承的值 + this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); + this.stackSize = stackSize; + //生成线程ID + tid = nextThreadID(); + } + //tid生成器 + private static synchronized long nextThreadID() { + return ++threadSeqNumber; + } + public final ThreadGroup getThreadGroup() { + return this.group; + } + public final boolean isDaemon() { + return this.daemon; + } + public final int getPriority() { + return this.priority; + } + public final void setPriority(int priority) { + if (priority <= 10 && priority >= 1) { + ThreadGroup var2; + if ((var2 = this.getThreadGroup()) != null) { + //这里需要注意:线程的优先级上限取决于所属线程组的优先级 + if (priority > var2.getMaxPriority()) { + priority = var2.getMaxPriority(); + } + this.setPriority0(this.priority = priority); + } + } else { + throw new IllegalArgumentException(); + } + } + /** + * 线程执行的具体任务 + */ + public void run() { + if (target != null) { + target.run(); + } + } + /** + * 线程真正退出前执行清理 + */ + private void exit() { + if (group != null) { + group = null; + } + target = null; + threadLocals = null; + inheritableThreadLocals = null; + blocker = null; + } + //获取当前线程的native方法 + public static native Thread currentThread(); + //设置线程优先级的native方法 + private native void setPriority0(int var1); +``` + +在上述有两个native方法,即currentThread()和setPriority0(),其中currentThread是获取父线程,setPriority0是设置线程优先级,均在jvm.cpp中,[点击查看](https://links.jianshu.com/go?to=http%3A%2F%2Fhg.openjdk.java.net%2Fjdk8u%2Fjdk8u%2Fhotspot%2Ffile%2F79920693f915%2Fsrc%2Fshare%2Fvm%2Fprims%2Fjvm.cpp);现摘出具体片段: + currentThread方法底层调用jvm.cpp中的JVM_CurrentThread函数: + + + +```php +JVM_ENTRY(jobject, JVM_CurrentThread(JNIEnv* env, jclass threadClass)) + JVMWrapper("JVM_CurrentThread"); + oop jthread = thread->threadObj(); + assert (thread != NULL, "no current thread!"); + return JNIHandles::make_local(env, jthread); +JVM_END +``` + +setPriority0方法底层调用jvm.cpp中的JVM_CurrentThread函数: + + + +```cpp +JVM_ENTRY(void, JVM_SetThreadPriority(JNIEnv* env, jobject jthread, jint prio)) + JVMWrapper("JVM_SetThreadPriority"); + // 确保C++线程和OS线程在操作之前不释放 + MutexLocker ml(Threads_lock); + oop java_thread = JNIHandles::resolve_non_null(jthread); + java_lang_Thread::set_priority(java_thread, (ThreadPriority)prio); + JavaThread* thr = java_lang_Thread::thread(java_thread); + if (thr != NULL) { + // 线程尚未启动,当设置优先级才会启动 + Thread::set_priority(thr, (ThreadPriority)prio); + } +JVM_END +``` + +#### 二 线程启动start + +start代码片段如下: + + + +```csharp +/** + * 调用start()方法启动线程,执行线程的run方法 + */ + public synchronized void start() { + /** + * 线程状态校验,线程必须是0即新建态才能启动 + * 这也是为何一个线程连续两次调start会报错 + */ + if (threadStatus != 0) throw new IllegalThreadStateException(); + //通知线程组当前线程即将执行,同时线程组中未启动线程数-1 + group.add(this); + boolean started = false; + try { + //使线程进入可执行(runnable)状态 + start0(); + started = true; + } finally { + try { + if (!started) { + //启动失败后,修改线程组未启动线程数+1 + group.threadStartFailed(this); + } + } catch (Throwable ignore) { } + } + } + /** + * 设置线程启动的native方法 + * 底层会新启动一个线程,新线程才会调用传递过来的Runnable对象run方法 + * */ + private native void start0(); +``` + +start0方法底层调用jvm.cpp中的JVM_StartThread函数: + + + +```php +JVM_ENTRY(void, JVM_StartThread(JNIEnv* env, jobject jthread)) + JVMWrapper("JVM_StartThread"); + JavaThread *native_thread = NULL; + + //由于排序问题,引发异常时无法持有线程锁。示例:我们可能需要在构造异常时获取堆锁。 + bool throw_illegal_thread_state = false; + + //在线程start中发布jvmti事件之前,必须释放线程锁。 + { + //确保C++线程和OS线程在操作之前没有被释放。 + MutexLocker mu(Threads_lock); + + //自JDK5以来,线程的threadstatus用于防止重新启动已启动的线程,所以通常会发现javathread是空的。 + //但是对于JNI附加的线程,在创建的线程对象(及其JavaThread集合)和对其ThreadStates的更新之间有一个小窗口, + //因此我们必须检查这个窗口 + if (java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)) != NULL) { + throw_illegal_thread_state = true; + } else { + //我们还可以检查stillborn标志,看看这个线程是否已经停止,但是出于历史原因,我们让线程在它开始运行时检测它自己 + jlong size = java_lang_Thread::stackSize(JNIHandles::resolve_non_null(jthread)); + //分配C++线程结构并创建本地线程,从Java中传递过来的stack size已经被声明, + //但是构造函数采用size_t(无符号类型),因此避免传递负值 + size_t sz = size > 0 ? (size_t) size : 0; + native_thread = new JavaThread(&thread_entry, sz); + //此时可能由于内存不足而没有为javathread创建Osthread。 + //检查这种情况并抛出异常。最后,我们可能希望更改此项,以便仅在成功创建线程时获取锁, + //然后我们还可以执行此检查并在JavaThread构造函数中抛出异常。 + if (native_thread->osthread() != NULL) { + //注意:当前线程未在“准备”阶段使用 + native_thread->prepare(jthread); + } + } + } + if (throw_illegal_thread_state) { + THROW(vmSymbols::java_lang_IllegalThreadStateException()); + } + assert(native_thread != NULL, "Starting null thread?"); + if (native_thread->osthread() == NULL) { + // No one should hold a reference to the 'native_thread'. + delete native_thread; + if (JvmtiExport::should_post_resource_exhausted()) { + JvmtiExport::post_resource_exhausted( + JVMTI_RESOURCE_EXHAUSTED_OOM_ERROR | JVMTI_RESOURCE_EXHAUSTED_THREADS, + "unable to create new native thread"); + } + THROW_MSG(vmSymbols::java_lang_OutOfMemoryError(), + "unable to create new native thread"); + } + Thread::start(native_thread); + +JVM_END +``` + +#### 三 线程中断判断 + + + +```java +/** + * 判断线程是否已经中断,同时清除中断标识 + * static方法, + */ + public static boolean interrupted() { + return currentThread().isInterrupted(true); + } + /** + * 判断线程是否已经中断,不清除中断标识 + * this代表当前调用此方法的线程对象 + */ + public boolean isInterrupted() { + return this.isInterrupted(false); + } + /** + * native方法判断线程是否中断 + */ + private native boolean isInterrupted(boolean ClearInterrupted); +``` + +isInterrupted方法底层调用jvm.cpp中的JVM_IsInterrupted函数: + + + +```php +JVM_QUICK_ENTRY(jboolean, JVM_IsInterrupted(JNIEnv* env, jobject jthread, jboolean clear_interrupted)) + JVMWrapper("JVM_IsInterrupted"); + + //确保C++线程和OS线程在操作之前没有被释放 + oop java_thread = JNIHandles::resolve_non_null(jthread); + MutexLockerEx ml(thread->threadObj() == java_thread ? NULL : Threads_lock); + //我们需要重新解析java_thread,因为在获取锁的过程中可能会发生GC + JavaThread* thr = java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)); + if (thr == NULL) { + return JNI_FALSE; + } else { + return (jboolean) Thread::is_interrupted(thr, clear_interrupted != 0); + } +JVM_END +``` + +#### 四 线程join + + + +```java +/** + * 等待调用join的线程执行结束 + */ + public final synchronized void join(long var1) throws InterruptedException { + long var3 = System.currentTimeMillis(); + long var5 = 0L; + if (var1 < 0L) { + throw new IllegalArgumentException("timeout value is negative"); + } else { + //如果join时不设置超时,则会调用Object.wait的无超时等待 + if (var1 == 0L) { + while(this.isAlive()) { + this.wait(0L); + } + } else { + //join设置超时,则会调用Object.wait的超时等待 + while(this.isAlive()) { + long var7 = var1 - var5; + if (var7 <= 0L) { + break; + } + this.wait(var7); + var5 = System.currentTimeMillis() - var3; + } + } + + } + } + /** + * native方法判断线程存活 + */ + public final native boolean isAlive(); +``` + +Object.wait在下面讲述,isAlive方法底层调用jvm.cpp中的JVM_IsThreadAlive函数: + + + +```php +JVM_ENTRY(jboolean, JVM_IsThreadAlive(JNIEnv* env, jobject jthread)) + JVMWrapper("JVM_IsThreadAlive"); + + oop thread_oop = JNIHandles::resolve_non_null(jthread); + return java_lang_Thread::is_alive(thread_oop); +JVM_END +``` + +#### 五 线程sleep + + + +```java +/** + * 线程休眠 + * @param var0 毫秒 + * @param var2 纳秒 + */ + public static void sleep(long var0, int var2) throws InterruptedException { + if (var0 < 0L) { + throw new IllegalArgumentException("timeout value is negative"); + } else if (var2 >= 0 && var2 <= 999999) { + //纳秒四舍五入 + if (var2 >= 500000 || var2 != 0 && var0 == 0L) { + ++var0; + } + sleep(var0); + } else { + throw new IllegalArgumentException("nanosecond timeout value out of range"); + } + } + /** + * native方法线程休眠 + */ + public static native void sleep(long var0) throws InterruptedException; +``` + +sleep方法底层调用jvm.cpp中的JVM_Sleep函数: + + + +```rust +JVM_ENTRY(void, JVM_Sleep(JNIEnv* env, jclass threadClass, jlong millis)) + JVMWrapper("JVM_Sleep"); + + if (millis < 0) { + THROW_MSG(vmSymbols::java_lang_IllegalArgumentException(), "timeout value is negative"); + } + //线程中断则抛出异常 + if (Thread::is_interrupted (THREAD, true) && !HAS_PENDING_EXCEPTION) { + THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted"); + } + //保存当前线程状态并在末尾还原它,并将新线程状态设置为SLEEPING + JavaThreadSleepState jtss(thread); + +#ifndef USDT2 + HS_DTRACE_PROBE1(hotspot, thread__sleep__begin, millis); +#else /* USDT2 */ + HOTSPOT_THREAD_SLEEP_BEGIN( + millis); +#endif /* USDT2 */ + + EventThreadSleep event; + + if (millis == 0) { + //当convertsleeptoyield为on时,这与JVM_Sleep的经典VM实现相匹配。 + //对于类似的线程行为(win32)至关重要,即在某些GUI上下文中,对Solaris进行短时间睡眠是有益的。 + if (ConvertSleepToYield) { + os::yield(); + } else { + ThreadState old_state = thread->osthread()->get_state(); + thread->osthread()->set_state(SLEEPING); + os::sleep(thread, MinSleepInterval, false); + thread->osthread()->set_state(old_state); + } + } else { + ThreadState old_state = thread->osthread()->get_state(); + thread->osthread()->set_state(SLEEPING); + if (os::sleep(thread, millis, true) == OS_INTRPT) { + //当休眠时,一个异步异常(例如,threaddeathexception)可能抛出了,但不需要覆盖它们。 + if (!HAS_PENDING_EXCEPTION) { + if (event.should_commit()) { + event.set_time(millis); + event.commit(); + } +#ifndef USDT2 + HS_DTRACE_PROBE1(hotspot, thread__sleep__end,1); +#else /* USDT2 */ + HOTSPOT_THREAD_SLEEP_END( + 1); +#endif /* USDT2 */ + + //THROW_MSG方法返回,意味着不能以正确地还原线程状态,因为那很可能是错的。 + THROW_MSG(vmSymbols::java_lang_InterruptedException(), "sleep interrupted"); + } + } + thread->osthread()->set_state(old_state); + } + if (event.should_commit()) { + event.set_time(millis); + event.commit(); + } +#ifndef USDT2 + HS_DTRACE_PROBE1(hotspot, thread__sleep__end,0); +#else /* USDT2 */ + HOTSPOT_THREAD_SLEEP_END( + 0); +#endif /* USDT2 */ +JVM_END +``` + +#### 六 线程yield + + + +```cpp +/** + * native方法线程让度CPU执行权 + */ + public static native void yield(); +``` + +yield方法底层调用jvm.cpp中的JVM_Yield函数: + + + +```cpp +JVM_ENTRY(void, JVM_Yield(JNIEnv *env, jclass threadClass)) + JVMWrapper("JVM_Yield"); + if (os::dont_yield()) return; +#ifndef USDT2 + HS_DTRACE_PROBE0(hotspot, thread__yield); +#else /* USDT2 */ + HOTSPOT_THREAD_YIELD(); +#endif /* USDT2 */ + //当ConvertYieldToSleep为off(默认)时,这与传统的VM使用yield相匹配,对于类似的线程行为至关重要 + if (ConvertYieldToSleep) {//on + //系统调用sleep + os::sleep(thread, MinSleepInterval, false); + } else {//off + //系统调用yield + os::yield(); + } +JVM_END +``` + +#### 七 线程中断interrupt + + + +```kotlin +/** + * 线程中断 + */ + public void interrupt() { + Object var1 = this.blockerLock; + synchronized(this.blockerLock) { + Interruptible var2 = this.blocker; + if (var2 != null) { + this.interrupt0(); + var2.interrupt(this); + return; + } + } + this.interrupt0(); + } + /** + * native方法线程中断 + */ + private native void interrupt0(); +``` + +interrupt0方法底层调用jvm.cpp中的JVM_Interrupt函数: + + + +```php +JVM_ENTRY(void, JVM_Interrupt(JNIEnv* env, jobject jthread)) + JVMWrapper("JVM_Interrupt"); + + //确保C++线程和OS线程在操作之前没有被释放 + oop java_thread = JNIHandles::resolve_non_null(jthread); + MutexLockerEx ml(thread->threadObj() == java_thread ? NULL : Threads_lock); + //我们需要重新解析java_thread,因为在获取锁的过程中可能会发生GC + JavaThread* thr = java_lang_Thread::thread(JNIHandles::resolve_non_null(jthread)); + if (thr != NULL) { + Thread::interrupt(thr); + } +JVM_END +``` + +#### 七 Object的Wait/Notify/NotifyAll + + + +```java +/** + * 线程等待 + * @param var1 毫秒 + * @param var3 纳秒 + */ + public final void wait(long var1, int var3) throws InterruptedException { + if (var1 < 0L) { + throw new IllegalArgumentException("timeout value is negative"); + } else if (var3 >= 0 && var3 <= 999999) { + //纳秒>0,毫秒直接++ + if (var3 > 0) { + ++var1; + } + //调用native方法 + this.wait(var1); + } else { + throw new IllegalArgumentException("nanosecond timeout value out of range"); + } + } + /** + * native方法线程等待 + */ + public final native void wait(long var1) throws InterruptedException; + /** + * native方法线程单个唤醒 + */ + public final native void notify(); + /** + * native方法线程唤醒等待池中所有线程 + */ + public final native void notifyAll(); +``` + +Wait/Notify/NotifyAll在objectMonitor.cpp中,[点击查看](https://links.jianshu.com/go?to=http%3A%2F%2Fhg.openjdk.java.net%2Fjdk8u%2Fjdk8u%2Fhotspot%2Ffile%2F677234770800%2Fsrc%2Fshare%2Fvm%2Fruntime%2FobjectMonitor.cpp); + Wait片段: + + + +```php +void ObjectMonitor::wait(jlong millis, bool interruptible, TRAPS) { + Thread * const Self = THREAD ; + assert(Self->is_Java_thread(), "Must be Java thread!"); + JavaThread *jt = (JavaThread *)THREAD; + + DeferredInitialize () ; + + // Throw IMSX or IEX. + CHECK_OWNER(); + + //调用is_interrupted()判断并清除线程中断状态,如果中断状态为true,抛出中断异常并结束 + if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) { + //post monitor waited event + //注意这是过去式,已经等待完了 + if (JvmtiExport::should_post_monitor_waited()) { + //注意:这里传递参数'false',这是因为由于线程中断,等待不会超时 + JvmtiExport::post_monitor_waited(jt, this, false); + } + TEVENT (Wait - Throw IEX) ; + THROW(vmSymbols::java_lang_InterruptedException()); + return ; + } + TEVENT (Wait) ; + + assert (Self->_Stalled == 0, "invariant") ; + Self->_Stalled = intptr_t(this) ; + jt->set_current_waiting_monitor(this); + + // create a node to be put into the queue + // Critically, after we reset() the event but prior to park(), we must check + // for a pending interrupt. + + //创建一个node放入队列 + //关键是,在reset()之后,但在park()之前,必须检查是否有挂起的中断 + ObjectWaiter node(Self); + node.TState = ObjectWaiter::TS_WAIT ; + Self->_ParkEvent->reset() ; + OrderAccess::fence(); + + //在本例中等待队列是一个循环的双向链表,但它也可以是一个优先级队列或任何数据结构。 + //_WaitSetLock保护着等待队列. + //通常,等待队列只能由监视器*except*的所有者访问,但在park()因中断超时而返回的情况下也是可以。 + //竞争非常小,所以使用一个自旋锁而不是重量级的阻塞锁。 + Thread::SpinAcquire (&_WaitSetLock, "WaitSet - add") ; + AddWaiter (&node) ; + Thread::SpinRelease (&_WaitSetLock) ; + + if ((SyncFlags & 4) == 0) { + _Responsible = NULL ; + } + intptr_t save = _recursions; // 记录旧的递归次数 + _waiters++; // waiters 自增 + _recursions = 0; // 设置 recursion level to be 1 + exit (Self) ; // 退出监视器 + guarantee (_owner != Self, "invariant") ; + + //一旦在上面的exit()调用中删除了ObjectMonitor的所有权, + //另一个线程就可以进入ObjectMonitor,执行notify()和exit()对象监视器。 + //如果另一个线程的exit()调用选择此线程作为后继者,并且此线程在发布MONITOR_CONTENDED_EXIT时发生unpark()调用, + //则我们使用RawMonitors运行事件风险处理,并使用unpark(). + //为了避免这个问题,我们重新发布事件,即使未使用原来的unpark(), + //这也不会造成任何伤害,因为已经为此监视器选好了继任者。 + if (node._notified != 0 && _succ == Self) { + node._event->unpark(); + } + + // The thread is on the WaitSet list - now park() it. + // On MP systems it's conceivable that a brief spin before we park + // could be profitable. + // + // TODO-FIXME: change the following logic to a loop of the form + // while (!timeout && !interrupted && _notified == 0) park() + + int ret = OS_OK ; + int WasNotified = 0 ; + { // State transition wrappers + OSThread* osthread = Self->osthread(); + OSThreadWaitState osts(osthread, true); + { + ThreadBlockInVM tbivm(jt); + // Thread is in thread_blocked state and oop access is unsafe. + //线程处于阻塞状态,并且oop访问是不安全的 + jt->set_suspend_equivalent(); + + if (interruptible && (Thread::is_interrupted(THREAD, false) || HAS_PENDING_EXCEPTION)) { + // Intentionally empty 空处理 + } else + if (node._notified == 0) { + if (millis <= 0) { + // 调用park()方法阻塞线程 + Self->_ParkEvent->park () ; + } else { + // 调用park()方法在超时时间内阻塞线程 + ret = Self->_ParkEvent->park (millis) ; + } + } + + // were we externally suspended while we were waiting? + if (ExitSuspendEquivalent (jt)) { + // TODO-FIXME: add -- if succ == Self then succ = null. + jt->java_suspend_self(); + } + + } // Exit thread safepoint: transition _thread_blocked -> _thread_in_vm + + //当线程不在等待队列时,使用双重检查锁定避免获取_WaitSetLock + if (node.TState == ObjectWaiter::TS_WAIT) { + Thread::SpinAcquire (&_WaitSetLock, "WaitSet - unlink") ; + if (node.TState == ObjectWaiter::TS_WAIT) { + DequeueSpecificWaiter (&node) ; // unlink from WaitSet + assert(node._notified == 0, "invariant"); + node.TState = ObjectWaiter::TS_RUN ; + } + Thread::SpinRelease (&_WaitSetLock) ; + } + + //从这个线程的角度来看,Node's TState是稳定的, + //没有其他线程能够异步修改TState + guarantee (node.TState != ObjectWaiter::TS_WAIT, "invariant") ; + OrderAccess::loadload() ; + if (_succ == Self) _succ = NULL ; + WasNotified = node._notified ; + + // Reentry phase -- reacquire the monitor. + // re-enter contended(竞争) monitor after object.wait(). + // retain OBJECT_WAIT state until re-enter successfully completes + // Thread state is thread_in_vm and oop access is again safe, + // although the raw address of the object may have changed. + // (Don't cache naked oops over safepoints, of course). + + // post monitor waited event. + //注意这是过去式,已经等待完了 + if (JvmtiExport::should_post_monitor_waited()) { + JvmtiExport::post_monitor_waited(jt, this, ret == OS_TIMEOUT); + } + OrderAccess::fence() ; + + assert (Self->_Stalled != 0, "invariant") ; + Self->_Stalled = 0 ; + + assert (_owner != Self, "invariant") ; + ObjectWaiter::TStates v = node.TState ; + if (v == ObjectWaiter::TS_RUN) { + enter (Self) ; + } else { + guarantee (v == ObjectWaiter::TS_ENTER || v == ObjectWaiter::TS_CXQ, "invariant") ; + ReenterI (Self, &node) ; + node.wait_reenter_end(this); + } + + // Self has reacquired the lock. + // Lifecycle - the node representing Self must not appear on any queues. + // Node is about to go out-of-scope, but even if it were immortal(长久的) we wouldn't + // want residual(残留的) elements associated with this thread left on any lists. + guarantee (node.TState == ObjectWaiter::TS_RUN, "invariant") ; + assert (_owner == Self, "invariant") ; + assert (_succ != Self , "invariant") ; + } // OSThreadWaitState() + + jt->set_current_waiting_monitor(NULL); + + guarantee (_recursions == 0, "invariant") ; + _recursions = save; // restore the old recursion count + _waiters--; // decrement the number of waiters + + // Verify a few postconditions + assert (_owner == Self , "invariant") ; + assert (_succ != Self , "invariant") ; + assert (((oop)(object()))->mark() == markOopDesc::encode(this), "invariant") ; + + if (SyncFlags & 32) { + OrderAccess::fence() ; + } + + //检查是否有通知notify发生 + // 从park()方法返回后,判断是否是因为中断返回,再次调用 + // thread::is_interrupted(Self, true)判断并清除线程中断状态 + // 如果中断状态为true,抛出中断异常并结束。 + if (!WasNotified) { + // no, it could be timeout or Thread.interrupt() or both + // check for interrupt event, otherwise it is timeout + if (interruptible && Thread::is_interrupted(Self, true) && !HAS_PENDING_EXCEPTION) { + TEVENT (Wait - throw IEX from epilog) ; + THROW(vmSymbols::java_lang_InterruptedException()); + } + } + //注意:虚假唤醒将被视为超时;监视器通知优先于线程中断。 +} +``` + +Notify片段: + + + +```php +void ObjectMonitor::notify(TRAPS) { + CHECK_OWNER(); + if (_WaitSet == NULL) { + TEVENT (Empty-Notify) ; + return ; + } + DTRACE_MONITOR_PROBE(notify, this, object(), THREAD); + + int Policy = Knob_MoveNotifyee ; + + Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notify") ; + ObjectWaiter * iterator = DequeueWaiter() ; + if (iterator != NULL) { + TEVENT (Notify1 - Transfer) ; + guarantee (iterator->TState == ObjectWaiter::TS_WAIT, "invariant") ; + guarantee (iterator->_notified == 0, "invariant") ; + if (Policy != 4) { + iterator->TState = ObjectWaiter::TS_ENTER ; + } + iterator->_notified = 1 ; + + ObjectWaiter * List = _EntryList ; + if (List != NULL) { + assert (List->_prev == NULL, "invariant") ; + assert (List->TState == ObjectWaiter::TS_ENTER, "invariant") ; + assert (List != iterator, "invariant") ; + } + + if (Policy == 0) { // prepend(预追加) to EntryList + if (List == NULL) { + iterator->_next = iterator->_prev = NULL ; + _EntryList = iterator ; + } else { + List->_prev = iterator ; + iterator->_next = List ; + iterator->_prev = NULL ; + _EntryList = iterator ; + } + } else + if (Policy == 1) { // append(真正追加) to EntryList + if (List == NULL) { + iterator->_next = iterator->_prev = NULL ; + _EntryList = iterator ; + } else { + //考虑:当前获取EntryList的tail需要遍历整个链表 + //将tail访问转换为CDLL而不是使用当前的DLL,从而使访问时间固定。 + ObjectWaiter * Tail ; + for (Tail = List ; Tail->_next != NULL ; Tail = Tail->_next) ; + assert (Tail != NULL && Tail->_next == NULL, "invariant") ; + Tail->_next = iterator ; + iterator->_prev = Tail ; + iterator->_next = NULL ; + } + } else + if (Policy == 2) { // prepend to cxq + // prepend(预追加) to cxq + if (List == NULL) { + iterator->_next = iterator->_prev = NULL ; + _EntryList = iterator ; + } else { + iterator->TState = ObjectWaiter::TS_CXQ ; + for (;;) { + ObjectWaiter * Front = _cxq ; + iterator->_next = Front ; + if (Atomic::cmpxchg_ptr (iterator, &_cxq, Front) == Front) { + break ; + } + } + } + } else + if (Policy == 3) { // append(真正追加) to cxq + iterator->TState = ObjectWaiter::TS_CXQ ; + for (;;) { + ObjectWaiter * Tail ; + Tail = _cxq ; + if (Tail == NULL) { + iterator->_next = NULL ; + if (Atomic::cmpxchg_ptr (iterator, &_cxq, NULL) == NULL) { + break ; + } + } else { + while (Tail->_next != NULL) Tail = Tail->_next ; + Tail->_next = iterator ; + iterator->_prev = Tail ; + iterator->_next = NULL ; + break ; + } + } + } else { + ParkEvent * ev = iterator->_event ; + iterator->TState = ObjectWaiter::TS_RUN ; + OrderAccess::fence() ; + ev->unpark() ; + } + + if (Policy < 4) { + iterator->wait_reenter_begin(this); + } + + // _WaitSetLock protects the wait queue, not the EntryList. We could + // move the add-to-EntryList operation, above, outside the critical section + // protected by _WaitSetLock. In practice that's not useful. With the + // exception of wait() timeouts and interrupts the monitor owner + // is the only thread that grabs _WaitSetLock. There's almost no contention + // on _WaitSetLock so it's not profitable to reduce the length of the + // critical section. + } + Thread::SpinRelease (&_WaitSetLock) ; + if (iterator != NULL && ObjectMonitor::_sync_Notifications != NULL) { + ObjectMonitor::_sync_Notifications->inc() ; + } +} +``` + +NotifyAll片段: + + + +```php +void ObjectMonitor::notifyAll(TRAPS) { + CHECK_OWNER(); + ObjectWaiter* iterator; + if (_WaitSet == NULL) { + TEVENT (Empty-NotifyAll) ; + return ; + } + DTRACE_MONITOR_PROBE(notifyAll, this, object(), THREAD); + + int Policy = Knob_MoveNotifyee ; + int Tally = 0 ; + Thread::SpinAcquire (&_WaitSetLock, "WaitSet - notifyall") ; + + for (;;) { + iterator = DequeueWaiter () ; + if (iterator == NULL) break ; + TEVENT (NotifyAll - Transfer1) ; + ++Tally ; + + // Disposition - what might we do with iterator ? + // a. add it directly to the EntryList - either tail or head. + // b. push it onto the front of the _cxq. + // For now we use (a). + + guarantee (iterator->TState == ObjectWaiter::TS_WAIT, "invariant") ; + guarantee (iterator->_notified == 0, "invariant") ; + iterator->_notified = 1 ; + if (Policy != 4) { + iterator->TState = ObjectWaiter::TS_ENTER ; + } + + ObjectWaiter * List = _EntryList ; + if (List != NULL) { + assert (List->_prev == NULL, "invariant") ; + assert (List->TState == ObjectWaiter::TS_ENTER, "invariant") ; + assert (List != iterator, "invariant") ; + } + + if (Policy == 0) { // prepend to EntryList + if (List == NULL) { + iterator->_next = iterator->_prev = NULL ; + _EntryList = iterator ; + } else { + List->_prev = iterator ; + iterator->_next = List ; + iterator->_prev = NULL ; + _EntryList = iterator ; + } + } else + if (Policy == 1) { // append to EntryList + if (List == NULL) { + iterator->_next = iterator->_prev = NULL ; + _EntryList = iterator ; + } else { + // CONSIDER: finding the tail currently requires a linear-time walk of + // the EntryList. We can make tail access constant-time by converting to + // a CDLL instead of using our current DLL. + ObjectWaiter * Tail ; + for (Tail = List ; Tail->_next != NULL ; Tail = Tail->_next) ; + assert (Tail != NULL && Tail->_next == NULL, "invariant") ; + Tail->_next = iterator ; + iterator->_prev = Tail ; + iterator->_next = NULL ; + } + } else + if (Policy == 2) { // prepend to cxq + // prepend to cxq + iterator->TState = ObjectWaiter::TS_CXQ ; + for (;;) { + ObjectWaiter * Front = _cxq ; + iterator->_next = Front ; + if (Atomic::cmpxchg_ptr (iterator, &_cxq, Front) == Front) { + break ; + } + } + } else + if (Policy == 3) { // append to cxq + iterator->TState = ObjectWaiter::TS_CXQ ; + for (;;) { + ObjectWaiter * Tail ; + Tail = _cxq ; + if (Tail == NULL) { + iterator->_next = NULL ; + if (Atomic::cmpxchg_ptr (iterator, &_cxq, NULL) == NULL) { + break ; + } + } else { + while (Tail->_next != NULL) Tail = Tail->_next ; + Tail->_next = iterator ; + iterator->_prev = Tail ; + iterator->_next = NULL ; + break ; + } + } + } else { + ParkEvent * ev = iterator->_event ; + iterator->TState = ObjectWaiter::TS_RUN ; + OrderAccess::fence() ; + ev->unpark() ; + } + + if (Policy < 4) { + iterator->wait_reenter_begin(this); + } + + // _WaitSetLock protects the wait queue, not the EntryList. We could + // move the add-to-EntryList operation, above, outside the critical section + // protected by _WaitSetLock. In practice that's not useful. With the + // exception of wait() timeouts and interrupts the monitor owner + // is the only thread that grabs _WaitSetLock. There's almost no contention + // on _WaitSetLock so it's not profitable to reduce the length of the + // critical section. + } + + Thread::SpinRelease (&_WaitSetLock) ; + + if (Tally != 0 && ObjectMonitor::_sync_Notifications != NULL) { + ObjectMonitor::_sync_Notifications->inc(Tally) ; + } +} +``` \ No newline at end of file diff --git "a/week_05/51/\345\217\214\344\272\262\345\247\224\346\264\276_51.md" "b/week_05/51/\345\217\214\344\272\262\345\247\224\346\264\276_51.md" new file mode 100644 index 0000000000000000000000000000000000000000..d8b328a993c48021988981a43667af15a63466df --- /dev/null +++ "b/week_05/51/\345\217\214\344\272\262\345\247\224\346\264\276_51.md" @@ -0,0 +1,425 @@ +# 前言 + +- 了解 类加载器 有利用在类初始化时进行一些功能操作 +- 本文全面讲解类加载器,希望你们会喜欢。 + + + +![img](https:////upload-images.jianshu.io/upload_images/944365-0adcc90ec5514c9f.png?imageMogr2/auto-orient/strip|imageView2/2/w/842/format/webp) + +示意图 + +------ + +# 目录 + +![img](https:////upload-images.jianshu.io/upload_images/944365-f06b4b223d677eac.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp) + +目录 + +------ + +# 1. 作用 + +1. 实现类加载的功能 +2. 确定被加载类 在 `Java`虚拟机中 的 唯一性 + +下面我会进行详细讲解。 + +### 1.1 实现类加载的功能 + +即实现 类加载过程中“加载”环节里 **“通过类的全限定名来获取定义此类的二进制字节流”** 的功能 + +> 具体请看我写的文章:[(JVM)Java虚拟机:类加载的5个过程](https://www.jianshu.com/p/3ca14ec823d7) + +### 1.2 确立 被加载类 在 `Java`虚拟机中 的 唯一性 + +- 确定 两个类是否 相等 的依据: + + 是否由同一个类加载器加载 + + 1. 若 由同一个类加载器 加载,则这两个类相等; + 2. 若 由不同的类加载器 加载,则这两个类不相等。 + +> 即使两个类来源于同一个 `Class` 文件、被同一个虚拟机加载,这两个类都不相等 + +- 在实际使用中,是通过下面方法的返回结果( + + ``` + Boolean + ``` + + 值)进行判断: + + 1. `Class`对象的`equals()`方法 + 2. `Class`对象的`isAssignableFrom()`方法 + 3. `Class`对象的`isInstance()`方法 + +> 当然也会使用instanceof关键字做对象所属关系判定等情况 + +- 实例说明 + 下面我将举个例子来说明: + + + +```kotlin +public class Test { + + // 自定义一个类加载器:myLoader + // 作用:可加载与自己在同一路径下的Class文件 + static ClassLoader myLoader = new ClassLoader() { + @Override + public Class loadClass(String name) throws ClassNotFoundException { + + if (!name.equals("com.carson.Test")) + return super.loadClass(name); + + try { + String fileName = name.substring(name.lastIndexOf(".") + 1) + + ".class"; + + InputStream is = getClass().getResourceAsStream(fileName); + if (is == null) { + return super.loadClass(fileName); + } + byte[] b = new byte[is.available()]; + is.read(b); + return defineClass(name, b, 0, b.length); + + } catch (IOException e) { + throw new ClassNotFoundException(name); + } + } + }; + + public static void main(String[] args) throws Exception { + + Object obj = myLoader.loadClass("com.carson.Test"); + // 1. 使用该自定义类加载器加载一个名为com.carson.Test的类 + // 2. 实例化该对象 + + System.out.println(obj); + // 输出该对象的类 ->>第一行结果分析 + + System.out.println(obj instanceof com.carson.Test); + // 判断该对象是否属于com.carson.Test类 ->>第二行结果分析 + + } + +} + +<-- 输出结果 --> +class com.carson.Test +false + +// 第一行结果分析 +// obj对象确实是com.carson.Test类实例化出来的对象 + +// 第二行结果分析 +// obj对象与类com.huachao.Test做所属类型检查时却返回了false +// 原因:虚拟机中存在了两个Test类(1 & 2):1是由系统应用程序类加载器加载的,2是由我们自定义的类加载器加载 +// 虽然都是来自同一个class文件,但由于由不同类加载器加载,所以依然是两个独立的类 +// 做对象所属类型检查结果自然为false。 +``` + +------ + +# 2. 类加载器的类型 + +- 类加载器的类型数量分别从 `Java`虚拟机 & `Java`开发者的角度来看,如下图 + +![img](https:////upload-images.jianshu.io/upload_images/944365-155021a5b830cf48.png?imageMogr2/auto-orient/strip|imageView2/2/w/1200/format/webp) + +示意图 + +- 下面主要讲解从 + + ``` + Java + ``` + + 开发者角度看的类加载器,即讲解: + + 1. 启动类加载器 + 2. 扩展类加载器 + 3. 应用程序类加载器 + +### 2.1 启动类加载器(Bootstrap ClassLoader) + +- 作用 + + 负责加载以下类: + + 1. 存放在`\lib`目录中的类 + 2. 被`-Xbootclasspath`参数所指定路径中、并且是被虚拟机识别的类库 + +> 仅按文件名识别,如:`rt.jar`,名字不符合的类库即使放在lib目录中也不会被加载 + +- 特别注意 + 1. 启动类加载器 无法 被`Java`程序直接引用 + 2. 用户在编写自定义类加载器时,若需把 加载请求 委派 给 引导类加载器,直接使用`null`代替即可,如`java.lang.ClassLoader.getClassLoader()`方法所示: + + + +```kotlin +@CallerSensitive +public ClassLoader getClassLoader() { + ClassLoader cl = getClassLoader0(); + if (cl == null) + return null; + SecurityManager sm = System.getSecurityManager(); + if (sm != null) { + ClassLoader.checkClassLoaderPermission(cl, Reflection.getCallerClass()); + } + return cl; +} +``` + +### 2.2 扩展类加载器(Extension ClassLoader) + +- 作用: + 负责加载以下类: + 1. `\lib\ext`目录中的类库 + 2. 被`java.ext.dirs`系统变量所指定的路径中的所有类库 +- 特别注意 + 1. 由`sum.misc.Launcher$ExtClassLoader`类实现 + 2. 开发者可以直接使用扩展类加载器 + +### 2.3 应用程序类加载器(Application ClassLoader) + +- 作用: + 负责加载 用户类路径(`ClassPath`)上所指定的类库 +- 特别注意 + 1. 也称为系统类加载器,因为该类加载器是`ClassLoader`中的`getSystemClassLoader()`方法的返回值 + 2. 由`sum.misc.Launcher$AppClassLoader`类实现 + 3. 开发者可以直接使用该类加载器 + 4. 若开发者 没 自定义类加载器,程序默认使用该类加载器 + +------ + +- 各种类加载器的使用并不是孤立的,而是相互配合使用 +- 在`Java`虚拟机中,各种类加载器 配合使用 的 模型(关系)是 **双亲委派模型** + +下面我将详细讲解。 + +------ + +# 3. 双亲委派模型 + +### 3.1 模型说明 + +![img](https:////upload-images.jianshu.io/upload_images/944365-cdd4548d762686c2.png?imageMogr2/auto-orient/strip|imageView2/2/w/690/format/webp) + +示意图 + +### 3.2 工作流程讲解 + +- 双亲委派模型的工作流程代码实现在`java.lang.ClassLoader的loadClass()`中 +- 具体如下 + + + +```swift +@Override +protected Class loadClass(String name, boolean resolve) + throws ClassNotFoundException { + Class c = findLoadedClass(name); + + // 检查需要加载的类是否已经被加载过 + if (c == null) { + try { + // 若没有加载,则调用父加载器的loadClass()方法 + if (parent != null) { + c = parent.loadClass(name, false); + }else{ + // 若父类加载器为空,则默认使用启动类加载器作为父加载器 + c=findBootstrapClassOrNull(name); + } + } catch (ClassNotFoundException e) { + // 若父类加载器加载失败会抛出ClassNotFoundException, + //说明父类加载器无法完成加载请求 + } + if(c==null){ + // 在父类加载器无法加载时 + // 再调用本身的findClass方法进行类加载 + c=findClass(name); + } + } + if(resolve){ + resolveClass(c); + } + return c; +} +``` + +**步骤总结**:若一个类加载器收到了类加载请求 + +1. 把 该类加载请求 委派给 **父类加载器**去完成,而不会自己去加载该类 + +> 每层的类加载器都是如此,因此所有的加载请求最终都应传送到顶层的启动类加载器中 + +1. 只有当 父类加载器 反馈 自己无法完成该加载请求(它的搜索范围中没有找到所需的类)时,子加载器才会自己去加载 + +### 3.3 优点 + +`Java`类随着它的类加载器一起具备了一种带优先级的层次关系 + +> 1. 如:类 `java.lang.Object`(存放在`rt.jar`中)在加载过程中,无论哪一个类加载器要加载这个类,最终需委派给模型顶端的启动类加载器进行加载,因此`Object`类在程序的各种类加载器环境中都是同一个类。 +> 2. 若没有使用双亲委派模型(即由各个类加载器自行去加载)、用户编写了一个`java.lang.Object`的类(放在`ClassPath`中),那系统中将出现多个不同的`Object`类,Java体系中最基础的行为就无法保证 + +------ + +在讲完系统的类加载器后,下面我将讲解如何根据需求自定义类加载器。 + +------ + +# 4. 自定义类加载器 + +主要是通过继承自ClassLoader类 从而自定义一个类加载器 + *MyClassLoader.java* + + + +```kotlin +// 继承自ClassLoader类 +public class MyClassLoader extends ClassLoader { + // 类加载器的名称 + private String name; + // 类存放的路径 + private String classpath = "E:/"; + + MyClassLoader(String name) { + this.name = name; + } + + MyClassLoader(ClassLoader parent, String name) { + super(parent); + this.name = name; + } + + @Override + public Class findClass(String name) { + byte[] data = loadClassData(name); + return this.defineClass(name, data, 0, data.length); + } + + public byte[] loadClassData(String name) { + try { + name = name.replace(".", "//"); + System.out.println(name); + FileInputStream is = new FileInputStream(new File(classpath + name + + ".class")); + byte[] data = new byte[is.available()]; + is.read(data); + is.close(); + return data; + + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} +``` + +------ + +下面我将用一个实例来说明如何自定义类加载器 & 使用。 + +**步骤1:自定义类加载器`MyClassLoader`** + *MyClassLoader.java* + + + +```kotlin +// 继承自ClassLoader类 +public class MyClassLoader extends ClassLoader { + // 类加载器的名称 + private String name; + // 类存放的路径 + private String classpath = "E:/"; + + MyClassLoader(String name) { + this.name = name; + } + + MyClassLoader(ClassLoader parent, String name) { + super(parent); + this.name = name; + } + + @Override + public Class findClass(String name) { + byte[] data = loadClassData(name); + return this.defineClass(name, data, 0, data.length); + } + + public byte[] loadClassData(String name) { + try { + name = name.replace(".", "//"); + System.out.println(name); + FileInputStream is = new FileInputStream(new File(classpath + name + + ".class")); + byte[] data = new byte[is.available()]; + is.read(data); + is.close(); + return data; + + } catch (Exception e) { + e.printStackTrace(); + } + return null; + } +} +``` + +**步骤2:定义待加载的类** + *TestObject.java* + + + +```csharp +public class TestObject { + public void print() { + System.out.println("hello DiyClassLoader"); + + } +} +``` + +**步骤3:定义测试类** + *Test.java* + + + +```java +public class Test { + + public static void main(String[] args) throws InstantiationException, + IllegalAccessException, ClassNotFoundException { + + MyClassLoader cl = new MyClassLoader("myClassLoader"); + // 步骤1:创建自定义类加载器对象 + + Class clazz = cl.loadClass("com.carson.TestObject"); + // 步骤2:加载定义的测试类:myClassLoader类 + + TestObject test= (TestObject) clazz.newInstance(); + // 步骤3:获得该类的对象 + test.print(); + // 输出 + } + +} + +// 输出结果 +hello DiyClassLoader +``` + +------ + +# 4. 总结 + +- 本文全面讲解类加载器 + +![img](https:////upload-images.jianshu.io/upload_images/944365-0adcc90ec5514c9f.png?imageMogr2/auto-orient/strip|imageView2/2/w/842/format/webp) \ No newline at end of file diff --git "a/week_05/51/\347\224\250\346\210\267\346\200\201\345\222\214\345\206\205\346\240\270\346\200\201_51.md" "b/week_05/51/\347\224\250\346\210\267\346\200\201\345\222\214\345\206\205\346\240\270\346\200\201_51.md" new file mode 100644 index 0000000000000000000000000000000000000000..ad5a2abfd52aa6c39bb6bdf0d447e2927daf4353 --- /dev/null +++ "b/week_05/51/\347\224\250\346\210\267\346\200\201\345\222\214\345\206\205\346\240\270\346\200\201_51.md" @@ -0,0 +1,57 @@ +# 用户态和内核态的区别 + +1.操作系统需要两种CPU状态: + +内核态(Kernel Mode):运行操作系统程序 + +用户态(User Mode):运行用户程序 + +2.指令划分: + +特权指令:只能由操作系统使用、用户程序不能使用的指令。 举例:启动I/O 内存清零 修改程序状态字 设置时钟 允许/禁止终端 停机 + +非特权指令:用户程序可以使用的指令。 举例:控制转移 算数运算 取数指令 访管指令(使用户程序从用户态陷入内核态) + +3.特权级别: + +特权环:R0、R1、R2和R3 + +R0相当于内核态,R3相当于用户态; + +不同级别能够运行不同的指令集合; + +4.CPU状态之间的转换: + +用户态--->内核态:唯一途径是通过中断、异常、陷入机制(访管指令) + +内核态--->用户态:设置程序状态字PSW + +5.内核态与用户态的区别: + +1)内核态与用户态是操作系统的两种运行级别,当程序运行在3级特权级上时,就可以称之为运行在用户态。因为这是最低特权级,是普通的用户进程运行的特权级,大部分用户直接面对的程序都是运行在用户态; + +2)当程序运行在0级特权级上时,就可以称之为运行在内核态。 + +3)运行在用户态下的程序不能直接访问操作系统内核数据结构和程序。当我们在系统中执行一个程序时,大部分时间是运行在用户态下的,在其需要操作系统帮助完成某些它没有权力和能力完成的工作时就会切换到内核态。 + +4)这两种状态的主要差别是: +处于用户态执行时,进程所能访问的内存空间和对象受到限制,其所处于占有的处理机是可被抢占的 ; +而处于核心态执行中的进程,则能访问所有的内存空间和对象,且所占有的处理机是不允许被抢占的。 + +1. 通常来说,以下三种情况会导致用户态到内核态的切换: + +1)系统调用 + +这是用户态进程主动要求切换到内核态的一种方式,用户态进程通过系统调用申请使用操作系统提供的服务程序完成工作。比如前例中fork()实际上就是执行了一个创建新进程的系统调用。 +而系统调用的机制其核心还是使用了操作系统为用户特别开放的一个中断来实现,例如Linux的int 80h中断。 + +2)异常 + +当CPU在执行运行在用户态下的程序时,发生了某些事先不可知的异常,这时会触发由当前运行进程切换到处理此异常的内核相关程序中,也就转到了内核态,比如缺页异常。 + +3)外围设备的中断 + +当外围设备完成用户请求的操作后,会向CPU发出相应的中断信号,这时CPU会暂停执行下一条即将要执行的指令转而去执行与中断信号对应的处理程序, +如果先前执行的指令是用户态下的程序,那么这个转换的过程自然也就发生了由用户态到内核态的切换。比如硬盘读写操作完成,系统会切换到硬盘读写的中断处理程序中执行后续操作等。 + +这3种方式是系统在运行时由用户态转到内核态的最主要方式,其中系统调用可以认为是用户进程主动发起的,异常和外围设备中断则是被动的。 \ No newline at end of file