学习技术的步骤
场景-->需求-->解决方案-->应用-->原理
一、多线程的发展历史
-
真空管和穿孔打卡
操作员在机房里面来回调度资源,以及计算机同一个时刻只能运行一个程序,在程序输入的过程中,计算机计算机和处理空闲状态 。而当时的计算机是非常昂贵的,人们为了减少这种资源的浪费。就采用了 批处理系统来解决
-
晶体管和批处理系统
批处理操作系统虽然能够解决计算机的空闲问题,但是当某一个作业因为等待磁盘或者其他 I/O 操作而暂停时,那CPU 就只能阻塞直到该 I/O 完成,对于 CPU 操作密集型的程序,I/O 操作相对较少,因此浪费的时间也很少。但是对于 I/O 操作较多的场景来说,CPU 的资源是属于严重浪费的
-
集成电路和多道程序设计
有了进程以后,可以让操作系统从宏观层面实现多应用并发。而并发的实现是通过 CPU 时间片不端切换执行的。对于单核 CPU 来说,在任意一个时刻只会有一个进程在被CPU 调度
二、线程
-
在多核 CPU 中,利用多线程可以实现真正意义上的并行执行
-
在一个应用进程中,会存在多个同时执行的任务,如果其中一个任务被阻塞,将会引起不依赖该任务的任务也
被阻塞。通过对不同任务创建不同的线程去处理,可以提升程序处理的实时性
- 线程可以认为是轻量级的进程,所以线程的创建、销毁比进程更快
创建线程的四种方式
- 继承 Thread 类创建线程
- 实现 Runnable 接口创建线程
- 实现 Callable 接口通过 FutureTask 包装器来创建 Thread 线程
- 通过线程池创建线程
三、线程的六种状态
-
线程一共有 6 种状态(NEW、RUNNABLE、BLOCKED、WAITING、TIME_WAITING、TERMINATED)以枚举的方式存储在Thread类内部
- NEW:初始状态,线程被构建,但是还没有调用 start 方法RUNNABLED:运行状态,JAVA 线程把操作系统中的就绪和运行两种状态统一称为“运行中”
- BLOCKED:阻塞状态,表示线程进入等待状态,也就是线程因为某种原因放弃了 CPU 使用权,阻塞也分为几种情况:
- 等待阻塞:运行的线程执行 wait 方法,jvm 会把当前线程放入到等待队列
- 同步阻塞:运行的线程在获取对象的同步锁时,若该同步锁被其他线程锁占用了,那么 jvm 会把当前的线程放入到锁池中
- 其他阻塞:运行的线程执行 Thread.sleep 或者 t.join 方法,或者发出了 I/O 请求时,JVM 会把当前线程设置为阻塞状态,当 sleep 结束、join 线程终止、io 处理完毕则线程恢复
- TIME_WAITING:超时等待状态,超时以后自动返回
- TERMINATED:终止状态,表示当前线程执行完毕
-
代码示例
public class ThreadStatusDemo { public static void main(String[] args) { //新建线程执行sleep方法 new Thread(()->{ while(true){ try { TimeUnit.SECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } },"Time_Waiting_Thread").start(); //新建线程执行await方法,需要notify唤醒,进入阻塞状态 new Thread(()->{ while(true){ synchronized (ThreadStatusDemo.class) { try { ThreadStatusDemo.class.wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } },"Wating_Thread").start(); //添加通过锁,进行锁争抢 为争抢到锁的线程进入BLOCKED new Thread(new BlockedDemo(),"Blocke01_Thread").start(); new Thread(new BlockedDemo(),"Blocke02_Thread").start(); } static class BlockedDemo extends Thread{ @Override public void run() { synchronized (BlockedDemo.class){ while(true){ try { TimeUnit.SECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } } } } } }
启动一个线程前,最好为这个线程设置线程名称,因为这样在使用 jstack 分析程序或者进行问题排查时,就会给开发人员提供一些提示;
显示线程所处状态(在idea的Terminal执行,target对应目录下)
- 运行该示例,打开终端或者命令提示符,键入“jps”, (JDK1.5 提供的一个显示当前所有 java 进程 pid 的命令)
- 根据上一步骤获得的 pid,继续输入 jstack pid(jstack是 java 虚拟机自带的一种堆栈跟踪工具。jstack 用于打印出给定的 java 进程 ID 或 core file 或远程调试服务的 Java 堆栈信息)
线程的启动和停止
-
线程的启动(start()方法和run方法的联系和区别),进入start底层,会发现调用了native方法,也就是外部方法,jvm中实现的方法start0,它在底层实现了对run方法的调用;主线程可以调用run方法,不过那是调用实体类的方法,属于主线程,不属于多线程,多次调用同一线程的start方法,会出现场错误;
-
线程的停止
-
interrupt() 方法
通过interrupt() 方法,发出中断标志,程序根据标志判断是是否停止线程,interrupt()并不能停止线程,它和return,特定循环中的break可以停止到当前的线程
public class StopThread extends Thread { @Override public void run() { while (true){ if(Thread.currentThread().isInterrupted()){ System.out.println("线程被中断了"); break; } } System.out.println("这是收到中断标志,跳出循环"); } } //main方法 public class Run { public static void main(String[] args) throws InterruptedException { Thread s = new StopThread(); s.start(); Thread.sleep(10); s.interrupt(); } }
-
虽然中断标志收到跳出了循环,但是线程后面的程序仍然继续执行了
-
抛异常
public class StopThreadUseException extends Thread { @Override public void run() { super.run(); try { for (int i = 0; i <500000 ; i++) { if(this.isInterrupted()){ System.out.println("线程被中断了"); throw new InterruptedException(); } System.out.println("i:"+i); } System.out.println("for后面的代码"); } catch (InterruptedException e) { e.printStackTrace(); } } } //main方法 public class Run { public static void main(String[] args) throws InterruptedException { // Thread s = new StopThread(); Thread s = new StopThreadUseException(); s.start(); Thread.sleep(10); s.interrupt(); } }
运行结果:执行的线程收到信号,抛出异常,并且跳出for的代码没有继续执行
-
stop方法:不安全,资源为完全退出,多线程中会导致数据不一致(已执行代码所修改的类变量不会还原)
-
中断复位
Thread.interrupted()是属于当前线程的,是当前线程对外界中断信号的一个响应,表示自己已经得到了中断信号,但不会立刻中断自己,具体什么时候中断由自己决定,让外界知道在自身中断前,他的中断状态仍然是 false,这就是复位的原因。,thread.interrupt()方法实际就是设置一个 interrupted 状态标识为 true、并且通过ParkEvent 的 unpark 方法来唤醒线程。
-
对于 synchronized 阻塞的线程,被唤醒以后会继续尝试获取锁,如果失败仍然可能被 park
-
在调用 ParkEvent 的 park 方法之前,会先判断线程的中断状态,如果为 true,会清除当前线程的中断标识
-
Object.wait 、 Thread.sleep 、 Thread.join 会 抛 出InterruptedException?
你会发现这几个方法有一个共同点,都是属于阻塞的方法,阻塞方法的释放会取决于一些外部的事件,但是阻塞方法可能因为等不到外部的触发事件而导致无法终止,所以它允许一个线程请求自己来停止它正在做的事情。当一个方法抛出 InterruptedException 时,它是在告诉调用者如果执行该方法的线程被中断,它会尝试停止正在做的事情并且通过抛出 InterruptedException 表示提前返回。所以,这个异常的意思是表示一个阻塞被其他线程中断了。然 后 , 由 于 线 程 调 用 了 interrupt() 中 断 方 法 , 那 么
Object.wait、Thread.sleep 等被阻塞的线程被唤醒以后会通过 is_interrupted 方法判断中断标识的状态变化,如果发现中断标识为 true,则先清除中断标识,然后抛出InterruptedException