《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)(三)

简介: 《JUC并发编程 - 高级篇》01 - 进程与线程概述 | 02 - Java线程(创建线程、查看线程、线程常见方法、线程状态)

2.7.2 yield


调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程

具体的实现依赖于操作系统的任务调度器

对比 yield与sleep


相同点:两者都是让当前线程不再执行,调度执行其他线程


不同点:


调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)。 此时当前线程不会被执行,直至达到sleep的时间或线程被唤醒;调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程,此时线程也就可能被再次调用,这依赖于任务调度器。

sleep()可以有时间参数,yield()中没有时间参数。


2.7.3 线程优先级


线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它

如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用


08a0b42cc84b61464ab01e7af7bcaa63.png


关于yield和优先级的对比

@Slf4j(topic = "c.Test9")
public class Test9 {
    public static void main(String[] args) {
        Runnable task1 = () -> {
            int count = 0;
            for (;;) {
                System.out.println("---->1 " + count++);
            }
        };
        Runnable task2 = () -> {
            int count = 0;
            for (;;) {
//                Thread.yield();
                System.out.println("              ---->2 " + count++);
            }
        };
        Thread t1 = new Thread(task1, "t1");
        Thread t2 = new Thread(task2, "t2");
        // t1.setPriority(Thread.MIN_PRIORITY);
        // t2.setPriority(Thread.MAX_PRIORITY);
        t1.start();
        t2.start();
    }
}

c92d74283aa136f0b181a23421674dea.png

2.7.4 sleep和yield使用案例

使用sleep和yield可以防止CPU占用达到 100 %


d5728b0369b885897cd3ed5613c972b7.png

2.8 join 方法详解

2.8.1 为什么需要 join

下面的代码执行,打印 r 是什么?

@Slf4j(topic = "c.Test10")
public class Test10 {
    static int r = 0;
    public static void main(String[] args) throws InterruptedException {
        test1();
    }
    private static void test1() throws InterruptedException {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            sleep(1);
            log.debug("结束");
            r = 10;
        },"t1");
        t1.start();
        log.debug("结果为:{}", r);
        log.debug("结束");
    }
}

打印结果为:0。


7e7d591e3049f96818984cd05915b32d.png


分析


因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10


而主线程一开始就要打印 r 的结果,所以只能打印出 r=0


一句话:因为异步性,后面的程序可能先执行,因此当进程睡觉的时候,就把r=0输出了 因此结果为0


解决方法


用 sleep 行不行?为什么? 可行,但是main线程需要和t1线程 sleep相同的时间。不推荐使用

用 join,加在 t1.start() 之后即可


db6cf5327e8c5df852979ebb26960a4f.png

2.8.2 应用之同步(案例1)

以调用方角度来讲,如果


需要等待结果返回,才能继续运行就是同步

不需要等待结果返回,就能继续运行就是异步


f9677bf93bddc35eefe4da8858598123.png

2.8.3 应用之同步(等待多个结果)

问,下面代码 cost 大约多少秒?

@Slf4j(topic = "c.TestJoin")
public class TestJoin {
    static int r1 = 0;
    static int r2 = 0;
    public static void main(String[] args) throws InterruptedException {
        test2();
    }
    private static void test2() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            sleep(1);
            r1 = 10;
        });
        Thread t2 = new Thread(() -> {
            sleep(2);
            r2 = 20;
        });
        t1.start();
        t2.start();
        long start = System.currentTimeMillis();
        log.debug("join begin");
        t1.join();
        log.debug("t1 join end");
        t2.join();
        log.debug("t2 join end");
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);// 2s
    }
}

7a5093816ba3d594ce635bdbbd22aa5f.png


分析如下


第一个 join:等待 t1 时, t2 并没有停止, 而在运行

第二个 join:1s 后, 执行到此, t2 也运行了 1s, 因此也只需再等待 1s

如果颠倒两个 join 呢?


最终输出的仍然是 2s


2c599b2416023b873f06671818f9a003.png


图解分析:


b449e3fc1ec09626f5a089f6caad4884.png


2.8.4 有时效的 join


没等够时间

@Slf4j(topic = "c.TestJoin")
public class TestJoin {
    static int r = 0;
    static int r1 = 0;
    static int r2 = 0;
    public static void main(String[] args) throws InterruptedException {
        test3();
    }
    public static void test3() throws InterruptedException {
        Thread t1 = new Thread(() -> {
            sleep(2);
            r1 = 10;
        });
        long start = System.currentTimeMillis();
        t1.start();
        // 线程执行结束会导致 join 结束
        log.debug("join begin");
        long end = System.currentTimeMillis();
        log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);
    }
}

1080df296562f264c7586fcbcc68dd2e.png


等够时间

t1.join(3000);


e05f8b7388f87b91fd03520635983755.png

2.9 interrupt 方法详解

2.9.1 打断 sleep,wait,join 的线程

这几个方法都会让线程进入阻塞状态,但是打断后,会清空打断状态

以打断 sleep 的线程为例:

@Slf4j(topic = "c.Test11")
public class Test11 {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            log.debug("sleep...");
            try {
                Thread.sleep(5000); // wait, join
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");
        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt");
        t1.interrupt();
        log.debug("打断标记:{}", t1.isInterrupted());
    }
}

运行结果:

ace9718729b95fd20449a19890387fe5.png

2.9.2 打断正常运行的线程

打断正常运行的线程, 不会清空打断状态 ,所以我们可以利用标记让线程进行结束。

@Slf4j(topic = "c.Test12")
public class Test12 {
    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();//t1的打断标记变为true
    }
}

fd1427d00c26bf7b55e6633df66922dc.png

通过标记打断线程的好处:因为直接把线程终结了,人家线程事情都没干完。不如跟他说一声,说我要打断你,他处理完事情后自行了断不更好。很优雅

相关文章
|
7月前
|
JSON 网络协议 安全
【Java】(10)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
368 1
|
7月前
|
JSON 网络协议 安全
【Java基础】(1)进程与线程的关系、Tread类;讲解基本线程安全、网络编程内容;JSON序列化与反序列化
几乎所有的操作系统都支持进程的概念,进程是处于运行过程中的程序,并且具有一定的独立功能,进程是系统进行资源分配和调度的一个独立单位一般而言,进程包含如下三个特征。独立性动态性并发性。
348 1
|
8月前
|
数据采集 存储 弹性计算
高并发Java爬虫的瓶颈分析与动态线程优化方案
高并发Java爬虫的瓶颈分析与动态线程优化方案
Java 数据库 Spring
339 0
|
8月前
|
算法 Java
Java多线程编程:实现线程间数据共享机制
以上就是Java中几种主要处理多线程序列化资源以及协调各自独立运行但需相互配合以完成任务threads 的技术手段与策略。正确应用上述技术将大大增强你程序稳定性与效率同时也降低bug出现率因此深刻理解每项技术背后理论至关重要.
516 16
|
9月前
|
缓存 并行计算 安全
关于Java多线程详解
本文深入讲解Java多线程编程,涵盖基础概念、线程创建与管理、同步机制、并发工具类、线程池、线程安全集合、实战案例及常见问题解决方案,助你掌握高性能并发编程技巧,应对多线程开发中的挑战。
|
9月前
|
数据采集 存储 前端开发
Java爬虫性能优化:多线程抓取JSP动态数据实践
Java爬虫性能优化:多线程抓取JSP动态数据实践
|
Linux 数据库 Perl
【YashanDB 知识库】如何避免 yasdb 进程被 Linux OOM Killer 杀掉
本文来自YashanDB官网,探讨Linux系统中OOM Killer对数据库服务器的影响及解决方法。当内存接近耗尽时,OOM Killer会杀死占用最多内存的进程,这可能导致数据库主进程被误杀。为避免此问题,可采取两种方法:一是在OS层面关闭OOM Killer,通过修改`/etc/sysctl.conf`文件并重启生效;二是豁免数据库进程,由数据库实例用户借助`sudo`权限调整`oom_score_adj`值。这些措施有助于保护数据库进程免受系统内存管理机制的影响。
|
Linux Shell
Linux 进程前台后台切换与作业控制
进程前台/后台切换及作业控制简介: 在 Shell 中,启动的程序默认为前台进程,会占用终端直到执行完毕。例如,执行 `./shella.sh` 时,终端会被占用。为避免不便,可将命令放到后台运行,如 `./shella.sh &`,此时终端命令行立即返回,可继续输入其他命令。 常用作业控制命令: - `fg %1`:将后台作业切换到前台。 - `Ctrl + Z`:暂停前台作业并放到后台。 - `bg %1`:让暂停的后台作业继续执行。 - `kill %1`:终止后台作业。 优先级调整:
1376 5
|
算法 Linux 调度
探索进程调度:Linux内核中的完全公平调度器
【8月更文挑战第2天】在操作系统的心脏——内核中,进程调度算法扮演着至关重要的角色。本文将深入探讨Linux内核中的完全公平调度器(Completely Fair Scheduler, CFS),一个旨在提供公平时间分配给所有进程的调度器。我们将通过代码示例,理解CFS如何管理运行队列、选择下一个运行进程以及如何对实时负载进行响应。文章将揭示CFS的设计哲学,并展示其如何在现代多任务计算环境中实现高效的资源分配。

热门文章

最新文章