diff --git a/java/其他/5.java多线程总结.md b/java/其他/5.java多线程总结.md new file mode 100644 index 0000000..3020eef --- /dev/null +++ b/java/其他/5.java多线程总结.md @@ -0,0 +1,193 @@ +--- +id: '2019-04-08-20-52' +date: '2019/04/08 20:52' +title: '最全java多线程总结1--线程基础' +tags: ['java', 'thread'] +categories: + - 'java' + - 'java基础' +--- + +本篇所用全部代码:[github](https://github.com/FleyX/demo-project/tree/master/2.javaThreadDemo) + +  《java 核心技术》这本书真的不错,知识点很全面,翻译质量也还不错,本系列博文是对该书中并发章节的一个总结。 + +# 什么是线程 + +  官方解释:线程是操作系统能够进行运算调度的最小单位,包含于进程之中,是进程中的实际运作单位。也就是说线程是代码运行的载体,我们所编写的代码都是在线程上跑的,以一个最简单的 hellowWorld 为例: + +```java +public class Main { + + public static void main(String[] args) { + System.out.println("Hello World!"); + System.out.println("当前线程名为:"+Thread.currentThread().getName()); + System.out.println("当前线程id为:"+Thread.currentThread().getId()); + } +} +``` + +结果为: + +``` +Hello World! +当前线程名为:main +当前线程id为:1 +``` + +在程序运行时默认会创建一个主线程来执行代码,线程名为:main,线程 id 为 1 + +# 什么是多线程 + +  顾名思义就是多个线程同时运行,提高程序执行速度。单个线程一次只能做一件事,想要提高执行效率有两种途径: + +- 异步。因为大多数时候线程都不是时刻在进行计算,都是在等待 io 操作,那么就可以将等待时间利用起来以提高线程的利用率。这里不做过多讨论,想要进一步了解异步的可以学习 Node.js(原生支持异步) +- 多线程。一个线程一次只能做一件事,那么多个线程就能同时做多件事了,通过增大线程数来提高执行速度。 + +# 如何创建线程 + +  创建线程有两种方法 + +- 继承 Thread 类 +- 实现 runnable 接口 + +## 继承 Thread 类 + +  不推荐本方式来创建线程,原因显而易见:java 不支持多继承,如果继承了 Thread 类就不能再继承其他类了。 + +  使用继承方式创建线程代码如下: + +```java +public class CustomThreadExtendThread extends Thread{ + + @Override + public void run() { + String threadName = Thread.currentThread().getName(); + long threadId = Thread.currentThread().getId(); + System.out.println("创建线程名为:"+threadName+",id为:"+threadId); + } + + public static void main(String[] args){ + Thread thread1 = new CustomThreadExtendThread(); + Thread thread2 = new CustomThreadExtendThread(); + thread1.start(); + thread2.start(); + } +} +``` + +## 实现 runnable 接口 + +  实现接口来创建线程是目前推荐的一种方式,原因也很简单:一个类可以实现多个接口。实现 Runnable 接口并不影响实现类再去实现其他接口。 + +  使用实现接口方式创建线程代码如下: + +```java +public class CustomThreadImplementInterface implements Runnable { + @Override + public void run() { + Thread.currentThread().setName(((Double) Math.random()).toString()); + String threadName = Thread.currentThread().getName(); + long threadId = Thread.currentThread().getId(); + System.out.println("创建线程名为:" + threadName + ",id为:" + threadId); + } + + public static void main(String[] args) { + Thread thread1 = new Thread(new CustomThreadImplementInterface()); + Thread thread2 = new Thread(new CustomThreadExtendThread()); + thread1.start(); + thread2.start(); + + //使用lambda表达式,让创建线程更简单 + new Thread(() -> { + System.out.println("创建了一个新线程"); + }).start(); + } +} +``` + +  通过查看 Thread 源码可以看到 Thread 类也是 Runnable 接口的一个实现类。 + +**PS:下面的代码全部使用 runnable 创建线程** + +# 线程状态 + +  上面只是演示了线程的创建,现在来详细了解线程的状态。在 java 规范中,线程可以有以下 6 种状态: + +- New(新创建) +- Runnable(可运行) +- Blocked(阻塞) +- Waiting(等待) +- Timed waiting(计时等待) +- Terminated(被终止) + +## 新创建线程 + +  当使用 new 操作符创建一个线程时,如 new Thread(r),线程还未开始运行,就属于新创建状态。 + +## 可运行线程 + +  一旦调用 Thread 类的 start 方法,线程就处于可运行状态。 + +为什么要叫**可**运行状态? + +  因为 Java 的规范中并没有将正在 CPU 上运行定义为一个单独的状态。因此处于可运行状态的线程可能正在运行,也可能没有运行,取决于 CPU 的调度策略。 + +## 被阻塞线程和等待线程 + +  当线程处于阻塞或等待状态时,不运行任何代码且消耗最少的资源。直到重新运行。有如下几种途径让线程进入阻塞或等待状态: + +- 当一个线程试图获取一个内部的对象锁,而该锁被其他线程持有 +- 当线程等待另一个线程通知调度器一个条件时,进入等待状态。比如调用 Object.wait 或 Thread.join 方法,或等待 java.util.concurrent 库中的 Lock 或 Condition 时。 +- 当调用计时等待方法时。比如 Thread.sleep,Object.wait,Thread.join,Lock.tryLock 以及 Condition.await + +## 被终止的线程 + +  线程可由以下两种办法进入终止状态: + +- run 方法的结束而自然死亡 +- 未捕获异常中止了 run 方法而意外死亡 + +**注意:** 调用线程的 stop 方法也可以终止线程,但是这个方法已经被弃用,最好不要使用。 + +# 线程属性 + +  线程有各种属性:优先级,守护线程,线程组以及处理未捕获异常处理器。 + +## 线程优先级 + +  java 中,每个线程都有一个优先级。默认情况下,线程继承父线程优先级。也可以调用`setPriority`方法指定优先级。优先级范围:1(MIN_PRIORITY)-10(MAX_PRIORITY).NORM_PRIORITY 为 5,这些常量定义在 Thread 类中. + +**注意:** 线程优先级时高度依赖于系统的,因此当 java 线程优先级映射到宿主机平台的优先级时,优先级个数可能会变少或者变成 0.比如,Windows 中有 7 个优先级,java 线程映射时部分优先级将会映射到相同的操作系统优先级上。Oracle 为 Linux 编写的 java 虚拟机中,忽略了线程的优先级,所有 java 线程都有相同的优先级。**不要编写依赖优先级的代码**。 + +## 守护线程 + +  通过调用`Thread.setDaemon(true)`将一个线程转换为守护线程。守护线程唯一的用户是为其他线程提供服务,比如计时线程,定时发送计时信号给其他线程。因此当虚拟机中只有守护线程时,虚拟机就会关闭推出。**不要在守护线程中访问任何资源,比如文件、数据库等** + +## 未捕获异常处理器 + +  线程的 run 方法不能抛出任何受查异常,非受查异常会导致线程终止,除了 try/catch 捕获异常外,还可以通过未捕获异常处理器来处理异常。异常处理器需要实现`Thread.UncaughtExceptionHandler`接口。 + +  可以使用线程示例的`setUncaughtExceptionHandler()`方法为某个线程设置处理器,也可使用`Thread.setDefaultUncaughtExceptionHandler()`为所有线程设置默认处理器,代码如下: + +```java +public class CustomExceptionHandler implements Thread.UncaughtExceptionHandler { + + @Override + public void uncaughtException(Thread t, Throwable e) { + System.out.println("捕获到线程"+t.getName()+",异常:" + e.getMessage()); + e.printStackTrace(); + } + + public static void main(String[] args) { + Thread.setDefaultUncaughtExceptionHandler(new CustomExceptionHandler()); + new Thread(() -> { + throw new RuntimeException("test"); + }).start(); + } +} +``` + +  如果不设置默认处理器且不为独立的线程设置处理器,那么该线程的处理器就为该线程的线程组对象--ThreadGroup(因为线程组对象实现了`Thread.UncaughtExceptionHandler`接口)。 + +**本篇原创发布于:**[https://www.tapme.top/blog/detail/2019-04-08-20-52](https://www.tapme.top/blog/detail/2019-04-08-20-52)