1、 进程与进程概念
进程
对正在运行程序的一种抽象,是资源分配和独立运行的基本单位。一个正在运行的一个应用程序就是一个进程。
线程
线程是进程中的一个执行单元,执行进程中的子任务,一个进程中至少有一个线程。
Java 中,线程是CPU调度的最小单位,进程作为资源分配的最小单位。 在 windows 中进程是不活动的,只是作为线程的容器(这里感觉要学了计算机组成原理之后会更有感觉吧!)
并发
在单核 cpu 下,线程实际还是串行执行的。操作系统中有一个组件叫做任务调度器,将 cpu 的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于 cpu 在线程间(时间片很短)的切换非常快,人类感觉是同时运行的 。一般会将这种线程轮流使用 CPU 的做法称为并发(concurrent)
并行
多核 cpu下,每个核(core) 都可以调度运行线程,这时候线程可以是并行的,不同的线程同时使用不同的cpu在执行。
线程上下文切换(Thread Context Switch)
因为以下一些原因导致 cpu 不再执行当前的线程,转而执行另一个线程的代码
线程的 cpu 时间片用完(每个线程轮流执行,看前面并行的概念)
- 垃圾回收
- 有更高优先级的线程需要运行
- 线程自己调用了
sleep
、yield
、wait
、join
、park
、synchronized
、lock
等方法
当 Context Switch 发生时,需要由操作系统保存当前线程的状态,并恢复另一个线程的状态,Java 中对应的概念就是程序计数器(Program Counter Register),它的作用是记住下一条 jvm 指令的执行地址,是线程私有的
2、 Thread的常见方法
sleep 与 yield
sleep
- 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
- 其它线程可以使用 interrupt 方法打断正在睡眠的线程,那么被打断的线程这时就会抛出
InterruptedException
异常【注意:这里打断的是正在休眠的线程,而不是其它状态的线程】 - 睡眠结束后的线程未必会立刻得到执行(需要分配到cpu时间片)
- 建议用 TimeUnit 的
sleep()
代替 Thread 的sleep()
来获得更好的可读性
yield
- 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
- 具体的实现依赖于操作系统的任务调度器(就是可能没有其它的线程正在执行,虽然调用了yield方法,但是也没有用)
小结
yield使cpu调用其它线程,但是cpu可能会再分配时间片给该线程;而sleep需要等过了休眠时间之后才有可能被分配cpu时间片
3.3.3 线程优先级
线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用
3.3.4 join
在主线程中调用t1.join,则主线程会等待t1线程执行完之后再继续执行 Test10.java
private static void test1() throws InterruptedException {
log.debug("开始");
Thread t1 = new Thread(() -> {
log.debug("开始");
sleep(1);
log.debug("结束");
r = 10;
},"t1");
t1.start();
t1.join();
log.debug("结果为:{}", r);
log.debug("结束");
}
3.3.5 interrupt 方法详解
打断 sleep,wait,join 的线程
先了解一些interrupt()方法的相关知识:博客地址
sleep,wait,join 的线程,这几个方法都会让线程进入阻塞状态,以 sleep 为例Test7.java
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread() {
@Override
public void run() {
log.debug("线程任务执行");
try {
Thread.sleep(10000); // wait, join
} catch (InterruptedException e) {
//e.printStackTrace();
log.debug("被打断");
}
}
};
t1.start();
Thread.sleep(500);
log.debug("111是否被打断?{}",t1.isInterrupted());
t1.interrupt();
log.debug("222是否被打断?{}",t1.isInterrupted());
Thread.sleep(500);
log.debug("222是否被打断?{}",t1.isInterrupted());
log.debug("主线程");
}
输出结果:(我下面将中断和打断两个词混用)可以看到,打断 sleep 的线程, 会清空中断状态,刚被中断完之后t1.isInterrupted()
的值为true
,后来变为false
,即中断状态会被清除。那么线程是否被中断过可以通过异常来判断。【同时要注意如果打断被join()
,wait()
blocked的线程也是一样会被清除,被清除(interrupt status will be cleared)的意思即中断状态设置为false
,被设置( interrupt status will be set)的意思就是中断状态设置为true
】
17:06:11.890 [Thread-0] DEBUG com.concurrent.test.Test7 - 线程任务执行
17:06:12.387 [main] DEBUG com.concurrent.test.Test7 - 111是否被打断?false
17:06:12.390 [Thread-0] DEBUG com.concurrent.test.Test7 - 被打断
17:06:12.390 [main] DEBUG com.concurrent.test.Test7 - 222是否被打断?true
17:06:12.890 [main] DEBUG com.concurrent.test.Test7 - 222是否被打断?false
17:06:12.890 [main] DEBUG com.concurrent.test.Test7 - 主线程
打断正常运行的线程
打断正常运行的线程, 线程并不会暂停,只是调用方法Thread.currentThread().isInterrupted();
的返回值为true,可以判断Thread.currentThread().isInterrupted();
的值来手动停止线程
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(() -> {
while(true) {
boolean interrupted = Thread.currentThread().isInterrupted();
if(interrupted) {
log.debug("被打断了, 退出循环");
break;
}
}
}, "t1");
t1.start();
Thread.sleep(1000);
log.debug("interrupt");
t1.interrupt();
}
终止模式之两阶段终止模式
Two Phase Termination,就是考虑在一个线程T1中如何优雅地终止另一个线程T2?这里的优雅指的是给T2一个料理后事的机会(如释放锁)。
如下所示:那么线程的isInterrupted()
方法可以取得线程的打断标记,如果线程在睡眠sleep
期间被打断,打断标记是不会变的,为false,但是sleep
期间被打断会抛出异常,我们据此手动设置打断标记为true
;如果是在程序正常运行期间被打断的,那么打断标记就被自动设置为true
。处理好这两种情况那我们就可以放心地来料理后事啦!
代码实现如下:
@Slf4j
public class Test11 {
public static void main(String[] args) throws InterruptedException {
TwoParseTermination twoParseTermination = new TwoParseTermination();
twoParseTermination.start();
Thread.sleep(3000); // 让监控线程执行一会儿
twoParseTermination.stop(); // 停止监控线程
}
}
@Slf4j
class TwoParseTermination{
Thread thread ;
public void start(){
thread = new Thread(()->{
while(true){
if (Thread.currentThread().isInterrupted()){
log.debug("线程结束。。正在料理后事中");
break;
}
try {
Thread.sleep(500);
log.debug("正在执行监控的功能");
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
e.printStackTrace();
}
}
});
thread.start();
}
public void stop(){
thread.interrupt();
}
}
3.3.6 sleep,yiled,wait,join 对比
关于join的原理和这几个方法的对比:看这里
补充:
- sleep,join,yield,interrupted是Thread类中的方法
- wait/notify是object中的方法
sleep 不释放锁、释放cpujoin 释放锁、抢占cpuyiled 不释放锁、释放cpuwait 释放锁、释放cpu
线程状态之六种状态
这是从 Java API 层面来描述的,我们主要研究的就是这种。状态转换详情图:地址
根据 Thread.State 枚举,分为六种状态 Test12.java
- NEW 跟五种状态里的初始状态是一个意思
- RUNNABLE 是当调用了
start()
方法之后的状态,注意,Java API 层面的RUNNABLE
状态涵盖了操作系统层面的【可运行状态】、【运行状态】和【io阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为是可运行) BLOCKED
,WAITING
,TIMED_WAITING
都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节
详述