technology-note/java/1.基础知识/多线程/1.java多线程总结1-线程基础.md
2019-08-28 16:53:23 +08:00

196 lines
8.4 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
id: "2019-04-08-20-52"
date: "2019/04/08 20:52"
title: "最全java多线程学习总结1--线程基础"
tags: ["java", "thread"]
categories:
- "java"
- "java基础"
---
  《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());
}
}
```
<!-- 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)
**本篇原创发布于:**[https://www.tapme.top/blog/detail/2019-04-08-20-52](https://www.tapme.top/blog/detail/2019-04-08-20-52)