《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

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

相关文章
|
2天前
|
Java
并发编程之线程池的底层原理的详细解析
并发编程之线程池的底层原理的详细解析
12 0
|
3天前
|
Java
并发编程之线程池的应用以及一些小细节的详细解析
并发编程之线程池的应用以及一些小细节的详细解析
16 0
|
14天前
|
安全 Java 数据处理
Python网络编程基础(Socket编程)多线程/多进程服务器编程
【4月更文挑战第11天】在网络编程中,随着客户端数量的增加,服务器的处理能力成为了一个重要的考量因素。为了处理多个客户端的并发请求,我们通常需要采用多线程或多进程的方式。在本章中,我们将探讨多线程/多进程服务器编程的概念,并通过一个多线程服务器的示例来演示其实现。
|
24天前
|
算法 数据处理 Python
Python并发编程:解密异步IO与多线程
本文将深入探讨Python中的并发编程技术,重点介绍异步IO和多线程两种常见的并发模型。通过对比它们的特点、适用场景和实现方式,帮助读者更好地理解并发编程的核心概念,并掌握在不同场景下选择合适的并发模型的方法。
|
29天前
|
消息中间件 安全 Linux
线程同步与IPC:单进程多线程环境下的选择与权衡
线程同步与IPC:单进程多线程环境下的选择与权衡
58 0
|
2天前
|
Java
Java中的并发编程:理解和应用线程池
【4月更文挑战第23天】在现代的Java应用程序中,性能和资源的有效利用已经成为了一个重要的考量因素。并发编程是提高应用程序性能的关键手段之一,而线程池则是实现高效并发的重要工具。本文将深入探讨Java中的线程池,包括其基本原理、优势、以及如何在实际开发中有效地使用线程池。我们将通过实例和代码片段,帮助读者理解线程池的概念,并学习如何在Java应用中合理地使用线程池。
|
2天前
|
监控 Java
并发编程之线程池的详细解析
并发编程之线程池的详细解析
7 0
|
5天前
|
Python
多ip多进程代理的实现方法
多ip多进程代理的实现方法
|
5天前
使用代理IP池实现多线程的方法
使用代理IP池实现多线程的方法
|
7天前
|
安全 Java 调度
Java并发编程:深入理解线程与锁
【4月更文挑战第18天】本文探讨了Java中的线程和锁机制,包括线程的创建(通过Thread类、Runnable接口或Callable/Future)及其生命周期。Java提供多种锁机制,如`synchronized`关键字、ReentrantLock和ReadWriteLock,以确保并发访问共享资源的安全。此外,文章还介绍了高级并发工具,如Semaphore(控制并发线程数)、CountDownLatch(线程间等待)和CyclicBarrier(同步多个线程)。掌握这些知识对于编写高效、正确的并发程序至关重要。