从一道阿里面试题说起
前言
昨晚老东家微信群里一堆前同事充满兴致的在讨论一道据说是阿里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无限循环优雅一些了 (><)
本次“注水”博文到此结束,谢谢观看!