为了账号安全,请及时绑定邮箱和手机立即绑定

Java多线程(7):JUC(下)

标签:
Java

您好,我是湘王,这是我的慕课手记,欢迎您来,欢迎您再来~


除了四种常见的同步器发令枪摇号器栅栏和交换机),JUC还有所谓线程安全的容器阻塞队列和一些特殊的类其中常出现的就是线程安全的容器和阻塞队列与其说这是两个大的分类还不如说它就是两个用得最多的类ConcurrentHashMapArrayBlockingQueue

我的风格是尽量少讲原理多讲实际生活中的案例除非它非常重要就像AQS这玩意绝对是个重量级的神器差不多整个JUC都是建立在它之上的如果说学习多线程只有一次集中全部精力的机会的话那我绝对建议把这份宝贵的精力花在AQS上物超所值至于搞明白ThreadPool底层原理synchronized关键字原理个人认为纯属浪费时间因为即使弄明白了那个机制你是改不了的AQS原理弄明白了可以直接拿来用自己玩出花来

从之前的集合类继承结构图可以知道ConcurrentHashMap继承自Map,实现了ConcurrentMap接口,它是线程安全的Map,侧重于放入或者获取的速度,而不在乎顺序。

因为现在的Java基本上都升级到了JDK1.8以上有的甚至和官方保持同步所以基本上很多JDK1.7及以下会出现的问题在新的版本中都修复了而且性能越来越好所以只需要知道一点在并发量比较高的环境中尽量使用ConcurrentHashMap就好了不用弄明白为什么因为不值得JDK1.8ConcurrentHashMap底层是用红黑树实现的至于什么是红黑树又得回头补一些基础而且记住了也没啥用)。当然如果是为了应付面试稍稍了解一下就好了

为了给大家直观的印象ConcurrentHashMap怎么个线程安全法这里那我之前的项目代码做个演示当然是删除了很多实际需求代码仅演示ConcurrentHashMap)。

电商里为了造一些刷单数据需要模拟真实用户那么就由代码产生好了

/**
 * 随机姓名产生器
 *
 * @author 湘王
 */
public class RandomInfo2 {
    private static final String firstname = "赵钱孙李周吴郑王冯陈褚卫蒋沈韩杨朱秦尤许何吕施张孔曹严华金魏陶姜戚谢邹喻水云苏潘" +
            "葛奚范彭郎鲁韦昌马苗凤花方俞任袁柳鲍史唐费岑薛雷贺倪汤滕殷罗毕郝邬安常乐于时傅卞齐康伍余元卜顾孟平黄和穆萧尹姚邵湛汪祁毛" +
            "禹狄米贝明臧计成戴宋茅庞熊纪舒屈项祝董粱杜阮席季麻强贾路娄危江童颜郭梅盛林刁钟徐邱骆高夏蔡田胡凌霍万柯卢莫房缪干解应宗丁" +
            "宣邓郁单杭洪包诸左石崔吉龚程邢滑裴陆荣翁荀羊甄家封芮储靳邴松井富乌焦巴弓牧隗山谷车侯伊宁仇祖武符刘景詹束龙叶幸司韶黎乔苍" +
            "双闻莘劳逄姬冉宰桂牛寿通边燕冀尚农温庄晏瞿茹习鱼容向古戈终居衡步都耿满弘国文东殴沃曾关红游盖益桓公晋楚闫";
    private static final String lastname = "伟刚勇毅俊秀娟英华慧巧美娜静淑惠珠翠雅芝玉萍红娥玲芬芳燕彩春菊兰凤洁梅琳素云莲真环" +
            "雪荣爱妹霞香月莺媛艳瑞凡佳嘉琼勤珍贞莉桂娣叶璧璐娅琦晶妍茜秋珊莎锦黛青倩婷姣婉娴瑾颖露瑶怡婵雁蓓纨仪荷丹蓉眉君琴蕊薇菁梦" +
            "岚苑婕馨瑗琰韵融园艺咏卿聪澜纯毓悦昭冰爽琬茗羽希宁欣飘育滢馥筠柔竹霭凝晓欢霄枫芸菲寒伊亚宜可姬舒影荔枝思丽峰强军平保东文" +
            "辉力明永健世广志义兴良海山仁波宁贵福生龙元全国胜学祥才发武新利清飞彬富顺信子杰涛昌成康星光天达安岩中茂进林有坚和彪博诚先" +
            "敬震振壮会思群豪心邦承乐绍功松善厚庆磊民友裕河哲江超浩亮政谦亨奇固之轮翰朗伯宏言若鸣朋斌梁栋维启克伦翔旭鹏泽晨辰士以建家" +
            "致树炎德行时泰盛雄琛钧冠策腾楠榕风航弘";

    /**
     * 随机产生姓氏
     *
     */
    public static String getFirstName() {
        int strLen = firstname.length();
        int index = new Random().nextInt(strLen - 1);
        return firstname.substring(index, index + 1);
    }

    /**
     * 随机产生名字
     *
     */
    public static String getLastName() {
        int strLen = lastname.length();
        int index = new Random().nextInt(strLen - 1);
        return lastname.substring(index, index + 2);
    }

    public static void count(final List<String> list, final Map<String, Integer> map) {
        long start = System.currentTimeMillis();
        // 这里为了比较效率,特意调大了人数
        for (int i = 0; i < 10_000_000; i++) {
            String name = getFirstName() + getLastName();
            list.add(name);
        }
        System.out.println("产生了 " + list.size() + " 个名字");
        for (int i = 0; i < 5; i++) {
            new Thread(new Runnable() {
                @Override
                public void run() {
                    list.stream().map(str -> str.substring(0, 1)).forEach(str -> {
                        Integer count = 1;
                        if (map.containsKey(str)) {
                            // 多线程情况下,普通的HashMap的get()方法得到的结果可能为null
                            // 但ConcurrentHashMap一定不会是null
                            count = map.get(str);
                            if (null == count) {
                                System.out.println("count == null");
                            }
                            ++count;
                        }
                        map.put(str, count);
                    });
                    System.out.println("总共 " + map.size() + " 个姓氏");
                }
            }).start();
        }
        int count = 0;
        for(Map.Entry<String, Integer> entry : map.entrySet()) {
            count = count + entry.getValue();
        }
        long end = System.currentTimeMillis();
        // 最后统计名字总数
        System.out.println("执行时间:" + (end - start) + " 毫秒");
    }

    public static void main(String[] args) throws IOException {
        List<String> list = new ArrayList<>();
        Map<String, Integer> map = new HashMap<>();
        count(list, map);

        list = new ArrayList<>();
        map = new ConcurrentHashMap<>();
        count(list, map);
    }
}

 

通过这个程序可以非常直观地看到这两者的区别至于原理就不必强记了因为你不可能记住记住了也没鸟用



ArrayBlockingQueue和LinkedBlockingQueue在之前的线程池ThreadPool中也提过它们的共同点都是一边放一边拿唯一的不同是一个是数量有限的一个是数量无限的

https://img1.sycdn.imooc.com//636124c40001b0ad05720495.jpg

 

之前拿上菜的案例演示了ArrayBlockingQueue现在同样的案例再用LinkedBlockingQueue来实现

/**
 * LinkedBlockingQueue测试
 *
 * @author 湘王
 */
public class LinkedBlockingQueueTester {
   public static BlockingQueue<String> queue = new LinkedBlockingQueue<String>(5);
   // 一个往里放
   class Producer implements Runnable {
      @Override
      public void run() {
         try {
            queue.offer("川菜");
            System.out.println(Thread.currentThread().getName() + " 厨师做好 川菜");
         } catch (Exception e) {
            e.printStackTrace();
         }
      }
   }
   // 一个往外拿
   class Consumer implements Runnable {
      @Override
      public void run() {
         try {
            String food = queue.poll();
            System.out.println(Thread.currentThread().getName() + " 客人消费 " + food);
         } catch (Exception e) {
            e.printStackTrace();
         }
      }
   }
   public static void main(String[] args) {
      // 和ArrayBlockingQueue不讲究顺序不同,LinkedBlockingQueue需要将生产的一方放在前面
      // 厨师做好菜
      for (int i = 0; i < 5; i++) {
         new Thread(new LinkedBlockingQueueTester().new Producer()).start();
      }
      // 客人等着菜
      for (int i = 0; i < 5; i++) {
         new Thread(new LinkedBlockingQueueTester().new Consumer()).start();
      }
   }
}


 

JUC中一些其他的类例如ForkJoinPool可以把任务分解成更小的任务,被分解出来任务会被继续分解成更小的任务,更形象地说法是“分形器”RecursiveTask是一种会返回结果的任务,它可以将自己分解成若干更小的任务,并将这些任务的执行结果合并到一个结果里这两种用的不多

整个JUC里面最重要的还是前面3种共6Java类掌握好AQS和这6个常见类JUC基本就没啥问题了




感谢您的大驾光临!咨询技术、产品、运营和管理相关问题,请关注后留言。欢迎骚扰,不胜荣幸~



点击查看更多内容
TA 点赞

若觉得本文不错,就分享一下吧!

评论

作者其他优质文章

正在加载中
  • 推荐
  • 评论
  • 收藏
  • 共同学习,写下你的评论
感谢您的支持,我会继续努力的~
扫码打赏,你说多少就多少
赞赏金额会直接到老师账户
支付方式
打开微信扫一扫,即可进行扫码打赏哦
今天注册有机会得

100积分直接送

付费专栏免费学

大额优惠券免费领

立即参与 放弃机会
意见反馈 帮助中心 APP下载
官方微信

举报

0/150
提交
取消