1. 1 不可撤销
  2. 2 小年兽 程嘉敏
  3. 3 手放开 李圣杰
  4. 4 迷人的危险3(翻自 dance flow) FAFA
  5. 5 山楂树之恋 程佳佳
  6. 6 summertime cinnamons / evening cinema
  7. 7 不谓侠(Cover 萧忆情Alex) CRITTY
  8. 8 神武醉相思(翻自 优我女团) 双笙
  9. 9 空山新雨后 音阙诗听 / 锦零
  10. 10 Wonderful U (Demo Version) AGA
  11. 11 广寒宫 丸子呦
  12. 12 陪我看日出 回音哥
  13. 13 春夏秋冬的你 王宇良
  14. 14 世界が终わるまでは… WANDS
  15. 15 多想在平庸的生活拥抱你 隔壁老樊
  16. 16 千禧 徐秉龙
  17. 17 我的一个道姑朋友 双笙
  18. 18 大鱼  (Cover 周深) 双笙
  19. 19 霜雪千年(Cover 洛天依 / 乐正绫) 双笙 / 封茗囧菌
  20. 20 云烟成雨(翻自 房东的猫) 周玥
  21. 21 情深深雨濛濛 杨胖雨
  22. 22 Five Hundred Miles Justin Timberlake / Carey Mulligan / Stark Sands
  23. 23 斑马斑马 房东的猫
  24. 24 See You Again Wiz Khalifa / Charlie Puth
  25. 25 Faded Alan Walker / Iselin Solheim
  26. 26 Natural J.Fla
  27. 27 New Soul Vox Angeli
  28. 28 ハレハレヤ(朗朗晴天)(翻自 v flower) 猫瑾
  29. 29 像鱼 王贰浪
  30. 30 Bye Bye Bye Lovestoned
  31. 31 Blame You 眠 / Lopu$
  32. 32 Believer J.Fla
  33. 33 书信 戴羽彤
  34. 34 柴 鱼 の c a l l i n g【已售】 幸子小姐拜托了
  35. 35 夜空中最亮的星(翻自 逃跑计划) 戴羽彤
  36. 36 慢慢喜欢你 LIve版(翻自 莫文蔚) 戴羽彤
  37. 37 病变(翻自 cubi) 戴羽彤
  38. 38 那女孩对我说 (完整版) Uu
  39. 39 绿色 陈雪凝
  40. 40 月牙湾 LIve版(翻自 F.I.R.) 戴羽彤
夜空中最亮的星(翻自 逃跑计划) - 戴羽彤
00:00 / 04:10

夜空中最亮的星 能否听清

那仰望的人 心底的孤独和叹息

夜空中最亮的星 能否记起

那曾与我同行 消失在风里的身影

我祈祷拥有一颗透明的心灵

和会流泪的眼睛

给我再去相信的勇气

越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请指引我靠近你

夜空中最亮的星 是否知道

那曾与我同行的身影 如今在哪里

夜空中最亮的星 是否在意

是等太阳先升起 还是意外先来临

我宁愿所有痛苦都留在心底

也不愿忘记你的眼睛

哦 给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行 哒~

我祈祷拥有一颗透明的心灵

和会流泪的眼睛 哦

给我再去相信的勇气

哦 越过谎言去拥抱你

每当我找不到存在的意义

每当我迷失在黑夜里

噢喔喔 夜空中最亮的星

请照亮我向前行

多线程之Exchanger

前言

今天我们来分享最后多线程最后一个工具类组件,之后我们会继续探索多线程的相关知识:线程池、并发容器和框架,然后就是总结和查漏补缺。

今天的内容很简单,内容也不太多,但是应用场景很典型,可以解决我们实际开发中数据对比的应用需求,好了,我们直接开始吧。

Exchanger

exchanger也是jdk1.5引入的,主要用来解决线程之间数据交换的问题,和它的字面意思一样,exchanger主要是用来交换数据的,需要注意的是,交换数据的时候只能是两个(一对)线程两两交换,下面我们直接看示例代码:

public class Example {
    private final static Exchanger<String> exchanger = new Exchanger<>();

    public static void main(String[] args) {
        ExecutorService executorService = Executors.newFixedThreadPool(4);
        executorService.execute(() -> {
            String taskStr = "10只羊";
            try {
                System.out.println("我是task1,正在等待交换,我有" + taskStr );
                String exchange = exchanger.exchange(taskStr);
                System.out.println("交换完成,task1获得:" + exchange);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        executorService.execute(() -> {
            String taskStr = "一头牛";
            try {
                System.out.println("我是task2,正在等待交换,我有" + taskStr );
                String exchange = exchanger.exchange(taskStr);
                System.out.println("交换完成,task2获得:" + exchange);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        executorService.execute(() -> {
            String taskStr = "50只鸡";
            try {
                System.out.println("我是task3,正在等待交换,我有" + taskStr );
                String exchange = exchanger.exchange(taskStr);
                System.out.println("交换完成,task3获得:" + exchange);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });
        executorService.execute(() -> {
            String taskStr = "40只鸭";
            try {
                System.out.println("我是task4,正在等待交换,我有" + taskStr );
                String exchange = exchanger.exchange(taskStr);
                System.out.println("交换完成,task4获得:" + exchange);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        });

        executorService.shutdown();
    }
}

在上面的代码中,我们定义了一个交换器Exchanger,它本身是支持泛型的,这里我们定义的是string,然后定义了一个线程池,通过线程池分别启动四个线程,在四个线程中,都有这样一行代码:

String exchange = exchanger.exchange(taskStr);

它的作用就是和其他线程交换数据,并拿到交换后的数据,然后我们运行示例代码:

数据交换前,他们分别拥有10只羊,一头牛,50只鸡,40只鸭,交换后task2拿到task1的牛,task1拿到task2的羊,其他的也一样,之所以用这个例子,是因为它的交换过程确实很像原始社会的以物易物。

这里需要注意的是,交换数据的线程数量必须为双数,否则线程会一直被exchange方法阻塞,这里我们把最后一个线程先删除掉,然后运行:

因为没有线程再与task3进行数据交换,所以线程被阻塞了。

当然有时候,阻塞并非是人为的,而是在某些特殊情况下发生,为了避免因为这种情况导致线程持续阻塞,我们可以用exchanger的另一个方法:

这个方法支持设定超时时间,如果到设定时间依然没有数据交换,该方法会抛出TimeoutException异常:

关于exchanger的应用场景,我能想到的就两个,一个就是数据校验,两个线程同时操作同一批数据,我们可以对数据最终的一致性做校验,互相验证,比如两个excel的数据比对;另外一个场景和这个很类似,就是我们在实际开发经常会遇到方法A的运行条件需要根据B的运行结果进行优化调整,这时候我们就可以通过exchanger来来进行数据交换,然后再继续触发相关操作。

总结

exchanger最大的优点是她可以在运行的过程中交换数据,其他的应用场景在遇到具体问题的时候再进一步分析吧。好了,exchanger的相关内容就到这里。

今天还要补充一个小知识,是关于mysql的,是一个小知识点,也是线上发现的一个小问题,具体来说就是mysql的求和语句,如果求和字段的值全部为null,那么最终的求和结果是null,而不是0,这样就会有潜在的空指针异常:

select course_type, sum(order_id) from course GROUP BY course_type

因为order_id都是null,所以最终sum(order_id)就是null:

这样如果你用包装类接收sum(order_id)就是null,后续在操作它的时候一定要做空指针校验,否则就是个线上bug。好了,就这么多,over

posted @ 2021-07-15 13:30  云中志  阅读(148)  评论(0编辑  收藏  举报