JUC------常见方法

java线程常见方法

在这里插入图片描述
在这里插入图片描述

1.start与run

直接调用run,主线程会运行所有代码

@Slf4j
public class StartAndRunTest {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
          log.info("running...");
        });
        //输出NEW状态
        System.out.println(thread.getState());
        thread.run();
        //还是输出NEW状态
        System.out.println(thread.getState());

        log.info("主线程");
        
        //输出
        //NEW
        //2024-05-02 20:27:38,181 INFO  [main] c.w.j.m.StartAndRunTest.lambda$main$0(13): running...
        //NEW
        //2024-05-02 20:27:38,186 INFO  [main] c.w.j.m.StartAndRunTest.main(21): 主线程

    }
}

使用start调用,各线程执行各线程的代码

@Slf4j
public class StartAndRunTest {
    public static void main(String[] args) {
        Thread thread = new Thread(() -> {
          log.info("running...");
        });
        //输出NEW状态
        System.out.println(thread.getState());
        thread.start();
        //输出RUNNABLE状态
        System.out.println(thread.getState());

        log.info("主线程");

        //输出
        //NEW
        //2024-05-02 20:27:38,181 INFO  [main] c.w.j.m.StartAndRunTest.lambda$main$0(13): running...
        //NEW
        //2024-05-02 20:27:38,186 INFO  [main] c.w.j.m.StartAndRunTest.main(21): 主线程

    }
}

总结

  • 直接调用 run 是在主线程中执行了 run,没有启动新的线程
  • 使用 start 是启动新的线程,通过新的线程间接执行 run 中的代码

2.sleep与yield方法

sleep

  1. 调用 sleep 会让当前线程从 Running 进入 Timed Waiting 状态(阻塞)
  2. 其它线程可以使用 interrupt 方法打断正在睡眠的线程,这时 sleep 方法会抛出 InterruptedException
  3. 睡眠结束后的线程未必会立刻得到执行
  4. 建议用 TimeUnit 的 sleep 代替 Thread 的 sleep 来获得更好的可读性

yield

  1. 调用 yield 会让当前线程从 Running 进入 Runnable 就绪状态,然后调度执行其它线程
  2. 具体的实现依赖于操作系统的任务调度器

sleep和yield的区别在于:
相同点:都是让出当前cpu的时间片。
不同点:sleep之后程序会处于阻塞状态,cpu不会考虑把时间片再分给他,直到其睡醒。而yield让出时间片之后,cpu还是会考虑调用此线程。

具体区别:https://blog.csdn.net/u013470512/article/details/21049009?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522171465440816800182775897%2522%252C%2522scm%2522%253A%252220140713.130102334…%2522%257D&request_id=171465440816800182775897&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2allsobaiduend~default-2-21049009-null-null.142v100pc_search_result_base9&utm_term=sleep%E5%92%8Cyield%E7%9A%84%E5%BC%82%E5%90%8C&spm=1018.2226.3001.4187

sleep的应用
在没有利用cpu来计算时,不要让while(true)空转浪费cpu,这时可以使用yield或sleep来让cpu的使用权给其他程序。

也就是说,下面代码在没有sleep时运行的话,cpu会飙到99%,但加了sleep之后就会恢复正常。

        while (true){
            try {
                TimeUnit.MILLISECONDS.sleep(5000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
  • 可以用wait或条件变量达到类似的效果
  • 不同的是,后两种都需要加锁,并且需要相应的唤醒操作,一般适用于要进行同步的场景
  • sleep适用于无需锁同步的场景

3. 线程优先级 setPriority(int)

  • 线程优先级会提示(hint)调度器优先调度该线程,但它仅仅是一个提示,调度器可以忽略它
  • 如果 cpu 比较忙,那么优先级高的线程会获得更多的时间片,但 cpu 闲时,优先级几乎没作用

4. join方法

等待某线程运行结束
以调用方角度来讲,如果

  • 需要等待结果返回,才能继续运行就是同步
  • 不需要等待结果返回,就能继续运行就是异步
/**
 * 若注释掉t1.join();则最终打印 r 的值是0
 * 若加上t1.join();则最终打印 r 的值是10
 * @author Spider Man
 * @date 2024-05-09 10:14
 */
@Slf4j
public class JoinTest {
    static int r = 0;

    public static void main(String[] args) throws InterruptedException {
        log.debug("开始");
        Thread t1 = new Thread(() -> {
            log.debug("开始");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
            log.debug("结束");
            r=10;
        },"t1");
        t1.start();

        t1.join();

        log.debug("结束,r结果为:{}",r);
    }
}

5. interrupt 方法

可以理解为给要打断的线程发送 打断信号,发送这个信号之后只是表示需要打断(即需要被打断的线程的打断标记isInterrupted 由false变为true),线程并不会自动终止。

  • 打断阻塞状态的线程
    sleep、wait、join的线程,会清空打断状态,抛出打断异常 java.lang.InterruptedException: sleep interrupted,并把打断状态重置为false
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            try {
                TimeUnit.SECONDS.sleep(5);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }

        }, "t1");
        t1.start();

        TimeUnit.SECONDS.sleep(1);
        log.debug("interrupt...");
        t1.interrupt();
        TimeUnit.SECONDS.sleep(1);
        log.debug("打断标记为...{}", t1.isInterrupted());
    }
  • 打断正常运行的线程
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
            while (true) {
                //对于正常运行的线程(非阻塞线程),如果收到打断信号,则isInterrupted()返回true,并抛出异常
                if (Thread.currentThread().isInterrupted()) {
                    log.debug("收到打断信号,线程终止");
                    break;
                }
            }
        }, "t1"); 

        t1.start();

        TimeUnit.SECONDS.sleep(1);
        log.debug("interrupt.....");
        t1.interrupt();
    }

两阶段终止 模式
问题:在一个线程T1中如何优雅终止线程T2?这里的优雅指的是给T2一个料理后事的机会。

错误思路

  • 使用线程对象的stop() (已被废弃)方法停止线程
    stop方法会正在杀死线程,如何这时线程对象锁住了共享资源,那么当它被杀死后就再也没有机会释放锁,其他线程将永远无法获取锁
  • 使用System.exit(int)方法停止线程
    这种做法会让整个程序停止,我们仅是停止一个线程。
@Slf4j
public class Interrupt3Test {
    private Thread monitor;

    public static void main(String[] args) throws InterruptedException {
        Interrupt3Test interrupt3Test = new Interrupt3Test();
        interrupt3Test.start();
        TimeUnit.SECONDS.sleep(5);
        interrupt3Test.stop();
    }

    public void start() {
        monitor = new Thread(() -> {
            while (true){
                Thread thread = Thread.currentThread();
                if (thread.isInterrupted()){
                    log.debug("料理后事");
                    break;
                }
                try {
                    TimeUnit.SECONDS.sleep(1);
                    log.debug("执行监控记录");
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    //此处是关键点,因为线程在sleep时,会清除中断标志位,所以需要重新设置标志位
                    // 捕获异常后,需要将标志位重新设置
                    thread.interrupt();
                }
            }
        });

        monitor.start();
    }

    public void stop(){
        monitor.interrupt();
    }
}

不推荐使用的方法

还有一些不推荐使用的方法,这些方法已过时,容易破坏同步代码块,造成线程死锁

方法名static功能说明
stop()停止线程运行
suspend()挂起(暂停)线程运行
resume()恢复线程运行

守护线程 setDaemon(boolean)

默认情况下,Java 进程需要等待所有线程都运行结束,才会结束。有一种特殊的线程叫做守护线程,只要其它非守
护线程运行结束了,即使守护线程的代码没有执行完,也会强制结束。

@Slf4j
public class daemonTest {
    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() -> {
           while (true){

           }
        },"t1");
        
        //如果没有设置守护线程,主线程结束,t1线程不会结束
        t1.setDaemon(true);
        t1.start();

        TimeUnit.SECONDS.sleep(3);
        log.debug("main 结束");
    }
}

注意

  • 垃圾回收器线程就是一种守护线程
  • Tomcat 中的 Acceptor 和 Poller 线程都是守护线程,所以 Tomcat 接收到 shutdown 命令后,不会等
    待它们处理完当前请求

线程的五种状态

操作系统 层面来描述的
在这里插入图片描述

  • 【初始状态】仅是在语言层面创建了线程对象,还未与操作系统线程关联
  • 【可运行状态】(就绪状态)指该线程已经被创建(与操作系统线程关联),可以由 CPU 调度执行
  • 【运行状态】指获取了 CPU 时间片运行中的状态
    当 CPU 时间片用完,会从【运行状态】转换至【可运行状态】,会导致线程的上下文切换
  • 【阻塞状态】
    1、 如果调用了阻塞 API,如 BIO 读写文件,这时该线程实际不会用到 CPU,会导致线程上下文切换,进入
    【阻塞状态】
    2、 等 BIO 操作完毕,会由操作系统唤醒阻塞的线程,转换至【可运行状态】
    3、 与【可运行状态】的区别是,对【阻塞状态】的线程来说只要它们一直不唤醒,调度器就一直不会考虑
    调度它们
  • 【终止状态】表示线程已经执行完毕,生命周期已经结束,不会再转换为其它状态

线程的六种状态

这是从 Java API 层面来描述的,根据 Thread.State 枚举,分为六种状态。

    public enum State {
        /**
         * Thread state for a thread which has not yet started.
         */
        NEW,

        /**
         * Thread state for a runnable thread.  
         */
        RUNNABLE,

        /**
         * Thread state for a thread blocked waiting for a monitor lock.
         */
        BLOCKED,

        /**
         * Thread state for a waiting thread.
         */
        WAITING,

        /**
         * Thread state for a waiting thread with a specified waiting time.
         */
        TIMED_WAITING,

        /**
         * Thread state for a terminated thread.
         * The thread has completed execution.
         */
        TERMINATED;
    }

在这里插入图片描述

  • NEW 线程刚被创建,但是还没有调用 start() 方法
    - RUNNABLE 当调用了 start() 方法之后,注意,Java API 层面的 RUNNABLE 状态涵盖了 操作系统 层面的
    【可运行状态】、【运行状态】和【阻塞状态】(由于 BIO 导致的线程阻塞,在 Java 里无法区分,仍然认为
    是可运行)
  • BLOCKEDWAITINGTIMED_WAITING 都是 Java API 层面对【阻塞状态】的细分,后面会在状态转换一节
    详述
  • TERMINATED 当线程代码运行结束

在这里插入图片描述

更详细的会在锁后面详细介绍

@Slf4j
public class ThreadStatusTest {
    public static void main(String[] args) {

        //不调用start  NEW
        Thread t1 = new Thread(() -> {

        }, "t1");

        //RUNNABLE
        Thread t2 = new Thread(() -> {
            while (true) {

            }
        }, "t2");

        //WAITING
        Thread t3 = new Thread(() -> {
            synchronized (ThreadStatusTest.class) {
                try {
                    TimeUnit.SECONDS.sleep(2000);
                } catch (InterruptedException e) {
                    throw new RuntimeException(e);
                }
            }
        }, "t3");

        //TIMED_WAITING
        Thread t4 = new Thread(() -> {
            try {
                t2.join();
            } catch (InterruptedException e) {
                throw new RuntimeException(e);
            }
        }, "t4");


        //BLOCKED
        Thread t5 = new Thread(() -> {
            synchronized (ThreadStatusTest.class) {
                try {
                    TimeUnit.SECONDS.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }, "t5");

        //TERMINATED
        Thread t6 = new Thread(() -> {
        }, "t6");

        t2.start();
        t3.start();
        t4.start();
        t5.start();
        t6.start();

        log.debug("{}", t1.getState());
        log.debug("{}", t2.getState());
        log.debug("{}", t3.getState());
        log.debug("{}", t4.getState());
        log.debug("{}", t5.getState());
        log.debug("{}", t6.getState());

    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值