From 42d3ad122f709d44488e6a91eb1930a62c3f62a2 Mon Sep 17 00:00:00 2001 From: Asianwyz Date: Sun, 12 Jan 2020 23:54:32 +0800 Subject: [PATCH] =?UTF-8?q?Week=2005=E2=80=9C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- week_05/44/ThreadLocal.md | 312 +++++++++++++++++++++++ week_05/44/ThreadPoolExecutor.md | 46 ++++ week_05/44/thread.md | 144 +++++++++++ "week_05/44/\347\272\277\347\250\213.md" | 243 ++++++++++++++++++ 4 files changed, 745 insertions(+) create mode 100644 week_05/44/ThreadLocal.md create mode 100644 week_05/44/ThreadPoolExecutor.md create mode 100644 week_05/44/thread.md create mode 100644 "week_05/44/\347\272\277\347\250\213.md" diff --git a/week_05/44/ThreadLocal.md b/week_05/44/ThreadLocal.md new file mode 100644 index 0000000..a45789d --- /dev/null +++ b/week_05/44/ThreadLocal.md @@ -0,0 +1,312 @@ +# ThreadLocal + +## 参考 + +彤哥读源码 + + + + + +## 简介 + +`ThreadLocal`类用来提供线程内部的局部变量。这种变量在多线程环境下访问(通过`get`和`set`方法访问)时能保证各个线程的变量相对独立于其他线程内的变量。`ThreadLocal`实例通常来说都是`private static`类型的,用于关联线程和线程上下文。 + +`ThreadLocal`的作用是提供线程内的局部变量,不同的线程之间不会相互干扰,这种变量在线程的生命周期内起作用,减少同一个线程内多个函数或组件之间一些公共变量的传递的复杂度。 + +`ThreadLocal`提供线程内部的局部变量,在本线程内随时随地可取,隔离其他线程。 + +## 源码分析 + +![]() + + + +我们可以看出每个Thread维护一个**ThreadLocalMap**,存储在ThreadLocalMap内的就是一个以Entry为元素的table数组,Entry就是一个key-value结构,key为ThreadLocal,value为存储的值。类比HashMap的实现,其实就是每个线程借助于一个哈希表,存储线程独立的值。我们可以看看Entry的定义: + +```java +static class Entry extends WeakReference> { + /** The value associated with this ThreadLocal. */ + Object value; + + Entry(ThreadLocal k, Object v) { + super(k); + value = v; + } +} +``` + +这里ThreadLocal和key之间的线是虚线,因为Entry是继承了WeakReference实现的,当ThreadLocal Ref销毁时,指向堆中ThreadLocal实例的唯一一条强引用消失了,只有Entry有一条指向ThreadLocal实例的弱引用,假设你知道弱引用的特性,那么这里ThreadLocal实例是可以被GC掉的。这时Entry里的key为null了,那么直到线程结束前,Entry中的value都是无法回收的,这里可能产生内存泄露,后面会说如何解决。 + +### get方法 + +```java +public T get() { + Thread t = Thread.currentThread(); + ThreadLocalMap map = getMap(t); + if (map != null) { + ThreadLocalMap.Entry e = map.getEntry(this); + if (e != null) { + @SuppressWarnings("unchecked") + T result = (T)e.value; + return result; + } + } + return setInitialValue(); +} +``` + +直接看代码,可以分析主要有以下几步: + +1. 获取当前的Thread对象,通过getMap获取Thread内的ThreadLocalMap +2. 如果map已经存在,以当前的ThreadLocal为键,获取Entry对象,并从从Entry中取出值 +3. 否则,调用setInitialValue进行初始化。 + +### getMap + +```java + ThreadLocalMap getMap(Thread t) { + return t.threadLocals; + } +``` + +getMap很简单,就是返回线程中ThreadLocalMap,跳到Thread源码里看,ThreadLocalMap是这么定义的: + +```java +ThreadLocal.ThreadLocalMap threadLocals = null; +``` + +所以ThreadLocalMap还是定义在ThreadLocal里面的。 + +### setInitialValue + +```java +private T setInitialValue() { + T value = initialValue(); + Thread t = Thread.currentThread(); + ThreadLocalMap map = getMap(t); + if (map != null) + map.set(this, value); + else + createMap(t, value); + return value; +} +``` + +setInititialValue在Map不存在的时候调用 + +1. 首先是调用initialValue生成一个初始的value值,深入initialValue函数,我们可知它就是返回一个null; +2. 然后还是在get以下Map,如果map存在,则直接map.set,这个函数会放在后文说; +3. 如果不存在则会调用createMap创建ThreadLocalMap,这里正好可以先说明下ThreadLocalMap了。 + +### ThreadLocalMap + +createMap方法的定义很简单: + +```java +void createMap(Thread t, T firstValue) { + t.threadLocals = new ThreadLocalMap(this, firstValue); +} +``` + +就是调用ThreadLocalMap的构造函数生成一个map,下面我们看看ThreadLocalMap的定义: + +```java +static class ThreadLocalMap { + static class Entry extends WeakReference> { + /** The value associated with this ThreadLocal. */ + Object value; + + Entry(ThreadLocal k, Object v) { + super(k); + value = v; + } + } + + /** + * The initial capacity -- MUST be a power of two. + */ + private static final int INITIAL_CAPACITY = 16; + + /** + * The table, resized as necessary. + * table.length MUST always be a power of two. + */ + private Entry[] table; + + /** + * The number of entries in the table. + */ + private int size = 0; + + /** + * The next size value at which to resize. + */ + private int threshold; // Default to 0 + + /** + * Set the resize threshold to maintain at worst a 2/3 load factor. + */ + private void setThreshold(int len) { + threshold = len * 2 / 3; + } + + /** + * Increment i modulo len. + */ + private static int nextIndex(int i, int len) { + return ((i + 1 < len) ? i + 1 : 0); + } + + /** + * Decrement i modulo len. + */ + private static int prevIndex(int i, int len) { + return ((i - 1 >= 0) ? i - 1 : len - 1); + } + +``` + +ThreadLocalMap被定义为一个静态类,上面是包含的主要成员: + +1. 首先是Entry的定义,前面已经说过; +2. 初始的容量为`INITIAL_CAPACITY = 16`; +3. 主要数据结构就是一个Entry的数组table; +4. size用于记录Map中实际存在的entry个数; +5. threshold是扩容上限,当size到达threashold时,需要resize整个Map,threshold的初始值为`len * 2 / 3`; +6. nextIndex和prevIndex则是为了安全的移动索引,后面的函数里经常用到 + +ThreadLocalMap的构造函数如下: + +```java +ThreadLocalMap(ThreadLocal firstKey, Object firstValue) { + table = new Entry[INITIAL_CAPACITY]; + int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1); + table[i] = new Entry(firstKey, firstValue); + size = 1; + setThreshold(INITIAL_CAPACITY); +} +``` + +就是使用firstKey和firstValue创建一个Entry,计算好索引i,然后把创建好的Entry插入table中的i位置,再设置好size和threshold。 + +### map.getEntry + +```java +private Entry getEntry(ThreadLocal key) { + int i = key.threadLocalHashCode & (table.length - 1); + Entry e = table[i]; + if (e != null && e.get() == key) + return e; + else + return getEntryAfterMiss(key, i, e); +} +``` + +1. 首先是计算索引位置i,通过计算key的hash%(table.length-1)得出; +2. 根据获取Entry,如果Entry存在且Entry的key恰巧等于ThreadLocal,那么直接返回Entry对象; +3. 否则,也就是在此位置上找不到对应的Entry,那么就调用getEntryAfterMiss。 + +这么一深入,引出了**getEntryAfterMiss**方法: + +### getEntryAfterMiss + +```java +private Entry getEntryAfterMiss(ThreadLocal key, int i, Entry e) { + Entry[] tab = table; + int len = tab.length; + + while (e != null) { + ThreadLocal k = e.get(); + if (k == key) + return e; + if (k == null) + expungeStaleEntry(i); + else + i = nextIndex(i, len); + e = tab[i]; + } + return null; +} +``` + +是因为不满足`e != null && e.get() == key`才沦落到调用`getEntryAfterMiss`的,所以首先e如果为null的话,那么`getEntryAfterMiss`还是直接返回null的,如果是不满足`e.get() == key`,那么进入while循环,这里是不断循环,如果e一直不为空,那么就调用nextIndex,不断递增i,在此过程中一直会做两个判断: + +1. 如果`k==key`,那么代表找到了这个所需要的Entry,直接返回; +2. 如果`k==null`,那么证明这个Entry中key已经为null,那么这个Entry就是一个过期对象,这里调用`expungeStaleEntry`清理该Entry。 这里解答了前面留下的一个坑,**即ThreadLocal Ref销毁时,ThreadLocal实例由于只有Entry中的一条弱引用指着,那么就会被GC掉,Entry的key没了,value可能会内存泄露的**,其实在每一个get,set操作时都会不断清理掉这种key为null的Entry的。 + +### set方法 + +```java +public void set(T value) { + Thread t = Thread.currentThread(); + ThreadLocalMap map = getMap(t); + if (map != null) + map.set(this, value); + else + createMap(t, value); +} +``` + +首先也是获取当前线程,根据线程获取到ThreadLocalMap,若是有ThreadLocalMap,则调用map.set(ThreadLocal key, Object value),若是没有则调用createMap创建。 + +### map.set方法 + +```java +private void set(ThreadLocal key, Object value) { + Entry[] tab = table; + int len = tab.length; + int i = key.threadLocalHashCode & (len-1); + + for (Entry e = tab[i]; + e != null; + e = tab[i = nextIndex(i, len)]) { + ThreadLocal k = e.get(); + + if (k == key) { + e.value = value; + return; + } + + if (k == null) { + replaceStaleEntry(key, value, i); + return; + } + } + + tab[i] = new Entry(key, value); + int sz = ++size; + if (!cleanSomeSlots(i, sz) && sz >= threshold) + rehash(); +} +``` + +看上面这段代码: + +1. 首先还是根据key计算出位置i,然后查找i位置上的Entry, +2. 若是Entry已经存在并且key等于传入的key,那么这时候直接给这个Entry赋新的value值。 +3. 若是Entry存在,但是key为null,则调用replaceStaleEntry来更换这个key为空的Entry +4. 不断循环检测,直到遇到为null的地方,这时候要是还没在循环过程中return,那么就在这个null的位置新建一个Entry,并且插入,同时size增加1。 +5. 最后调用cleanSomeSlots,这个函数就不细说了,你只要知道内部还是调用了上面提到的expungeStaleEntry函数清理key为null的Entry就行了,最后返回是否清理了Entry,接下来再判断`sz>thresgold`,这里就是判断是否达到了rehash的条件,达到的话就会调用rehash函数。 + +### remove方法 + +用于在map中移除一个不用的Entry。也是先计算出hash值,若是第一次没有命中,就循环直到null,在此过程中也会调用expungeStaleEntry清除空key节点。代码如下: + +```java +private void remove(ThreadLocal key) { + Entry[] tab = table; + int len = tab.length; + int i = key.threadLocalHashCode & (len-1); + for (Entry e = tab[i]; + e != null; + e = tab[i = nextIndex(i, len)]) { + if (e.get() == key) { + e.clear(); + expungeStaleEntry(i); + return; + } + } +} +``` + diff --git a/week_05/44/ThreadPoolExecutor.md b/week_05/44/ThreadPoolExecutor.md new file mode 100644 index 0000000..2ddec73 --- /dev/null +++ b/week_05/44/ThreadPoolExecutor.md @@ -0,0 +1,46 @@ +## 参考 + +## + + + +public class ThreadPoolExecutorextends AbstractExecutorService一个 ExecutorService,它使用可能的几个池线程之一执行每个提交的任务,通常使用 Executors 工厂方法配置。 + + +线程池可以解决两个不同问题:由于减少了每个任务调用的开销,它们通常可以在执行大量异步任务时提供增强的性能,并且还可以提供绑定和管理资源(包括执行集合任务时使用的线程)的方法。每个 ThreadPoolExecutor 还维护着一些基本的统计数据,如完成的任务数。 + + +为了便于跨大量上下文使用,此类提供了很多可调整的参数和扩展挂钩。但是,强烈建议程序员使用较为方便的 Executors 工厂方法 Executors.newCachedThreadPool()(无界线程池,可以进行自动线程回收)、Executors.newFixedThreadPool(int)(固定大小线程池)和 Executors.newSingleThreadExecutor()(单个后台线程),它们均为大多数使用场景预定义了设置。否则,在手动配置和调整此类时,使用以下指导: + + +核心和最大池大小 +ThreadPoolExecutor 将根据 corePoolSize(参见 getCorePoolSize())和 maximumPoolSize(参见 getMaximumPoolSize())设置的边界自动调整池大小。当新任务在方法 execute(java.lang.Runnable) 中提交时,如果运行的线程少于 corePoolSize,则创建新线程来处理请求,即使其他辅助线程是空闲的。如果运行的线程多于 corePoolSize 而少于 maximumPoolSize,则仅当队列满时才创建新线程。如果设置的 corePoolSize 和 maximumPoolSize 相同,则创建了固定大小的线程池。如果将 maximumPoolSize 设置为基本的无界值(如 Integer.MAX_VALUE),则允许池适应任意数量的并发任务。在大多数情况下,核心和最大池大小仅基于构造来设置,不过也可以使用 setCorePoolSize(int) 和 setMaximumPoolSize(int) 进行动态更改。 +按需构造 +默认情况下,即使核心线程最初只是在新任务需要时才创建和启动的,也可以使用方法 prestartCoreThread() 或 prestartAllCoreThreads() 对其进行动态重写。 +创建新线程 +使用 ThreadFactory 创建新线程。如果没有另外说明,则在同一个 ThreadGroup 中一律使用 Executors.defaultThreadFactory() 创建线程,并且这些线程具有相同的 NORM_PRIORITY 优先级和非守护进程状态。通过提供不同的 ThreadFactory,可以改变线程的名称、线程组、优先级、守护进程状态,等等。如果从 newThread 返回 null 时 ThreadFactory 未能创建线程,则执行程序将继续运行,但不能执行任何任务。 +保持活动时间 +如果池中当前有多于 corePoolSize 的线程,则这些多出的线程在空闲时间超过 keepAliveTime 时将会终止(参见 getKeepAliveTime(java.util.concurrent.TimeUnit))。这提供了当池处于非活动状态时减少资源消耗的方法。如果池后来变得更为活动,则可以创建新的线程。也可以使用方法 setKeepAliveTime(long, java.util.concurrent.TimeUnit) 动态地更改此参数。使用 Long.MAX_VALUE TimeUnit.NANOSECONDS 的值在关闭前有效地从以前的终止状态禁用空闲线程。 +排队 +所有 BlockingQueue 都可用于传输和保持提交的任务。可以使用此队列与池大小进行交互: +如果运行的线程少于 corePoolSize,则 Executor 始终首选添加新的线程,而不进行排队。 +如果运行的线程等于或多于 corePoolSize,则 Executor 始终首选将请求加入队列,而不添加新的线程。 +如果无法将请求加入队列,则创建新的线程,除非创建此线程超出 maximumPoolSize,在这种情况下,任务将被拒绝。 +排队有三种通用策略: +直接提交。工作队列的默认选项是 SynchronousQueue,它将任务直接提交给线程而不保持它们。在此,如果不存在可用于立即运行任务的线程,则试图把任务加入队列将失败,因此会构造一个新的线程。此策略可以避免在处理可能具有内部依赖性的请求集合时出现锁定。直接提交通常要求无界 maximumPoolSizes 以避免拒绝新提交的任务。当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 +无界队列。使用无界队列(例如,不具有预定义容量的 LinkedBlockingQueue)将导致在所有 corePoolSize 线程都忙的情况下将新任务加入队列。这样,创建的线程就不会超过 corePoolSize。(因此,maximumPoolSize 的值也就无效了。)当每个任务完全独立于其他任务,即任务执行互不影响时,适合于使用无界队列;例如,在 Web 页服务器中。这种排队可用于处理瞬态突发请求,当命令以超过队列所能处理的平均数连续到达时,此策略允许无界线程具有增长的可能性。 +有界队列。当使用有限的 maximumPoolSizes 时,有界队列(如 ArrayBlockingQueue)有助于防止资源耗尽,但是可能较难调整和控制。队列大小和最大池大小可能需要相互折衷:使用大型队列和小型池可以最大限度地降低 CPU 使用率、操作系统资源和上下文切换开销,但是可能导致人工降低吞吐量。如果任务频繁阻塞(例如,如果它们是 I/O 边界),则系统可能为超过您许可的更多线程安排时间。使用小型队列通常要求较大的池大小,CPU 使用率较高,但是可能遇到不可接受的调度开销,这样也会降低吞吐量。 +被拒绝的任务 +当 Executor 已经关闭,并且 Executor 将有限边界用于最大线程和工作队列容量,且已经饱和时,在方法 execute(java.lang.Runnable) 中提交的新任务将被拒绝。在以上两种情况下,execute 方法都将调用其 RejectedExecutionHandler 的 RejectedExecutionHandler.rejectedExecution(java.lang.Runnable, java.util.concurrent.ThreadPoolExecutor) 方法。下面提供了四种预定义的处理程序策略: +在默认的 ThreadPoolExecutor.AbortPolicy 中,处理程序遭到拒绝将抛出运行时 RejectedExecutionException。 +在 ThreadPoolExecutor.CallerRunsPolicy 中,线程调用运行该任务的 execute 本身。此策略提供简单的反馈控制机制,能够减缓新任务的提交速度。 +在 ThreadPoolExecutor.DiscardPolicy 中,不能执行的任务将被删除。 +在 ThreadPoolExecutor.DiscardOldestPolicy 中,如果执行程序尚未关闭,则位于工作队列头部的任务将被删除,然后重试执行程序(如果再次失败,则重复此过程)。 +定义和使用其他种类的 RejectedExecutionHandler 类也是可能的,但这样做需要非常小心,尤其是当策略仅用于特定容量或排队策略时。 +挂钩方法 +此类提供 protected 可重写的 beforeExecute(java.lang.Thread, java.lang.Runnable) 和 afterExecute(java.lang.Runnable, java.lang.Throwable) 方法,这两种方法分别在执行每个任务之前和之后调用。它们可用于操纵执行环境;例如,重新初始化 ThreadLocal、搜集统计信息或添加日志条目。此外,还可以重写方法 terminated() 来执行 Executor 完全终止后需要完成的所有特殊处理。 +如果挂钩或回调方法抛出异常,则内部辅助线程将依次失败并突然终止。 + + +队列维护 +方法 getQueue() 允许出于监控和调试目的而访问工作队列。强烈反对出于其他任何目的而使用此方法。remove(java.lang.Runnable) 和 purge() 这两种方法可用于在取消大量已排队任务时帮助进行存储回收。 \ No newline at end of file diff --git a/week_05/44/thread.md b/week_05/44/thread.md new file mode 100644 index 0000000..aea9c45 --- /dev/null +++ b/week_05/44/thread.md @@ -0,0 +1,144 @@ +# Thread + +## 参考 + +彤哥读源码 + + + +## Thread是什么 + +​ 线程Thread是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。 Java程序运行时,会启动一个JVM进程,JVM寻找到程序的入口main(),会创建一个主线程运行。一个java程序至少有一个进程,一个进程至少有一个线程。 + +## 线程的状态 + +### 线程的状态标志 + +```java + public enum State { + NEW, + RUNNABLE, + BLOCKED, + WAITING, + TIMED_WAITING, + TERMINATED; + } +``` + +1. NEW 线程刚刚创建,尚未启动 + +2. RUNNABLE 线程正在运行 + +3. BLOCKED 线程堵塞状态,等待同步块的锁的释放,如果获得锁,就会自动进入运行状态 + +4. WAITING 线程处于等待状态,需要被其他线程唤醒(notify、notifyAll方法的调用)。线程在调用了join之后,也会进入等待状态,直到被join的线程执行结束才会被唤醒 + +5. TIMED_WAITING 有限时间的等待,一般出现在调用sleep(time),join(time),sleep(time)后 + +6. TERMINATED 线程运行结束 + +### 线程的状态转换 + +![]() + +## 线程的基本属性 + +```java +private volatile String name; // 线程名称 +private int priority; // 优先级,1~10 +private boolean daemon = false; // 是否是守护线程 +private Runnable target; // 实际被执行的对象 +ThreadLocal.ThreadLocalMap threadLocals = null; // 当前线程的数据 +private long tid; // 线程id +``` + +## 主要方法 + +```java + public synchronized void start() { + // 判断状态,只能启动一次 + if (threadStatus != 0 || started) + throw new IllegalThreadStateException(); + group.add(this); + + started = false; + try { + nativeCreate(this, stackSize, daemon); + started = true; + } finally { + try { + if (!started) { + group.threadStartFailed(this); + } + } catch (Throwable ignore) { + } + } + } + + // 执行方法,target是实际可执行的Runnable对象 + public void run() { + if (target != null) { + target.run(); + } + } + + // 退出 + private void exit() { + // 将当前线程冲ThreadGroup里面移除 + if (group != null) { + group.threadTerminated(this); + group = null; + } + target = null; + threadLocals = null; + inheritableThreadLocals = null; + inheritedAccessControlContext = null; + blocker = null; + uncaughtExceptionHandler = null; + } + + // 线程中断 + public void interrupt() { + if (this != Thread.currentThread()) + checkAccess(); + + synchronized (blockerLock) { + Interruptible b = blocker; + if (b != null) { + nativeInterrupt(); + b.interrupt(this); + return; + } + } + nativeInterrupt(); + } + + public final void join(long millis) throws InterruptedException { + synchronized(lock) { + long base = System.currentTimeMillis(); + long now = 0; + + if (millis < 0) { + throw new IllegalArgumentException("timeout value is negative"); + } + + if (millis == 0) { + while (isAlive()) { + lock.wait(0); + } + } else { + while (isAlive()) { + long delay = millis - now; + if (delay <= 0) { + break; + } + lock.wait(delay); + now = System.currentTimeMillis() - base; + } + } + } + } + + public static native void yield(); +``` + diff --git "a/week_05/44/\347\272\277\347\250\213.md" "b/week_05/44/\347\272\277\347\250\213.md" new file mode 100644 index 0000000..d1078e8 --- /dev/null +++ "b/week_05/44/\347\272\277\347\250\213.md" @@ -0,0 +1,243 @@ +# 线程 + +## 参考: + +彤哥读源码 + +黑马 + + + +## 简介 + +​ 几乎每种操作系统都支持进程的概念——进程就是在某种程度上相互隔离的、独立运行的程序。 + +​ 线程化是允许多个活动共存于一个进程中的工具。大多数现代的操作系统都支持线程,而且线程的概念以各种形式已存在了好多年。Java 是第一个在语言本身中显式地包含线程的主流编程语言,它没有把线程化看作是底层操作系统的工具。 + +​ 有时候,线程也称作*轻量级进程*。就象进程一样,线程在程序中是独立的、并发的执行路径,每个线程有它自己的堆栈、自己的程序计数器和自己的局部变量。但是,与分隔的进程相比,进程中的线程之间的隔离程度要小。它们共享内存、文件句柄和其它每个进程应有的状态。 + +​ 进程可以支持多个线程,它们看似同时执行,但互相之间并不同步。一个进程中的多个线程共享相同的内存地址空间,这就意味着它们可以访问相同的变量和对象,而且它们从同一堆中分配对象。尽管这让线程之间共享信息变得更容易,但您必须小心,确保它们不会妨碍同一进程里的其它线程。 + +​ Java 线程工具和 API 看似简单。但是,编写有效使用线程的复杂程序并不十分容易。因为有多个线程共存在相同的内存空间中并共享相同的变量,所以必须小心,确保线程不会互相干扰。 + +### 每个Java都使用线程 + +​ 每个 Java 程序都至少有一个线程 ― 主线程。当一个 Java 程序启动时,JVM 会创建主线程,并在该线程中调用程序的 `main()` 方法。 + +​ JVM 还创建了其它线程,通常都看不到它们 ― 例如,与垃圾收集、对象终止和其它 JVM 内务处理任务相关的线程。其它工具也创建线程,如 AWT(抽象窗口工具箱(Abstract Windowing Toolkit))或 Swing UI 工具箱、servlet 容器、应用程序服务器和 RMI(远程方法调用(Remote Method Invocation))。 + +## 创建线程的8种方式 + +### 继承Thread类并重写run()方法 + +```java +public class CreatingThread01 extends Thread { + + @Override + public void run() { + System.out.println(getName() + " is running"); + } + + public static void main(String[] args) { + new CreatingThread01().start(); + new CreatingThread01().start(); + new CreatingThread01().start(); + new CreatingThread01().start(); + } +} +``` + +继承Thread类并重写run()方法,这种方式的弊端是一个类只能继承一个父类,如果这个类本身已经继承了其它类,就不能使用这种方式了。 + +### 实现Runnable接口 + +```java +public class CreatingThread02 implements Runnable { + + @Override + public void run() { + System.out.println(Thread.currentThread().getName() + " is running"); + } + + public static void main(String[] args) { + new CreatingThread02().start(); + new CreatingThread02().start(); + new CreatingThread02().start(); + new CreatingThread02().start(); + } +} +``` + +实现Runnable接口,这种方式的好处是一个类可以实现多个接口,不影响其继承体系。 + +### 匿名内部类 + +```java +public class CreatingThread03 { + public static void main(String[] args) { + // Thread匿名类,重写Thread的run()方法 + new Thread() { + @Override + public void run() { + System.out.println(getName() + " is running"); + } + } + }.start(); + + // Runable匿名类,实现其run()方法 + new Thread(new Runnable() { + @Override + public void run() { + System.out.println(Thread.currentThread().getName() + " is running"); + } + }).start(); + + // 使用lambda表达式函数式编程 + new Thread(()->{ + System.out.println(Thread.currentThread().getName() + " is running"); + }).start(); +} +``` + +使用匿名类的方式,一是重写Thread的run()方法,二是传入Runnable的匿名类,三是使用lambda方式,现在一般使用第三种(java8+),简单快捷。 + +### 实现Callable接口 + +```java +public class CreatingThread04 implements Callable { + @Override + public Long call() throws Exception { + Thread.sleep(2000); + System.out.println(Thread.currentThread().getId() + " is running"); + return Thread.currentThread().getId(); + } + + public static void main(String[] args) throws ExccutionException, InterruptedException { + FutureTask task = new FutureTask<>(new CreatingThread04); + new Thread(task).start(); + System.out.println("等待完成任务"); + Long result = task.get(); + System.out.println("任务结果:" + result); + } +} +``` + +实现Callabe接口,可以获取线程执行的结果,FutureTask实际上实现了Runnable接口。 + +### 定时器(java.util.Timer) + +```java +public class CreatingThread05 { + public static void main(String[] args) { + Timer timer = new Timer(); + // 每隔一秒执行一次 + timer.schedule(new TimerTask() { + @Override + public void run() { + System.out.println(Thread.currentThread().getName() + " is running"); + } + }, 0, 1000) + } +} +``` + +使用定时器java.util.Timer可以快速地实现定时任务,TimerTask实际上实现了Runnable接口。 + +### 线程池 + +```java +public class CreatingThread06 { + public static void main(String[] args) { + ExecutorService threadPool = Executors.newFixedThreadPool(5); + for (int i = 0; i < 100; i++) { + threadPool.execute(()-> System.out.println(Thread.currentThread().getName() + " is running")); + } + } +} +``` + +使用线程池的方式,可以复用线程,节约系统资源。 + +### 并行计算(Java8+) + +```java +public class CreatingThread07 { + + public static void main(String[] args) { + List list = Arrays.asList(1, 2, 3, 4, 5); + // 串行,打印结果为12345 + list.stream().forEach(System.out::print); + System.out.println(); + // 并行,打印结果随机,比如31524 + list.parallelStream().forEach(System.out::print); + } +} +``` + +### Spring异步方法 + +首先,springboot启动类加上 `@EnableAsync`注解(@EnableAsync是spring支持的,这里方便举例使用springboot)。 + +```java +@SpringBootApplication +@EnableAsync +public class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} +``` + +其次,方法加上@Async注解。 + +```java +@Service +public class CreatingThread08Service { + @Async + public void call() { + System.out.println(Thread.currentThread().getName() + " is running"); + } +} +``` + +然后,测试用例直接跟使用一般的Service方法一摸一样。 + +```java +@RunWith(SpringRunner.class) +@SpringBootTest(classes = Application.class) +public class CreatingThread08Test { + + @Autowired + private CreatingThread08Service CreatingThread08Service; + + @Test + public void test() { + CreatingThread08Service.call(); + CreatingThread08Service.call(); + CreatingThread08Service.call(); + CreatingThread08Service.call(); + } +} +``` + +可以看到每次执行方法时使用的线程都不一样。 + +使用Spring异步方法的方式,可以说是相当地方便,适用于前后逻辑不相关联的适合用异步调用的一些方法,比如发送短信的功能。 + +## 总结 + +(1)继承Thread类并重写run()方法; + +(2)实现Runnable接口; + +(3)匿名内部类; + +(4)实现Callabe接口; + +(5)定时器(java.util.Timer); + +(6)线程池; + +(7)并行计算(Java8+); + +(8)Spring异步方法; \ No newline at end of file -- Gitee