什么是java多线程?
进程与线程
进程
- 当一个程序被运行,就开启了一个进程, 比如启动了qq,word
- 程序由指令和数据组成,指令要运行,数据要加载,指令被cpu加载运行,数据被加载到内存,指令运行时可由cpu调度硬盘、网络等设备
线程
- 一个进程内可分为多个线程
- 一个线程就是一个指令流,cpu调度的最小单位,由cpu一条一条执行指令
并行与并发
并发:单核cpu运行多线程时,时间片进行很快的切换。线程轮流执行cpu
并行:多核cpu运行 多线程时,真正的在同一时刻运行
为什么用多线程?
多线程能实现的都可以用单线程来完成,那单线程运行的好好的,为什么java要引入多线程的概念呢?
多线程的好处:
- 程序运行的更快!
- 充分利用cpu资源,目前几乎没有线上的cpu是单核的,发挥多核cpu强大的能力
java多线程的基本使用
定义任务、创建和运行线程
任务:线程的执行体。也就是我们的核心代码逻辑
定义任务
- 继承Thread类 (可以说是 将任务和线程合并在一起)
- 实现Runnable接口 (可以说是 将任务和线程分开了)
- 实现Callable接口 (利用FutureTask执行任务)
Thread实现任务的局限性
- 任务逻辑写在Thread类的run方法中,有单继承的局限性
- 创建多线程时,每个任务有成员变量时不共享,必须加static才能做到共享
Runnable和Callable解决了Thread的局限性
但是Runbale相比Callable有以下的局限性
- 任务没有返回值
- 任务无法抛异常给调用方
上下文切换
多核cpu下,多线程是并行工作的,如果线程数多,单个核又会并发的调度线程,运行时会有上下文切换的概念
cpu执行线程的任务时,会为线程分配时间片,以下几种情况会发生上下文切换。
- 线程的cpu时间片用完
- 垃圾回收
- 线程自己调用了 sleep、yield、wait、join、park、synchronized、lock 等方法
当发生上下文切换时,操作系统会保存当前线程的状态,并恢复另一个线程的状态,jvm中有块内存地址叫程序计数器,用于记录线程执行到哪一行代码,是线程私有的。
idea打断点的时候可以设置为Thread模式,idea的debug模式可以看出栈帧的变化
线程的阻塞
线程的阻塞可以分为好多种,从操作系统层面和java层面阻塞的定义可能不同,但是广义上使得线程阻塞的方式有下面几种
- BIO阻塞,即使用了阻塞式的io流
- sleep(long time) 让线程休眠进入阻塞状态
- a.join() 调用该方法的线程进入阻塞,等待a线程执行完恢复运行
- sychronized或ReentrantLock 造成线程未获得锁进入阻塞状态 (同步锁章节细说)
- 获得锁之后调用wait()方法 也会让线程进入阻塞状态 (同步锁章节细说)
- LockSupport.park() 让线程进入阻塞状态
sleep( )
使线程休眠,会将运行中的线程进入阻塞状态。当休眠时间结束后,重新争抢cpu的时间片继续运行
// 方法的定义 native方法
public static native void sleep(long millis) throws InterruptedException;
try {
// 休眠2秒
// 该方法会抛出 InterruptedException异常 即休眠过程中可被中断,被中断后抛出异常
Thread.sleep(2000);
} catch (InterruptedException异常 e) {
}
try {
// 使用TimeUnit的api可替代 Thread.sleep
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
}
join( )
join是指调用该方法的线程进入阻塞状态,等待某线程执行完成后恢复运行
// 方法的定义 有重载
// 等待线程执行完才恢复运行
public final void join() throws InterruptedException {
}
// 指定join的时间。指定时间内 线程还未执行完 调用方线程不继续等待就恢复运行
public final synchronized void join(long millis)
throws InterruptedException{}
Thread t = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
r = 10;
});
t.start();
// 让主线程阻塞 等待t线程执行完才继续执行
// 去除该行,执行结果为0,加上该行 执行结果为10
t.join();
log.info("r:{}", r);
// 运行结果
13:09:13.892 [main] INFO thread.TestJoin - r:10
线程的打断-interrupt()
// 相关方法的定义
public void interrupt() {
}
public boolean isInterrupted() {
}
public static boolean interrupted() {
}
打断标记:线程是否被打断,true表示被打断了,false表示没有
isInterrupted() 获取线程的打断标记 ,调用后不会修改线程的打断标记
interrupt()方法用于中断线程
- 可以打断sleep,wait,join等显式的抛出InterruptedException方法的线程,但是打断后,线程的打断标记还是false
- 打断正常线程 ,线程不会真正被中断,但是线程的打断标记为true