---
id: "2019-04-08-20-52"
date: "2019/04/08 20:52"
title: "最全java多线程学习总结1--线程基础"
tags: ["java", "thread"]
categories:
  - "java"
  - "java基础"
---

&emsp;&emsp;《java 核心技术》这本书真的不错，知识点很全面，翻译质量也还不错，本系列博文是对该书中并发章节的一个总结。

# 什么是线程

&emsp;&emsp;官方解释：线程是操作系统能够进行运算调度的最小单位，包含于进程之中，是进程中的实际运作单位。也就是说线程是代码运行的载体，我们所编写的代码都是在线程上跑的，以一个最简单的 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());
    }
}
```

<!-- more -->

结果为：

```
Hello World!
当前线程名为：main
当前线程id为：1
```

在程序运行时默认会创建一个主线程来执行代码，线程名为：main,线程 id 为 1

# 什么是多线程

&emsp;&emsp;顾名思义就是多个线程同时运行，提高程序执行速度。单个线程一次只能做一件事，想要提高执行效率有两种途径：

- 异步。因为大多数时候线程都不是时刻在进行计算，都是在等待 io 操作，那么就可以将等待时间利用起来以提高线程的利用率。这里不做过多讨论，想要进一步了解异步的可以学习 Node.js(原生支持异步)
- 多线程。一个线程一次只能做一件事，那么多个线程就能同时做多件事了，通过增大线程数来提高执行速度。

# 如何创建线程

&emsp;&emsp;创建线程有两种方法

- 继承 Thread 类
- 实现 runnable 接口

## 继承 Thread 类

&emsp;&emsp;不推荐本方式来创建线程，原因显而易见：java 不支持多继承，如果继承了 Thread 类就不能再继承其他类了。

&emsp;&emsp;使用继承方式创建线程代码如下：

```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 接口

&emsp;&emsp;实现接口来创建线程是目前推荐的一种方式，原因也很简单：一个类可以实现多个接口。实现 Runnable 接口并不影响实现类再去实现其他接口。

&emsp;&emsp;使用实现接口方式创建线程代码如下：

```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();
    }
}
```

&emsp;&emsp;通过查看 Thread 源码可以看到 Thread 类也是 Runnable 接口的一个实现类。

**PS:后续代码全部使用 runnable 创建线程**

# 线程状态

&emsp;&emsp;上面只是演示了线程的创建，现在来详细了解线程的状态。在 java 规范中，线程可以有以下 6 种状态：

- New(新创建)
- Runnable(可运行)
- Blocked(阻塞)
- Waiting(等待)
- Timed waiting(计时等待)
- Terminated(被终止)

## 新创建线程

&emsp;&emsp;当使用 new 操作符创建一个线程时,如 new Thread(r)，线程还未开始运行，就属于新创建状态。

## 可运行线程

&emsp;&emsp;一旦调用 Thread 类的 start 方法，线程就处于可运行状态。

为什么要叫**可**运行状态？

&emsp;&emsp;因为 Java 的规范中并没有将正在 CPU 上运行定义为一个单独的状态。因此处于可运行状态的线程可能正在运行，也可能没有运行，取决于 CPU 的调度策略。

## 被阻塞线程和等待线程

&emsp;&emsp;当线程处于阻塞或等待状态时，不运行任何代码且消耗最少的资源。直到重新运行。有如下几种途径让线程进入阻塞或等待状态：

- 当一个线程试图获取一个内部的对象锁，而该锁被其他线程持有
- 当线程等待另一个线程通知调度器一个条件时，进入等待状态。比如调用 Object.wait 或 Thread.join 方法，或等待 java.util.concurrent 库中的 Lock 或 Condition 时。
- 当调用计时等待方法时。比如 Thread.sleep,Object.wait,Thread.join,Lock.tryLock 以及 Condition.await

## 被终止的线程

&emsp;&emsp;线程可由以下两种办法进入终止状态：

- run 方法的结束而自然死亡
- 未捕获异常中止了 run 方法而意外死亡

**注意:** 调用线程的 stop 方法也可以终止线程，但是这个方法已经被弃用，最好不要使用。

# 线程属性

&emsp;&emsp;线程有各种属性：优先级，守护线程，线程组以及处理未捕获异常处理器。

## 线程优先级

&emsp;&emsp;java 中，每个线程都有一个优先级。默认情况下，线程继承父线程优先级。也可以调用`setPriority`方法指定优先级。优先级范围：1(MIN_PRIORITY)-10(MAX_PRIORITY).NORM_PRIORITY 为 5，这些常量定义在 Thread 类中.

**注意：** 线程优先级时高度依赖于系统的，因此当 java 线程优先级映射到宿主机平台的优先级时，优先级个数可能会变少或者变成 0.比如，Windows 中有 7 个优先级，java 线程映射时部分优先级将会映射到相同的操作系统优先级上。Oracle 为 Linux 编写的 java 虚拟机中,忽略了线程的优先级，所有 java 线程都有相同的优先级。**不要编写依赖优先级的代码**。

## 守护线程

&emsp;&emsp;通过调用`Thread.setDaemon(true)`将一个线程转换为守护线程。守护线程唯一的用户是为其他线程提供服务，比如计时线程，定时发送计时信号给其他线程。因此当虚拟机中只有守护线程时，虚拟机就会关闭退出。**不要在守护线程中访问任何资源,处理任何业务逻辑**

## 未捕获异常处理器

&emsp;&emsp;线程的 run 方法不能抛出任何受查异常，非受查异常会导致线程终止，除了 try/catch 捕获异常外，还可以通过未捕获异常处理器来处理异常。异常处理器需要实现`Thread.UncaughtExceptionHandler`接口。

&emsp;&emsp;可以使用线程示例的`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();
    }
}
```

&emsp;&emsp;如果不设置默认处理器且不为独立的线程设置处理器，那么该线程的处理器就为该线程的线程组对象--ThreadGroup(因为线程组对象实现了`Thread.UncaughtExceptionHandler`接口)。

本篇所用全部代码：[github](https://github.com/FleyX/demo-project/tree/master/2.javaThreadDemo)

