剑指JUC原理-3.线程常用方法及状态(上)

简介: 剑指JUC原理-3.线程常用方法及状态(上)

常用方法



start和run


调用run


public static void main(String[] args) {
 Thread t1 = new Thread("t1") {
 @Override
  public void run() {
    log.debug(Thread.currentThread().getName());
    FileReader.read(Constants.MP4_FULL_PATH);
  }
 };
 t1.run();
 log.debug("do other things ...");
}

输出:

19:39:14 [main] c.TestStart - main
19:39:14 [main] c.FileReader - read [1.mp4] start ...
19:39:18 [main] c.FileReader - read [1.mp4] end ... cost: 4227 ms
19:39:18 [main] c.TestStart - do other things ...

程序仍在 main 线程运行, FileReader.read() 方法调用还是同步的


调用start


将上述代码的 t1.run() 改为 t1.start


输出:

19:41:30 [main] c.TestStart - do other things ...
19:41:30 [t1] c.TestStart - t1
19:41:30 [t1] c.FileReader - read [1.mp4] start ...
19:41:35 [t1] c.FileReader - read [1.mp4] end ... cost: 4542 ms

此时再t1线程执行,而FileReader.read() 方法调用是异步的


总结


直接调用run是在主线程中执行了run,并没有启用新的线程,而使用start是启动新的线程,通过新的线程简介执行了run中的代码。


sleep和yield


sleep


**调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)**以一段代码为例

    Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        System.out.println("t1 state: "+ t1.getState());
        try {
            Thread.sleep(500);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println("t1 state: "+ t1.getState());

输出:

t1 state: RUNNABLE
t1 state: TIMED_WAITING

其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException

    Thread t1 = new Thread("t1") {
            @Override
            public void run() {
                log.debug("enter sleep...");
                try {
                    Thread.sleep(2000);
                } catch (InterruptedException e) {
                    log.debug("wake up...");
                    e.printStackTrace();
                }
            }
        };
        t1.start();
        Thread.sleep(1000);
        log.debug("interrupt...");
        t1.interrupt();

输出:

enter sleep...
wake up...
java.lang.InterruptedException: sleep interrupted
  at java.lang.Thread.sleep(Native Method)
  at test2$1.run(test2.java:10)

睡眠结束后的线程未必会立刻得到执行


睡眠结束,可能还在处理其他的线程,需要等待cpu调度器调度


yield


本质上是让出cpu的使用权, 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程。具体的实现依赖于操作系统的任务调度器


线程优先级


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


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


join方法详解


为什么需要join?


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

    log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            sleep(1);
            log.debug("结束");
            r = 10;
        },"t1");
        t1.start();
        // t1.join();
        log.debug("结果为:{}", r);
        log.debug("结束");

分析:


  • 因为主线程和线程 t1 是并行执行的,t1 线程需要 1 秒之后才能算出 r=10
  • 而主线程一开始就要打印 r 的结果,所以只能打印出 r=0


解决方案:


  • 用 sleep 行不行?为什么?不能精准的把控执行结束的时间。
  • 用 join,加在 t1.start() 之后即可**(可选)**


以调度的角度来讲,如果


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

等待多个结果

Thread t1 = new Thread(() -> {
  sleep(1);
  r1 = 10;
});
Thread t2 = new Thread(() -> {
  sleep(2);
  r2 = 20;
});
long start = System.currentTimeMillis();
t1.start();
t2.start();
t1.join();
t2.join();
long end = System.currentTimeMillis();
log.debug("r1: {} r2: {} cost: {}", r1, r2, end - start);

分析:


实际上,2s执行完成,为什么呢?因为 两个线程都是同时启动的,无论t1.join 和 t2.join那个在前那个在后,其最终都会对两个线程分别的阻塞等待,只因为两个线程是同时启动的。


所以结果时不变的,效果图如下:


剑指JUC原理-3.线程常用方法及状态(下):https://developer.aliyun.com/article/1413577

目录
相关文章
|
5天前
|
Java
并发编程之线程池的底层原理的详细解析
并发编程之线程池的底层原理的详细解析
52 0
|
5天前
|
消息中间件 安全 Ubuntu
【操作系统原理】—— 线程同步
【操作系统原理】—— 线程同步
16 1
|
5天前
简便的方法开线程干活并且出现等待提示
简便的方法开线程干活并且出现等待提示
14 3
|
5天前
|
Java
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
【Java多线程】面试常考 —— JUC(java.util.concurrent) 的常见类
24 0
|
5天前
|
NoSQL
线程死循环的定位方法
线程死循环的定位方法
25 2
|
5天前
使用代理IP池实现多线程的方法
使用代理IP池实现多线程的方法
|
5天前
|
Java 调度
《Java 多线程实战系列》- 01 基本概念与底层原理
《Java 多线程实战系列》- 01 基本概念与底层原理
19 0
|
5天前
|
监控 Java 关系型数据库
JVM工作原理与实战(十三):打破双亲委派机制-线程上下文类加载器
JVM作为Java程序的运行环境,其负责解释和执行字节码,管理内存,确保安全,支持多线程和提供性能监控工具,以及确保程序的跨平台运行。本文主要介绍了打破双亲委派机制的方法、线程上下文类加载器等内容。
24 2
|
5天前
|
Java
Java线程通信的精髓:解析通知等待机制的工作原理
Java线程通信的精髓:解析通知等待机制的工作原理
27 3
Java线程通信的精髓:解析通知等待机制的工作原理
|
3天前
|
Python