• 从一道阿里面试题说起


    前言

            昨晚老东家微信群里一堆前同事充满兴致的在讨论一道据说是阿里P7的面试题,不管题目来源是不是真的,但题目本身却比较有意思,虚虚实实去繁化简,却能看出一个人对Java知识掌握的深度以及灵活度。

            闲话少叙,咱们直接“上菜”。

    正文

    1、原代码如下所示,问执行之后打印的数是什么?

     1     static Integer count = 0;
     2     public static void main(String[] args) {
     3         for (int i = 0; i < 1000; i++) {
     4             new Thread(() -> {
     5                 synchronized (count) {
     6                     count++;
     7                 }
     8             }).start();
     9         }
    10 
    11         System.out.println(count);
    12     }

    相信只要对多线程的执行机制有了解的道友应该都会知道,上文中的同步块只是一个幌子,因为这一千个子线程不一定都会在main方法所在的主线程执行到第11行时都执行完,跟同步块没有半毛钱关系。所以第11行输出的结果是从0到1000不等的(理论上会出现的结果范围,实际很难出现)。

    2、以上面的为基础,延伸一下呢,比如加个while循环后最终打印的又是什么?

     1     static Integer count = 0;
     2     public static void main(String[] args) {
     3         for (int i = 0; i < 1000; i++) {
     4             new Thread(() -> {
     5                 synchronized (count) {
     6                     count++;
     7                 }
     8             }).start();
     9         }
    10 
    11         while (true) {
    12             System.out.println(count);
    13         }
    14     }

    首先我们需要知道count++这种操作是非原子操作;其次我们需要了解synchronized同步块的作用机制。

    synchronized同步是对一个对象加锁,如果synchronized加在非静态方法上,锁的是当前对象实例;如果加在静态方法上,锁的是当前类的Class对象;如果是一个单独的块,锁的就是括号后面的对象。可知此处是同步块,锁的就是count这个Integer对象了。

    如果我们的知识掌握到这里,得出的答案就是1000了,因为同步块能保证多个线程对同一个对象的操作是顺序执行的。但是实际执行的时候,你会发现很多时候最终打印的数据不是1000,是999或者998这种数,那这是为什么呢?

    其中的关键就出在count这个对象身上。synchronized实现的是对同一个对象加锁,但看一下Integer源码你会发现,它是final类型的,就是说当你对它进行+1的操作之后,得到的这个新的count对象已经不是之前的count对象了。既然锁的对象都不一样,自然就不会触发synchronized的同步机制了。

    至此可以看出,本题目不知考查了对同步块的理解,还附带了对jdk源码的考查。另,java中的装包类,都是final类型的。

    后记

            到此本应结束,但我后来觉得用while无限循环这种方式获取主线程的最终执行结果有点蠢,于是我给改造了一下:

     1     static Integer count = 0;
     2     public static void main(String[] args) throws InterruptedException {
     3         for (int i = 0; i < 1000; i++) {
     4             Thread thread = new Thread(() -> {
     5                 synchronized (count) {
     6                     count++;
     7                 }
     8             });
     9             thread.start();
    10             thread.join();
    11         }
    12 
    13         System.out.println(count);
    14     }

    用join来确保主线程最后执行(可参照博主之前的一篇文章 https://www.cnblogs.com/zzq6032010/p/10921870.html 了解join方法的作用),但是执行完之后,发现结果总是1000。待检查一番之后才恍然,

    此处用join方法是不合适的。因为当主线程执行到thread.join()这一行之后,正常的话会继续执行for循环的下一次循环,但是由于被子线程join了,所以需先执行完这个子线程才能继续走下一次for循环,这样造成的效果就是这一千个线程都是顺序启动顺序执行,不存在并发现象,所以结果也就都是1000了。可以发现,利用join有时也能做到同步的效果。

    既然join方法不行,那就用并发包中的CountDownLatch吧。

     1     static Integer count = 0;
     2     public static void main(String[] args) throws InterruptedException {
     3         CountDownLatch countDownLatch = new CountDownLatch(1000);
     4         for (int i = 0; i < 1000; i++) {
     5             new Thread(() -> {
     6                 synchronized (count) {
     7                     count++;
     8                     countDownLatch.countDown();
     9                 }
    10             }).start();
    11         }
    12         countDownLatch.await();
    13         System.out.println(count);
    14     }

    这样就比while无限循环优雅一些了 (><)

    本次“注水”博文到此结束,谢谢观看!

  • 相关阅读:
    A component required a bean named xxx that could not be found. Action: Consider defining
    jmeter 配置csv 登陆网站 报错
    动手动脑(文件与流)
    java异常处理总结
    动手动脑(异常处理)
    动手动脑(继承与多态)
    查询对象个数
    动手动脑
    动手动脑
    统计英语文章的单词
  • 原文地址:https://www.cnblogs.com/zzq6032010/p/11368318.html
Copyright © 2020-2023  润新知