《java 核心技术》这本书真的不错, 知识点很全面, 翻译质量也还不错, 本系列博文是对该书中并发章节的一个总结.
什么是线程
官方解释: 线程是操作系统能够进行运算调度的最小单位, 包含于进程之中, 是进程中的实际运作单位. 也就是说线程是代码运行的载体, 我们所编写的代码都是在线程上跑的, 以一个最简单的 hellowWorld 为例:
- 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 类就不能再继承其他类了.
使用继承方式创建线程代码如下:
- 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 接口并不影响实现类再去实现其他接口.
使用实现接口方式创建线程代码如下:
- 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() 为所有线程设置默认处理器, 代码如下:
- 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.cnblogs.com/wuyoucao/p/11100889.html