0
点赞
收藏
分享

微信扫一扫

04给女朋友讲讲并发编程-线程的常见方法

沪钢木子 2021-09-30 阅读 29
Java学习

一、start()与run()

好多面试官也会作为一道面试题,问你start()与run()方法的区别是什么?接下来给大家讲解一下。
其实很好理解,run()方法是类中的一个普通方法,当我们使用Thread类或者Runnable接口时,会重写此方法。如果我们直接调用run()方法,如下:

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug("running...");
            }
        },"t1");
        t1.run();
    }

打印结果:

22:31:50.919 DEBUG [main] c.Test1 - running...

我们可以看见,执行run()方法的其实是main线程,并没有使用新创建的t1线程去执行。
如果调用start()方法:

    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                log.debug("running...");
            }
        },"t1");
        t1.start();
    }

打印结果:

22:34:41.522 DEBUG [t1] c.Test1 - running...

我们可以看见,此时执行run()方法中内容的是t1线程。

二者区别:
run()方法是线程类内部的一个普通方法,直接调用并不会异步执行、不会提高性能。
start()方法是用来启动线程的方法,调用此方法后线程会进入就绪状态等待任务调度器调度执行,可以异步执行方法,提高性能。

二、sleep()和yield()

sleep()方法---线程休眠(调用此方法的线程状态从RUNNABLE状态变成TIMED_WAITING)

    public static void main(String[] args) {       
        Thread t1 = new Thread(() ->{
            try {
                log.debug("t1进入休眠状态...");
                Thread.sleep(10000);
                log.debug("t1休眠结束...");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        },"t1");
        t1.start();

        log.debug("t1 当前的状态是:"+t1.getState());
        try {
            Thread.sleep(5000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug("t1 当前的状态是:"+t1.getState());
    }

打印结果:

22:52:07.902 DEBUG [main] c.Test2 - t1 当前的状态是:RUNNABLE
22:52:07.902 DEBUG [t1] c.Test2 - t1进入休眠状态...
22:52:12.903 DEBUG [main] c.Test2 - t1 当前的状态是:TIMED_WAITING
22:52:17.903 DEBUG [t1] c.Test2 - t1休眠结束...

sleep的可读性-TimeUnit(java.util.concurrent)在jdk1.5之后

        //线程休眠5秒
        Thread.sleep(5000);
       //使用TimeUnit让线程休眠5秒,可以指定时间单位,增加代码的可读性
        TimeUnit.SECONDS.sleep(5);

其实TimeUnit内部调用的也是Thread.sleep()方法,只是内部做了转换。源码如下:

    public void sleep(long timeout) throws InterruptedException {
        if (timeout > 0) {
            long ms = toMillis(timeout);
            int ns = excessNanos(timeout, ms);
            Thread.sleep(ms, ns);
        }
    }

yield()方法---让出线程 调用此方法的线程从运行状态变成就绪状态,等待cpu任务调度器再次调度执行。

二者区别:

sleep()方法--线程会进入阻塞状态(TIME_WAITING),任务调度器不会调用阻塞状态的线程,直到指定的休眠时间到了才会继续执行。
yield()方法-- 线程会重新变成就绪状态,任务调度器会再次调度执行。

sleep()方法的应用:防止CPU占用率100%。

三、join()方法

先看一个例子:

    static int count = 0;
    public static void main(String[] args) {
        Thread t1 = new Thread(() ->{
            log.debug("start...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count = 10;
            log.debug("end...");
        },"t1");
        t1.start();
        log.debug(String.valueOf(count));
    }

输出结果:

22:52:22.261 DEBUG [main] c.Test5 - 0
22:52:22.261 DEBUG [t1] c.Test5 - start...
22:52:23.263 DEBUG [t1] c.Test5 - end...

有人可能有疑问,为什么输出的不是10而是0?t1线程中明明给count赋值为10了啊!
因为此处t1线程是不同于main线程而格外创建的线程,两条线程互不干扰只会执行自己手中的任务。当main线程创建并启动t1线程后,并不会主动等待t1线程执行结束后才会继续往下执行,而是直接往下执行,所以就会看到当前的输出结果。

可是有时候我就需要等待t1线程执行结束后,main线程才能继续往下运行怎么办?那么就是保证线程的同步,可以使用join()方法。
join()-- 等待调用线程执行结束

    static int count = 0;
    public static void main(String[] args) {
        Thread t1 = new Thread(() ->{
            log.debug("start...");
            try {
                TimeUnit.SECONDS.sleep(1);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            count = 10;
            log.debug("end...");
        },"t1");
        t1.start();
        //在获取结果之前,等待t1执行结束
        try {
            t1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        log.debug(String.valueOf(count));
    }

输出结果:

22:57:23.373 DEBUG [t1] c.Test5 - start...
22:57:24.374 DEBUG [t1] c.Test5 - end...
22:57:24.374 DEBUG [main] c.Test5 - 10

四、interrupt()方法

1.用于打断阻塞状态的线程,打断后会有打断标记,但是会重置标记为false。线程什么时候会进入阻塞状态?(当线程调用sleep(),wait(),join())

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() ->{
            log.debug("{}","sleep...");
            try {
                TimeUnit.SECONDS.sleep(2);
            } catch (InterruptedException e) {
               log.debug("{}","被打断...");
            }
        },"t1");

        t1.start();
        /**
         * 此处如果主线程不进入阻塞状态,那么t1线程还没进入阻塞状态,此时打断方法则为打断运行中的线程。
         *
         * 如果打断的是运行中的线程,打断标记不会被清除,调用其isInterrupted()方法返回的是true,表示该线程被打断了。
         * 如果打断的是阻塞中的线程,打断标记会被重置,调用其isInterrupted()方法返回的是false,表示该线程被打断了,但是标记被清除了。
         */
        Thread.sleep(200);

        t1.interrupt();

        log.debug("t1线程是否被打断:{}",t1.isInterrupted());
        
    }

输出结果:

21:31:18.131 DEBUG [t1] c.Test7 - sleep...
21:31:18.331 DEBUG [t1] c.Test7 - 被打断...
21:31:18.331 DEBUG [main] c.Test7 - t1线程是否被打断:false

2.用于打断正在执行的线程,打断后会有打断标记,如果被打断过,标记则为true。

    public static void main(String[] args) throws InterruptedException {
        Thread t1 = new Thread(() ->{
            while (Boolean.TRUE){
                boolean flag = Thread.currentThread().isInterrupted();
                if (flag){
                    break;
                }
            }
        });
        t1.start();

        TimeUnit.MILLISECONDS.sleep(500);

        t1.interrupt();
    }

总结:interrupt()方法不会真正终止线程,而是给被打断的线程做一个标记。我们可以根据这个标记来区分是否打断。
两阶段终止模式
Two Phase Termination
在一个线程T1中如何优雅的终止线程T2?这里的优雅指的是给T2线程一个料理后事的机会。
错误思路

  • 使用线程对象的stop()方法来停止
    问题:stop()方法会真正杀死线程


  • 使用System.exit(int)方法来停止
    问题:小题大作,我们仅仅是为了停止一个线程,这种做法会让整个程序都停止。


    public static void main(String[] args) throws InterruptedException {

        //创建监控线程
        Thread monitor = new Thread(() ->{
                while (true){
                    Thread t1 = Thread.currentThread();
                    if (t1.isInterrupted()){
                        log.debug("{}","料理后事...");
                        break;
                    }
                    try {
                        TimeUnit.SECONDS.sleep(2);
                        log.debug("{}","执行监控记录");
                    } catch (InterruptedException e) {
                        //此处再次打断是为了解决监控线程在睡眠中被打断,标记会被重置的问题。
                        t1.interrupt();
                    }
                }

            },"monitor");

            monitor.start();
            //主线程隔5s后打断监控线程
            Thread.sleep(5000);

            monitor.interrupt();

    }

3.用于打断park()线程
如果一个线程调用了park()方法,会进入阻塞状态不会继续往下执行

    public static void main(String[] args) {
        Thread t1 = new Thread(() ->{
            log.debug("{}","park...");
            //如果此线程调用了park()方法,那么会进入阻塞状态,不会继续往下执行
            LockSupport.park();
            log.debug("{}","unPark...");
        },"t1");

        t1.start();        
    }

输出结果:

11:01:17.083 [t1] - park...

使用interrupt()打断

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() ->{
            log.debug("{}","park...");
            //如果此线程调用了park()方法,那么会进入阻塞状态,不会继续往下执行
            LockSupport.park();
            log.debug("{}","unPark...");
            log.debug("被打断,标记为{}",Thread.currentThread().isInterrupted());
        },"t1");

        t1.start();
        TimeUnit.SECONDS.sleep(1);
        //1s后打断t1线程
        t1.interrupt();
    }

输出结果:

11:05:52.550 [t1] - park...
11:05:53.549 [t1] - unPark...
11:05:53.549 [t1] - 被打断,标记为true

LockSupport.park()方法有个特点,如果该线程有打断标记后,再次执行此方法会不生效,无法park住线程执行。

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() ->{
            log.debug("{}","park...");
            //如果此线程调用了park()方法,那么会进入阻塞状态,不会继续往下执行
            LockSupport.park();
            log.debug("{}","unPark...");
            log.debug("被打断,标记为{}",Thread.currentThread().isInterrupted());
            //已经存在打断标记的线程 再次调用park()方法
            LockSupport.park();
            log.debug("{}","unPark...");
        },"t1");

        t1.start();
        TimeUnit.SECONDS.sleep(1);
        //1s后打断t1线程
        t1.interrupt();
    }

输出结果:

11:09:07.548 [t1] - park...
11:09:08.548 [t1] - unPark...
11:09:08.548 [t1] - 被打断,标记为true
11:09:08.548 [t1] - unPark...

我们可以看见,unPark打印了两次。如何解决?线程的方法中有一个interrupted()方法也是判断线程是否存在打断标记,但是返回结果后会将该线程的打断标记清除掉。

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(() ->{
            log.debug("{}","park...");
            //如果此线程调用了park()方法,那么会进入阻塞状态,不会继续往下执行
            LockSupport.park();
            log.debug("{}","unPark...");
            //此处获取线程打断标记使用interrupted()方法,返回结果后会清除打断标记
            log.debug("被打断,标记为{}",Thread.interrupted());
            //已经存在打断标记的线程 再次调用park()方法
            LockSupport.park();
            log.debug("{}","unPark...");
        },"t1");

        t1.start();
        TimeUnit.SECONDS.sleep(1);
        //1s后打断t1线程
        t1.interrupt();
    }

输出结果:

11:11:46.529 [t1] - park...
11:11:47.528 [t1] - unPark...
11:11:47.528 [t1] - 被打断,标记为true

五、不推荐使用的方法

  • stop()-停止
  • suspend()-挂起
  • resume()-恢复
    以上方法jdk官网也不推荐使用,已经被标记为过期方法。上述方法会影响到同步代码块,引起线程获取锁后不会释放锁的问题,破坏代码的安全性。

六、守护线程

先看一下这段代码,当主线程执行结束后,程序并不会结束,因为t1线程还在运行中。

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(()->{
            while (true){
                if (Thread.currentThread().isInterrupted()){
                    break;
                }
            }
            log.debug("{}","over...");
        },"t1");
        
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        log.debug("{}","over...");

    }

把t1线程设为守护线程后,我们发现当主线程结束后,t1也不会继续执行死循环了,整个程序会结束。

    public static void main(String[] args) throws InterruptedException {

        Thread t1 = new Thread(()->{
            while (true){
                if (Thread.currentThread().isInterrupted()){
                    break;
                }
            }
            log.debug("{}","over...");
        },"t1");
        //将t1设为守护线程
        t1.setDaemon(true);
        t1.start();
        TimeUnit.SECONDS.sleep(1);
        log.debug("{}","over...");

    }
举报

相关推荐

0 条评论