并发编程-线程的基本操作和原理

本文详细探讨了Java中Thread.join和Thread.sleep两个方法的使用及其原理。Thread.join确保线程执行顺序,保证数据可见性,而Thread.sleep使线程暂停指定时间。通过分析源码和工作流程,解释了它们如何影响线程调度和CPU资源分配。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

触发线程声明周期发生变化的操作
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资源竞争

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值