前言
昨晚老东家微信群里一堆前同事充满兴致的在讨论一道据说是阿里 P7 的面试题, 不管题目来源是不是真的, 但题目本身却比较有意思, 虚虚实实去繁化简, 却能看出一个人对 Java 知识掌握的深度以及灵活度.
闲话少叙, 咱们直接 "上菜".
正文
1, 原代码如下所示, 问执行之后打印的数是什么?
- static Integer count = 0;
- public static void main(String[] args) {
- for (int i = 0; i <1000; i++) {
- new Thread(() -> {
- synchronized (count) {
- count++;
- }
- }).start();
- }
- System.out.println(count);
- }
相信只要对多线程的执行机制有了解的道友应该都会知道, 上文中的同步块只是一个幌子, 因为这一千个子线程不一定都会在 main 方法所在的主线程执行到第 11 行时都执行完, 跟同步块没有半毛钱关系. 所以第 11 行输出的结果是从 0 到 1000 不等的 (理论上会出现的结果范围, 实际很难出现).
2, 以上面的为基础, 延伸一下呢, 比如加个 while 循环后最终打印的又是什么?
- static Integer count = 0;
- public static void main(String[] args) {
- for (int i = 0; i <1000; i++) {
- new Thread(() -> {
- synchronized (count) {
- count++;
- }
- }).start();
- }
- while (true) {
- System.out.println(count);
- }
- }
首先我们需要知道 count++ 这种操作是非原子操作; 其次我们需要了解 synchronized 同步块的作用机制.
synchronized 同步是对一个对象加锁, 如果 synchronized 加在非静态方法上, 锁的是当前对象实例; 如果加在静态方法上, 锁的是当前类的 Class 对象; 如果是一个单独的块, 锁的就是括号后面的对象. 可知此处是同步块, 锁的就是 count 这个 Integer 对象了.
如果我们的知识掌握到这里, 得出的答案就是 1000 了, 因为同步块能保证多个线程对同一个对象的操作是顺序执行的. 但是实际执行的时候, 你会发现很多时候最终打印的数据不是 1000, 是 999 或者 998 这种数, 那这是为什么呢?
其中的关键就出在 count 这个对象身上. synchronized 实现的是对同一个对象加锁, 但看一下 Integer 源码你会发现, 它是 final 类型的, 就是说当你对它进行 + 1 的操作之后, 得到的这个新的 count 对象已经不是之前的 count 对象了. 既然锁的对象都不一样, 自然就不会触发 synchronized 的同步机制了.
至此可以看出, 本题目不知考查了对同步块的理解, 还附带了对 jdk 源码的考查. 另, java 中的装包类, 都是 final 类型的.
后记
到此本应结束, 但我后来觉得用 while 无限循环这种方式获取主线程的最终执行结果有点蠢, 于是我给改造了一下:
- static Integer count = 0;
- public static void main(String[] args) throws InterruptedException {
- for (int i = 0; i <1000; i++) {
- Thread thread = new Thread(() -> {
- synchronized (count) {
- count++;
- }
- });
- thread.start();
- thread.join();
- }
- System.out.println(count);
- }
用 join 来确保主线程最后执行 (可参照博主之前的一篇文章 https://www.cnblogs.com/zzq6032010/p/10921870.html 了解 join 方法的作用), 但是执行完之后, 发现结果总是 1000. 待检查一番之后才恍然,
此处用 join 方法是不合适的. 因为当主线程执行到 thread.join() 这一行之后, 正常的话会继续执行 for 循环的下一次循环, 但是由于被子线程 join 了, 所以需先执行完这个子线程才能继续走下一次 for 循环, 这样造成的效果就是这一千个线程都是顺序启动顺序执行, 不存在并发现象, 所以结果也就都是 1000 了. 可以发现, 利用 join 有时也能做到同步的效果.
既然 join 方法不行, 那就用并发包中的 CountDownLatch 吧.
- static Integer count = 0;
- public static void main(String[] args) throws InterruptedException {
- CountDownLatch countDownLatch = new CountDownLatch(1000);
- for (int i = 0; i <1000; i++) {
- new Thread(() -> {
- synchronized (count) {
- count++;
- countDownLatch.countDown();
- }
- }).start();
- }
- countDownLatch.await();
- System.out.println(count);
- }
这样就比 while 无限循环优雅一些了 (><)
本次 "注水" 博文到此结束, 谢谢观看!
posted on 2019-08-17 13:32 张曾经 阅读 (...) 评论 (...) 编辑 收藏
来源: https://www.cnblogs.com/zzq6032010/p/11368318.html