触发线程声明周期发生变化的操作
1.Thread.join的使用及原理
Thread.join的作用是保证线程执行结果的可见性
实例:
public class ThreadJoinTest {
public static int i = 0;
public static int x = 0;
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(()->{
i = 1;
x = 2;
},"Thread-01");
Thread t2 = new Thread(()->{
i = x +2;
},"Thread-01");
t1.start();
t2.start();
Thread.sleep(1000);
System.out.println("result = " + i);
}
}
执行该程序发现运行结果有两种
result = 1
result = 4
产生的原因是,t1,t2两个线程是在主线程中创建,但是执行的顺序是不确定的,当t1线程后执行时候,结果为result = 1
t1线程先执行时候,结果为result = 4
如何保证t1线程修改的数据对于t2线程是可见的呢,可以使用t1.join方法
实例:其他代码不变
t1.start();
t1.join();
t2.start();
执行结果不变,t1的值对于t2可见
result = 4
那么t1线程为什么通过调用join方法就实现了对于t2线程的可见性呢,分析如下
t1线程通过调用t1.join方法,阻塞main主线程,同时执行t1线程,t1线程执行完成后,再唤醒主线程继续执行t2线程,通过保证t1线程执行顺序早于t2线程,实现t1线程结果对于t2线程结果的可见性
t1线程如何阻塞主线程,并唤醒主线程
我们通过t1.join方法源码分析
t1.join();
调用Thread.join方法
public final void join() throws InterruptedException {
join(0);
}
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//判断当前主线程是否存活,如果存活,则调用wait方法,使主线程陷入阻塞状态
if (millis == 0) {
while (isAlive()) {
wait(0);
}
在java代码中并没有体现出t1线程执行完成后唤醒主线程的地方,实际的情况是,在jvm虚拟机层面,线程执行完成后调用exit方法,会对线程资源进行清理,并唤醒所有处于等待的线程调用notify_all方法唤醒主线程,有兴趣请自行查阅jvm源码见 exit方法和ensure_join方法
2.Thread.sleep
使线程暂停一顿时间,知道等待的时间结束才回复执行或在这段时间内被中断
实例:
public class SleepDemo extends Thread{
@Override
public void run() {
System.out.println("start = " + System.currentTimeMillis());
try {
Thread.sleep(3000);
System.out.println("end = " + System.currentTimeMillis());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
new SleepDemo().start();
}
}
执行结果
start = 1616510238252
end = 1616510241264
由结果可见,时间相差3000ms
Thread.sleep的工作流程
挂起线程并修改其运行状态
用sleep()提供的参数来设置一个定时器
当时间结束,定时器会触发,内核收到中断后修改线程的运行状态,例如线程会被标志为就绪二进入就绪队列,等待调度
线程的调度算法
操作系统中CPU竞争有很多种策略,Unix系统使用的是时间片算法,windows则属于抢占式
时间片算法: (时间片算法)分时调度模型是指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的CPU的时间片这个也比较好理解
抢占式: java虚拟机采用抢占式调度模型,是指优先让可运行池中优先级高的线程占用CPU,如果可运行池中的线程优先级相同,那么就随机选择一个线程,使其占用CPU。处于运行状态的线程会一直运行,直至它不得不放弃CPU
上面实例中,执行结果为
start = 1616510238252
end = 1616510241264
执行结果并不是刚好相差3000ms,其中还多了2ms,是因为3000ms休眠后,线程被唤醒,此时线程处于就绪状态,不是立马被执行的
Thread.Sleep(0)的意义:
让线程休眠0秒,让cpu根据优先级重新分配资源,立即触发一次cpu资源竞争