十月份离职, 在家修养一个多月, 这一个多月做了很多事, 自己的微信公众号开通了, 博客也换了一种风格, 在简书和掘金分享一些 Android 方面的小知识, 这一个多月看了些书, 有技术相关的, 也有非技术相关的, 突然间觉得的这种生活也挺不错的, 这五年买了很多书, 加起来最起码有四五箱的书, 以前上班忙, 只有晚上回来看个一两个小时, 现在闲了, 想全天看就全天看, 读书是一辈子的事, 喜欢读书, 这样无论在什么时候都会有自己的思考和见地, 不会一味地迎合或沉沦, 从而失去立场, 失去自己. 当然在进行自我提升的同时也在看看有没有一些工作机会, 目前刚回到上海, 希望自己能找到一家与之奋斗的公司, 一起成长下去.
闲话就扯到这里, 下面进入正文.
在讲到多线程有必要了解下什么是进程, 在百度百科上是这么定义进程的: 进程是操作系统结构的基础; 是一次程序的执行; 是一个程序及其数据在处理上顺序执行时所发生的活动; 是程序在一个数据集合上运行的过程, 它是系统进行资源分配和调度的一个独立单位.
百度百科对进程的定义比较抽象, 举个例子, 我们在电脑打开一个程序 exe, 那这个 exe 就可以理解成一个进程, 进程是受操作系统管理的基本运行单元. 那线程又是什么, 线程是在进程中独立运行的子任务, 比如打开腾讯视频 (进程), 你一边在看视频, 一边在下载视频, 同时在看视频时数据的传输等等, 这些同时执行的任务都是线程, 利用多线程可以同一时间内运行更多不同种类的任务.
- public class Client {
- public static void main(String[] args){
- // 输出 main
- System.out.println(Thread.currentThread().getName());
- }
- }
通过 currentThread 方法获取当前的线程名, 上面这个程序在 main 入口函数中打印当前线程的名称, 发现默认就有一个叫做 main 线程在执行 main() 方法中的代码.
在 Java 中实现多线程编程的方式有两种, 一种是继承 Thread 类, 另一种是实现 Runnable 接口, 下面这个程序就使用第一种方式继承 Thread 类:
- public class Task extends Thread {
- @Override
- public void run() {
- super.run();
- System.out.println("执行相关任务");
- }
- }
Task 类继承 Thread,run 方法中打印一句 "执行相关任务".
- public class Client {
- public static void main(String[] args){
- Thread thread=new Task();
- thread.start();
- System.out.println("任务执行完毕!");
- }
- }
在 main 函数中先创建 Task 实例并执行 Task 线程, 接着打印 "任务执行完毕!", 运行下看看什么结果.
任务执行完毕!
执行相关任务
发现先打印 "任务执行完毕!", 后打印 "执行相关任务", 也就是在使用多线程时, 代码的运行结果与代码执行顺序或调用顺序是无关的. 线程是一个子任务, CPU 以不确定的方式, 或者说是以随机的时间来调用线程中的 run 方法.
如果我们继承了 Thread 类, 就不能继承其它类了, Java 不支持多继承, 那怎么办呢? 幸好 Java 提供了 Runnable 接口, 接下来看第二种方式实现 Runnable 接口来创建线程.
- public class Task implements Runnable {
- @Override
- public void run() {
- System.out.println("执行相关任务");
- }
- }
很简单, Task 类实现了 Runnable 接口并实现 run 方法, 怎么使用这个 Task, 和上面的 Client 一样, 代码如下:
- public class Client {
- public static void main(String[] args){
- Runnable runnable=new Task();
- Thread thread=new Thread(runnable);
- thread.start();
- System.out.println("任务执行完毕!");
- }
- }
在编写多线程时容易遇到数据共享问题, 多个线程可以访问一个变量, 看下面程序:
- public class Task implements Runnable {
- private int mTaskCount=0;
- @Override
- public void run() {
- mTaskCount++;
- System.out.println("执行第"+mTaskCount+"任务");
- }
- }
在 Task 线程中对 mTaskCount 进行递增, 下面是 Client 代码:
- public class Client {
- public static void main(String[] args) {
- Runnable runnable = new Task();
- Thread thread_1 = new Thread(runnable);
- Thread thread_2 = new Thread(runnable);
- Thread thread_3 = new Thread(runnable);
- Thread thread_4 = new Thread(runnable);
- thread_1.start();
- thread_2.start();
- thread_3.start();
- thread_4.start();
- System.out.println("任务执行完毕!");
- }
- }
输出如下:
任务执行完毕!
执行第 2 任务
执行第 2 任务
执行第 3 任务
执行第 4 任务
发现两个线程都打印了 mTaskCount 为 2, 产生了 "非线程安全" 问题, 非线程安全主要是指多个线程对同一个对象中的同一个实例变量进行操作时会出现值被更改, 值不同步的情况, 影响程序的执行流程. 在某些 JVM 中, mTaskCount 的操作分成 3 个步骤, 第一取得原有 mTaskCount 值, 第二计算 mTaskCount+1, 第三对 mTaskCount 进行赋值; 在这 3 个步骤中, 如果遇到多个线程同时访问, 会出现指令重排序的问题, 也就是非线程安全问题. 那怎么解决呢? 可以在 run 方法前加上 synchronized 关键字:
- public class Task implements Runnable {
- private int mTaskCount=0;
- @Override
- synchronized public void run() {
- mTaskCount++;
- System.out.println("执行第"+mTaskCount+"任务");
- }
- }
这样输出时 mTaskCount 是依次递增的, 在 run 方法前加上 synchronized 关键字, 使多个线程在执行 run 方法时, 以排队的形式进行处理. 当一个线程试图调用 run 方法前, 先判断 run 方法有没有上锁, 如果上锁了, 说明有其他线程在执行 run 方法, 必须等其他线程执行完 run 方法, 加锁的这段代码称为 "互斥区" 或 "临界区". 一个线程想要执行同步方法里的代码时, 需要先获取锁, 如果获取不到锁, 需要不断的尝试拿这把锁, 直到能够拿到为止.
接着了解下 Thread 常用的几种方法:
isAlive() 方法用于判断当前的线程是否处于活动状态, 活动状态就是线程已经启动且尚未终止, 线程处于正在运行或准备开始运行的状态, 就认为线程是 "存活" 的.
sleep() 方法的作用是在指定的毫秒数内让当前 "正在执行的线程" 休 眠 (暂停执行).
getId() 方法的作用是获取线程的唯一标识.
线程的开启是如此的简单, 但我们有时需要在满足一定条件后关闭线程, 这时如何去做呢?
可以通过 interrupt() 方法来停止线程, 但 interrupt() 方法仅仅是在当前线程中打了一个停止的标记, 并不是真的停止线程. 在 Java 的 SDK 中, Thread 提供了两种方法用于判断线程的状态是不是停止, 分别是 interrupted() 方法, 用于测试当前线程是否已经中断, 还有一个就是 isInterrupted() 方法, 用于测试线程是否已经中断.
先看 Thread.interrupted() 方法的使用:
- public class Task implements Runnable {
- @Override
- synchronized public void run() {
- for (int i=0;i<1000;i++){
- System.out.println("i="+i);
- }
- }
- }
在 Task 线程中通过 for 循环打印 0 到 999.
- public class Client {
- public static void main(String[] args) {
- Runnable runnable = new Task();
- Thread thread = new Thread(runnable);
- thread.start();
- try {
- Thread.sleep(10);
- thread.interrupt();
- System.out.println(Thread.interrupted());
- System.out.println(Thread.interrupted());
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
启动 Task 线程后, 执行暂停 10 毫秒后调用 interrupt() 方法, 最后打印出 Thread.interrupted() 方法两次, 我们看打印结果:
- i=0
- i=1
- i=2
- ...
- i=248
- false
- false
- i=249
- ...
- i=998
- i=999
通过 interrupt() 方法并不能停止 Task 线程, 而执行 Thread.interrupted() 方法, 输出两次都为 false, 也就是说 Thread.interrupted() 方法是用于测试当前线程是否已经中断, 这个当前线程指的是 main 线程, 它从未中断过, 所以打印的结果是两个 false, 这里先看如何使 main 线程产生中断效果, 看下面代码:
- public class Client {
- public static void main(String[] args) {
- Thread.currentThread().interrupt();
- System.out.println(Thread.interrupted());
- System.out.println(Thread.interrupted());
- }
- }
打印:
true
false
通过 Thread.currentThread().interrupt() 给当前 main 线程打上停止的标志, 那为什么第二次输出 Thread.interrupted() 方法时是 false 呢? 官方文档对 interrupted() 方法的解释如下: 测试当前线程是否已经中断. 线程的中断状态由该方法清除. 换句话说, 如果连续两次调用该方法, 则第二次调用将返回 false. 也就说 interrupted() 方法具有清除状态的功能.
isInterrupted() 方法与 interrupted() 方法相比, isInterrupted() 方法并不具有清除状态, 也就是我们给 Task 线程执行 interrupt() 方法后, Task 线程就被打上了中断状态, 不管执行多少次 isInterrupted() 方法都会返回 true.
既然知道了 interrupt() 的作用, 如果先执行 task 线程的 interrupt() 方法, 这时 Task 线程被打上中断状态, 然后再在 Task 的 run 方法中通过判断 Thread.interrupted() 是否为 true, 如果为 true 就退出循环, 代码如下:
- public class Task implements Runnable {
- @Override
- synchronized public void run() {
- for (int i=0;i<1000;i++){
- if(Thread.interrupted()){
- break;
- }
- System.out.println("i="+i);
- }
- }
- }
Client 代码如下:
- public class Client {
- public static void main(String[] args) {
- Runnable runnable = new Task();
- Thread thread = new Thread(runnable);
- thread.start();
- try {
- Thread.sleep(10);
- thread.interrupt();
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
打印:
- i=0
- i=1
- i=2
- ...
- i=284
- i=285
- i=286
- i=287
- i=288
这样不就可以在外部中断了 Task 线程, 这种方式虽然可以停止了 Task 线程, 但如果在 for 语句下打印一句话, 代码:
- public class Task implements Runnable {
- @Override
- synchronized public void run() {
- for (int i=0;i<1000;i++){
- if(Thread.interrupted()){
- break;
- }
- System.out.println("i="+i);
- }
- System.out.println("不应该打印");
- }
- }
打印:
- i=0
- i=1
- i=2
- ...
- i=223
- i=224
- i=225
不应该打印
发现 for 循环语句下面的的 println 还是打印出来了, 这时可以在判断 Thread.interrutped() 语句中通过抛出异常来退出, 代码如下:
- public class Task implements Runnable {
- @Override
- synchronized public void run() {
- try {
- for (int i = 0; i < 1000; i++) {
- if (Thread.interrupted()) {
- throw new InterruptedException();
- }
- System.out.println("i=" + i);
- }
- System.out.println("不应该打印");
- } catch (InterruptedException e) {
- e.printStackTrace();
- }
- }
- }
打印:
- i=0
- i=1
- i=2
- ...
- i=106
- i=107
- i=108
- java.lang.InterruptedException
- at com.book.demo.demo01.Task.run(Task.java:10)
- at java.lang.Thread.run(Thread.java:745)
这种方式叫做异常法退出.
当然也可以通过 return 来退出线程:
- public class Task implements Runnable {
- @Override
- synchronized public void run() {
- for (int i = 0; i < 1000; i++) {
- if (Thread.interrupted()) {
- return;
- }
- System.out.println("i=" + i);
- }
- System.out.println("不应该打印");
- }
- }
来源: https://juejin.im/post/5bebd83851882516dc617204