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

一、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()方法会真正杀死线程


    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...");

    }
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,519评论 5 468
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,842评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,544评论 0 330
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,742评论 1 271
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,646评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,027评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,513评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,169评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,324评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,268评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,299评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,996评论 3 315
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,591评论 3 303
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,667评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,911评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,288评论 2 345
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,871评论 2 341

推荐阅读更多精彩内容