多线程基础-01.基础

1.进程与线程

进程

  • 程序由指令和数据组成,但这些指令要运行,数据要读写,就必须将指令加载至CPU,数据加载至内存。在指令运行过程中还需要用到磁盘、网络等设备。进程就是用来加载指令、管理内存、管理 IO 的。
  • 当一个程序被运行,从磁盘加载这个程序的代码至内存,这时就开启了一个进程。进程就可以视为程序的一个实例。

线程

  • 一个进程之内可以分为一到多个线程。
  • 一个线程就是一个指令流,将指令流中的一条条指令以一定的顺序交给 CPU 执行
  • Java 中,线程作为最小调度单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器

区别

  • 进程基本上相互独立的,而线程存在于进程内,是进程的一个子集
  • 进程拥有共享的资源,如内存空间等,供其内部的线程共享
  • 进程间通信较为复杂
    • 同一台计算机的进程通信称为 IPC(Inter-process communication)
    • 不同计算机之间的进程通信,需要通过网络,并遵守共同的协议,例如 HTTP
  • 线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量
  • 线程更轻量,线程上下文切换成本一般上要比进程上下文切换低

2.并行与并发

单核CPU下线程实际是串行执行的。操作系统中的组件任务调度器,将CPU的时间片分给不同的程序使用,速度极快。
多核CPU下,每个核都可以独立的运行线程,此时线程是可以并行的。

  • 串行: 在时间上不可能发生重叠,前一个任务未完成,下一个任务只能等待
  • 并行: 同一时间,多个任务互不干扰的同时执行
  • 并发: 允许两个任务彼此干扰,同一时间Ian,之一个任务运行,交替执行

应用: 多线程可以让方法执行变为异步,比如说读取磁盘文件时,假设读取操作花费了2秒钟,如果没有线程调度机制,这两秒钟CPU将停顿,等待文件读取完毕

结论:

  1. 单核 cpu 下,多线程不能实际提高程序运行效率,只是为了能够在不同的任务之间切换,不同线程轮流使用cpu ,不至于一个线程总占用 cpu,别的线程调度

  2. 多核 cpu 可以并行跑多个线程,但是不一定能提高程序的运行效率

  3. IO 操作不占用 cpu,只是我们一般拷贝文件使用的是【阻塞 IO】,这时相当于线程虽然不用 cpu,但需要一直等待 IO 结束,没能充分利用线程。所以才有后面的【非阻塞 IO】和【异步 IO】优化

3.Java线程

3.1.创建线程的几种方式:

  1. 直接继承 Thread
  2. 实现Runnable接口
  3. FutureTask 配合 Thread

FutureTask 能够接收 Callable 类型的参数,用来处理有返回结果的情况

// 创建任务对象
FutureTask<Integer> task3 = new FutureTask<>(() -> {
 doSomeThing();
 return 100;
})

3.2常见方法

Static方法:

sleep(long n):

让当前执行的线程休眠n毫秒,休眠时让出 cpu 的时间片给其它线程

yield() :

让线程调度器让出当前线程对CPU的使用权,进入就绪状态,会进行争抢CPU时间片

interrupted() :

判断当前线程是否被打断 会清除 打断标记

currentThread() :

获取当前正在执行的线程

非Static方法:

start() 启动一个新线程,让线程进入就绪状态,多次调用会抛出IllegalThreadStateException
join() 等待线程运行结束
join(long n) 等待线程运行结束,最多等待n毫秒
getName() 获取线程名
getId() 获取线程ID
setName() 修改线程名称
setPriority(int n) 设置线程优先级
getPriority() 设置线程优先级
isInterrupted() 判断是否被打断, 不会清除 打断标记
interrupt() 打断线程(设置标记),如果被打断线程正在 sleep,wait,join 会导致被打断的线程抛出 InterruptedException,并清除打断标记 ;如果打断的正在运行的线程,就设置打断标记
isAlive() 判断线程是否存活(运行完)
getState() 获取线程状态Java 中线程状态
运行状态:NEW, RUNNABLE, BLOCKED, WAITING, TIMED_WAITING, TERMINATED

sleep和yield的区别:

sleep:

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield:
5. 调用 yield 会让当前线程从 Running 进入Runnable 就绪状态
6. 具体的实现依赖于操作系统的任务调度器

join方法
执行后线程立即进入阻塞状态,例如:在线程B中调用了线程A的join方法,线程B就会进入阻塞状态,知道A线程执行完毕或中断(底层原理就是wait)

线程优先级:
线程优先级会提醒任务调度器优先调用该线程,在CPU忙时,优先级高的线程会获得更多的时间片,但CPU闲时几乎没有作用

3.3.Interrupt优雅的终止线程

特别注意:打断sleep,wait,join等阻塞的线程会清空打断标记(打断状态置为false),并抛出InterruptException

打断正常运行的线程不会抛出异常,不影响查线程正常运行,仅仅将打断标记置为true

利用Interrupt退出线程:

Thread t2 = new Thread(()->{
	while(true) {
		Thread current = Thread.currentThread();
		boolean interrupted = current.isInterrupted();
        if(interrupted) {
        	log.debug(" 打断状态: {}", interrupted);
            	break;
        }
	}
}, "t2");
t2.start();
Thread.sleep(5000);
t2.interrupt();
//运行结果
//23:53:15 Thread [t2] -  打断状态: true

两阶段终止模式(取代Stop等过时方法)

在一个线程t1中优雅的终止线程t2,给t2一个终止前处理的时间(释放锁,处理资源等)

终止线程的错误方法:

  • stop方法会杀死线程,如果这时线程锁住共享的资源,那么线程死亡后也无法释放锁,导致其他线程永远无法释放锁
  • System.exit() 方法会停止整个程序

运行流程分析
在这里插入图片描述
两阶段终止模式实现:

public class Test2 {
    public static void main(String[] args) throws InterruptedException {
        TwoPhaseTermination tpt = new TwoPhaseTermination();
        tpt.start();
        TimeUnit.SECONDS.sleep(3);
        tpt.stop();
    }
}

class  TwoPhaseTermination {
    private Thread monitor;

    // 启动监控线程
    public void start() {
        monitor = new Thread(() -> {
            while (true) {
                Thread current = Thread.currentThread();
                if (current.isInterrupted()) {
                    System.out.println("处理线程结束前置操作...");
                    break;
                }
                try {
                    TimeUnit.SECONDS.sleep(1); // 情况1被打断 打断标记FALSE
                    System.out.println("执行监控记录...");// 情况2被打断 打断标记TRUE
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    // 重新设置打断标记
                    current.interrupt();
                }
            }
        });

        monitor.start();
    }

    // 停止监控线程
    public void stop() {
        monitor.interrupt();
    }
}

在这里插入图片描述

3.4.几个过时的方法

方法名方法作用
stop()停止线程运行
suspend()挂起(暂停)线程运行
resume()恢复线程运行

不推荐使用这些方法,容易破坏同步代码块造成线程死锁,可使用两阶段终止模式替代

3.5.守护线程

定义: 默认情况下,Java进程需要等待所有线程都运行结束才会结束。守护线程就是一张特殊的线程,只要其它非守护线程都运行结束了(即使守护线程代码未执行完),也会强制结束

// 设置线程未守护线程
setDaemon(true)

应用:

  • 垃圾回收器线程
  • Tomcat 中的 Acceptor 和
    Poller 线程都是守护线程(用于接收请求和分发请求),所以当 Tomcat 接收到shutdown命令后,不会等待它们处理完当前请求

3.6.线程的五种状态

这是从操作系统层面来描述的
在这里插入图片描述

初始状态: 仅创建了线程对象,还未与操作系统线程相关联
可运行(就绪)状态: 线程已经被创建(Start),可以由CPU进行调度
运行状态: 获取了CPU时间片,运行中的状态。当CPU时间片用完,会从【运行状态】转换为【就绪状态】,导致CPU线程上下文切换
阻塞状态: 调用了阻塞API,或锁
结束状态: 线程已经执行完毕,生命周期结束,不会再转换为其他状态

3.7.线程的六种状态

JAVA层面
在这里插入图片描述
在这里插入图片描述

NEW :线程刚被创建,但是还没有调用 start() 方法
RUNNABLE :当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的
【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为
是可运行)
BLOCKED , WAITING , TIMED_WAITING :都是 Java API 层面对【阻塞状态】的细分,对应(获取不到锁阻塞 join, wait无时间阻塞 sleep有时间阻塞)
TERMINATED :线程代码运行结束

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值