Java多线程 - 第三章

2019-03-12  本文已影响0人  也许________
join方法

先看下面代码,观察控制台输出

public static void shortSleep() {
        try {
            TimeUnit.SECONDS.sleep(1L);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

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

        Thread threadA = new Thread("线程A") {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "---" + i);
                    shortSleep();
                }
            }
        };

        Thread threadB = new Thread("线程B") {
            @Override
            public void run() {
                for (int i = 0; i < 10; i++) {
                    System.out.println(Thread.currentThread().getName() + "---" + i);
                    shortSleep();
                }
            }
        };

        List<Thread> threads = new ArrayList<>();
        threads.add(threadA);
        threads.add(threadB);

        for (Thread thread: threads) {
            thread.start();
        }

        for (int b = 0; b < 10; b++) {
            System.out.println(Thread.currentThread().getName() + "---" + b);
            shortSleep();
        }

    }

--- 控制台输出
main---0
线程A---0
线程B---0
main---1
线程A---1
线程B---1
main---2
线程A---2
线程B---2
main---3
线程A---3
线程B---3
线程A---4
main---4

可以看到主线程与子线程依次执行输出,如何让主线程在子线程完成后再执行呢?
当有如下业务场景:
用户查询航班信息,用户想看到所有航空公司的航班列表时,应当怎么做?
所有航空公司接口都调用一次,最后返回结果,这种方式属于串行查询,各家航空公司返回数据的时间不同,假设个别公司的接口出现问题,那么就会导致查询操作出现异常,即使接口不出现问题,多家航空公司的接口,一个一个的去调用,耗时也会非常久,非常影响用户体验。

这时使用多线程,让多个查询任务在子线程中执行,这样就会大大的缩短用户等待的时间,此时问题来了,之前的代码中看到子线程与主线程交替执行,那么在这个业务场景中如何确保主线程在所有的子线程完成后再执行,最后返回数据给前端?

这时就用到了join方法,join方法会使当前主线程处于等待状态,当子线程结束生命周期后主线程才会得以执行,观察如下代码以及控制台

public class JoinMethodExample {

    public static void main(String[] args) {

        List<String> results = new ArrayList<>();
        List<AirPortTask> tasks = new ArrayList<>();
        List<String> airPortCompanys = Arrays.asList("南航", "海航", "天津", "四川");
        String origin = "BJ";
        String destination = "SH";

        // 创建子线程
        for (String airPortName : airPortCompanys) {
            tasks.add(new AirPortTask(airPortName, origin, destination));
        }

        try {

            // 开始任务
            for (AirPortTask task : tasks) {
                task.start();
            }

            // 让主线程等待
            for (AirPortTask task : tasks) {
                task.join();
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        System.out.println("子线程结束");
        System.out.println("主线程开始执行");

        // 整理返回结果
        for (AirPortTask task : tasks) {
            results.addAll(task.get());
        }
        for (String str : results) {
            System.out.println("最终的返回结果" + str);
        }

    }

}

class AirPortTask extends Thread implements AirPortInterface {

    public String airPortName;
    public String origin;
    public String destination;
    public final List<String> results = new ArrayList<>();

    public AirPortTask(String airPortName, String origin, String destination) {
        super(airPortName);
        this.airPortName = airPortName;
        this.origin = origin;
        this.destination = destination;
    }

    @Override
    public void run() {

        String result = "航空公司:" + this.airPortName + " 起点:" + this.origin + " 终点:" + this.destination;
        long second = ThreadLocalRandom.current().nextInt(10);
        System.out.println( "子线程执行---" + this.getName() + " ---航空公司:" + this.airPortName + " 耗时:" + second + " 起点:" + this.origin + " 终点:" + this.destination);

        try {
            TimeUnit.SECONDS.sleep(second);
            results.add(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 实现接口,返回查询结果
     * @return
     */
    @Override
    public List<String> get() {
        return results;
    }
}

/**
 * 航班信息接口
 * 获取所有航空公司的查询结果
 */
interface AirPortInterface {

    /**
     * 获取所有航空公司的查询结果
     * @return
     */
    List<String> get();

}
--- 控制台输出
子线程执行---南航 ---航空公司:南航 耗时:9 起点:BJ 终点:SH
子线程执行---海航 ---航空公司:海航 耗时:9 起点:BJ 终点:SH
子线程执行---天津 ---航空公司:天津 耗时:5 起点:BJ 终点:SH
子线程执行---四川 ---航空公司:四川 耗时:7 起点:BJ 终点:SH
子线程结束
主线程开始执行
最终的返回结果航空公司:南航 起点:BJ 终点:SH
最终的返回结果航空公司:海航 起点:BJ 终点:SH
最终的返回结果航空公司:天津 起点:BJ 终点:SH
最终的返回结果航空公司:四川 起点:BJ 终点:SH
interrupt

中断线程,stop方法已经废弃,不推荐使用
使用interrupt方法来中断线程,但需要注意,中断线程需要捕捉中断状态才会结束任务,如果没有捕捉到,那么执行该方法无效,下面是两种捕捉中断状态的方式来结束线程任务

Thread thread = new Thread() {
            @Override
            public void run() {
                while (!isInterrupted()) {
                    System.out.println("still working....");
                }
                System.out.println("not working... exit");
            }
        };

        thread.start();
        try {
            TimeUnit.SECONDS.sleep(2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
        System.out.println("主线程代码...");
--- 控制台输出
still working....
still working....
still working....
still working....
still working....
still working....
still working....
still working....
still working....
still working....
still working....
still working....
主线程代码...
not working... exit

代码中使用了interrupt方法中断了线程,在run方法中isInterrupted()方法,判断是否已中断,其实就是捕获中断状态,如果注掉该行代码,会发现即使执行了中断方法,线程也不会停止,下面再看另一种方式

Thread thread = new Thread() {
            @Override
            public void run() {
                try {
                    while (true) {
                        System.out.println("子线程执行任务中....");
                        TimeUnit.SECONDS.sleep(1);
                    }
                } catch (Exception e) {
                    System.out.println("子线程发生异常");
                    e.printStackTrace();
                }
            }
        };

        thread.start();
        System.out.println("主线程开始执行任务");
        try {
            TimeUnit.SECONDS.sleep(5);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        thread.interrupt();
---- 控制台输出
主线程开始执行任务
子线程执行任务中....
子线程执行任务中....
子线程执行任务中....
子线程执行任务中....
子线程执行任务中....
子线程发生异常
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.example.thread.test.Test9$1.run(Test9.java:19)

通过上图中的代码以及控制台输出,可以发现,只有当线程任务被阻塞时,在主线程调用子线程的中断方法,才会中断子线程任务,注意必须使用try块进行捕获中断异常,否则子线程并不会解除阻塞并且中断任务
ps:一旦线程在阻塞的情况下被打断,会抛出一个java.lang.InterruptedException异常

中断信号

Thread.isInterrupted()方法,只会判断是否中断并不会更改中断信号
sleep()方法会擦除中断信号,通过下面例子来验证

Thread thread = new Thread(){
            @Override
            public void run() {
                try {
                    System.out.println("子线程开始执行");
                    TimeUnit.MINUTES.sleep(2);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                    System.out.println("发生异常,线程结束" + this.isInterrupted());
                }
            }
        };

        thread.start();
        try {
            System.out.println("主线程开始执行");
            TimeUnit.MILLISECONDS.sleep(5);
            System.out.println(thread.isInterrupted());
            thread.interrupt();
            // 注释这行代码观察控制台
            TimeUnit.MILLISECONDS.sleep(5);
            System.out.println(thread.isInterrupted());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
--- 控制台输出
主线程开始执行
子线程开始执行
java.lang.InterruptedException: sleep interrupted
    at java.lang.Thread.sleep(Native Method)
    at java.lang.Thread.sleep(Thread.java:340)
    at java.util.concurrent.TimeUnit.sleep(TimeUnit.java:386)
    at com.example.thread.test.Test9$4.run(Test9.java:120)
false
发生异常,线程结束false
false

示例中输出三次线程中断状态,可以发现都是false,第一个false,因为线程首次执行,肯定是非中断状态,第二次和第三次,都会先执行sleep()方法,擦除中断状态,所以会输出false,把代码中写注释的那行代码注掉,再观察输出,会发现最后一次输出变成了true,这就证明了sleep()方法会擦除线程的中断信号

ps:
Thread.isInterrupted() 不会擦除 interrupt信号
Thread.interrupted() 会擦除interrupt信号,并且首次会返回true,第二次以及后面都会返回false
sleep() 会擦除interrupt信号
这一块擦除信号对业务的影响还需要再研究,先备注一下

上一篇下一篇

猜你喜欢

热点阅读