当前位置: 首页 > news >正文

面试记录之synchronized的惨败经历

面试记录之synchronized的惨败经历

热乎的啊。刚面试完

面试题描述

​ 3个线程,线程1打印A,线程2打印B,线程3打印C,循环n次

面试题分析

​ 这个题有两个地方要考虑:第一,三个线程有序执行,A->B->C,第二,共循环10次。

​ 那么有序执行可以通过锁+条件变量,循环10次可以粗暴的通过一个变量来控制。

​ synchronized+wait,ReentrantLock+Condition就可以。还有一个:信号量。我们知道synchronized+wait,ReentrantLock+Condition是标准的Java中的管程实现,那信号量也可以用来实现管程。

落地很重要,因为实现的时候会有很多意想不到的问题。

思路一:锁+条件变量

实现一:synchronized+wait+notify

synchronized+变量i来判断+wait+notify

Error实例:

public class PrintError {
    private static volatile int i = 0;
    private static final Object mutex = new Object();

    //method1:sync+wai+notifyAll
    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            System.out.println("=====threadA=====");
            synchronized (mutex) {
                if (i % 3 == 0) {
                    System.out.println("A");
                    i++;
                    mutex.notifyAll();
                } else {
                    try {
                        mutex.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });

        Thread threadB = new Thread(() -> {
            System.out.println("=====threadB=====");
            synchronized (mutex) {
                if (i % 3 == 1) {
                    System.out.println("B");
                    i++;
                    mutex.notifyAll();
                } else {
                    try {
                        mutex.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });


        Thread threadC = new Thread(() -> {
            System.out.println("=====threadC=====");
            synchronized (mutex) {
                if (i % 3 == 2) {
                    System.out.println("C");
                    i++;
                } else {
                    try {
                        mutex.wait();
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        threadA.start();
        threadB.start();
        threadC.start();
        //我的想法是,循环n次。。而当时面试官说了不让用thread.join。。orz
 		while(i<3*n){
           Thread.sleep(500); 
        }
    }

}

错误分析:

我的思路是:线程A,B,C。假设线程A来了,正好获取到锁,此时i=0,发现自己可以打印,然后i++,然后唤醒B跟C,然后她们俩再去打印。也就是说我认为B跟C被唤醒后是会从头执行的,看下运行结果:

运行结果:

在这里插入图片描述

在这里插入图片描述

在这里插入图片描述

emmm。。?我当时的反应是懵逼的,然后并没有反应过来是哪里有问题。注意看左侧,程序已经结束了,也就是说线程A跟线程B线程C在一轮循环之后,线程的生命周期已经结束了。

验证一下:

当然。是面试完验证的QAQ,如果面试的时候反应过来也不至于说是悲惨的经验了。。mmmp。。。


在这里插入图片描述

threadA执行完notifyAll后,线程状态:

在这里插入图片描述

也就是说线程A此时生命周期已经结束了

接下来面试官开始二连问:

被问倒の问题一:

	while(i<3*n){
           Thread.sleep(500); 
        }

这段代码有用吗?显然是没用的。。

我最开始为什么要加上这行,其实是因为想在主线程对变量i做控制,并且想让主线程等子线程结束(为什么要等子线程结束呢。。你别问我。。我也不知道,当时下意识就这么些写了)。当然,这也就是我路走窄的第一个地方。

被问倒の问题二:

上面三个线程什么时候结束?你这样写能打印N次吗?要不你来说说线程声明周期?

被问倒の问题三:

线程wait又被唤醒后,应该从哪里执行?wait方法下一行还是从头?

路走窄第二个地方:(说实话我的写法就表明了我错的离谱:我认为被唤醒后是从头开始执行的QAQ)

测试demo
public class WaitNotify {
    public static void main(String[] args) {
        Object mutex=new Object();
        Thread threadB = new Thread(() -> {
           while(true){
               synchronized (mutex) {
                   System.out.println("threadB working...");
                   try {
                       System.out.println("threadB waiting...");
                       mutex.notifyAll();
                       mutex.wait();
                       System.out.println("threadB back!!");
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                   }
               }
           }
        });


        Thread threadC = new Thread(() -> {
            while(true){
                synchronized (mutex) {
                    System.out.println("threadC working...");
                    try {
                        System.out.println("threadC waiting...");
                        mutex.notifyAll();
                        mutex.wait();
                        System.out.println("threadC back!!");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }

                }
            }
        });
        threadC.start();
        threadB.start();
    }
}
Thread threadC = new Thread(() -> {
            while(true){
                synchronized (mutex){
                    if (i % 3 == 2) {
                        System.out.println("C");
                        i++;
                    }else{
                        try {
                            mutex.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
测试结果:

事实证明啊。是从wait的下一行开始执行的。

threadB working...
threadB waiting...
threadC back!!
threadC working...
threadC waiting...

正确的代码实例:

public class Print {
    private static volatile  int i=0;
    private static final Object mutex=new Object();
    //method1:sync+wai+notifyAll
    public static void main(String[] args) throws InterruptedException {
        Thread threadA = new Thread(() -> {
            while(true){
                synchronized (mutex){
                    if (i % 3 == 0) {
                        System.out.println("A");
                        i++;
                        mutex.notifyAll();
                    }else{
                        try {
                            mutex.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

        });

        Thread threadB = new Thread(() -> {
            while(true){
                synchronized (mutex){
                    if (i % 3 == 1) {
                        System.out.println("B");
                        i++;
                        mutex.notifyAll();
                    }else{
                        try {
                            mutex.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }

        });


        Thread threadC = new Thread(() -> {
            while(true){
                synchronized (mutex){
                    if (i % 3 == 2) {
                        System.out.println("C");
                        i++;
                    }else{
                        try {
                            mutex.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                }
            }
        });
        threadWe.start();
        threadNeed.start();
        threadYou.start();
    }

}

实现二:ReentrantLock+Condition

既然刚是用synchronized+变量i判断谁来输出+条件变量,那么ReentrantLock+Condition也是可以的

ReentrantLock错误示范:

public class ABC_ReetrantLockError {
    private static final Lock lock = new ReentrantLock();
    private static final Condition conditionA = lock.newCondition();
    private static final Condition conditionB = lock.newCondition();
    private static final Condition conditionC = lock.newCondition();
    private static volatile int i;

    public static void main(String[] args) {
        //假设循环n次
        Thread threadA = new Thread(() -> {
            //这里有问题,到最后一次时,threadA是阻塞在conditionA。await上,被唤醒后,会继续往下走
            while (i < 30) {
                try {
                    lock.lock();
                    while (i % 3 != 0) {
                        try {
                            conditionA.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.print("A");
                    i++;
                    conditionB.signal();
                } finally {
                    lock.unlock();
                }
            }

        });

        Thread threadB = new Thread(() -> {
            while (i < 30) {
                try {
                    lock.lock();
                    while (i % 3 != 1) {
                        try {
                            conditionB.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.print("B");
                    i++;
                    conditionC.signal();
                } finally {
                    lock.unlock();
                }
            }

        });


        Thread threadC = new Thread(() -> {
            while (i < 30) {
                try {
                    lock.lock();
                    while (i % 3 != 2) {
                        try {
                            conditionC.await();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println("C");
                    i++;
                    conditionA.signal();
                } finally {
                    lock.unlock();
                }
            }

        });
        threadA.start();
        threadB.start();
        threadC.start();
    }
}

正确示范:

先唤醒再判断条件i,并且需要主线程来唤醒线程A。再把重复的代码抽出来,over。
问把大象装冰箱分为几步?😃

public class ABC_ReetrantLockRight {
    private static final Lock lock = new ReentrantLock();
    private static final Condition conditionA = lock.newCondition();
    private static final Condition conditionB = lock.newCondition();
    private static final Condition conditionC = lock.newCondition();
    private static volatile int i;

    public static void main(String[] args) {

        Thread threadA = new Thread(() -> run(conditionA, conditionB, "A"));
        Thread threadB = new Thread(() -> run(conditionB, conditionC, "B"));
        Thread threadC = new Thread(() -> run(conditionC, conditionA, "C"));

        threadA.start();
        threadB.start();
        threadC.start();

        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    lock.lock();
                    conditionA.signal();
                } finally {
                    lock.unlock();
                }
            }
        }).start();
    }

    private static void run(Condition awaitCondition, Condition signalCondition, String result) {
        while (true) {
            try {
                lock.lock();
                try {
                    awaitCondition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                signalCondition.signal();
                //假设循环10次
                if (i >= 30) {
                    break;
                }
                System.out.print(result);
                i++;
            } finally {
                lock.unlock();
            }
        }
    }
}

思路二:CyclicBarrier

因为看到了N轮,所以就想到了CyclicBarrier,但是还是需要条件变量保证顺序。

所以是可以,但是没必要啊。

思路三:信号量

根据要求,起几个线程搞几个信号量,然后按照顺序循环给信号量塞值即可。

public class ABC_SemaphoreRight {
    private static final Semaphore semaphoreA = new Semaphore(1);
    private static final Semaphore semaphoreB = new Semaphore(0);
    private static final Semaphore semaphoreC = new Semaphore(0);
    private static volatile int i;

    public static void main(String[] args) {
        Thread threadA = new Thread(() -> run(semaphoreA, semaphoreB, "A"));
        Thread threadB = new Thread(() -> run(semaphoreB, semaphoreC, "B"));
        Thread threadC = new Thread(() -> run(semaphoreC, semaphoreA, "C"));

        threadA.start();
        threadB.start();
        threadC.start();
    }

    private static void run(Semaphore signalSemaphore, Semaphore awaitSemaphore, String result) {
        while (true) {
            try {
                signalSemaphore.acquire();
                //假设循环10次
                if (i >= 30) {
                    break;
                }
                System.out.print(result);
                i++;
                awaitSemaphore.release();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
    }
}

知识扩展篇

你以为到上面就结束了?并不。接下来继续看下线程的几个同步器:

慢慢补吧。。睡觉了orz

AQS

CountDownLatch

CyclicBarrier

相关文章:

  • 面试复盘整理
  • Go语言基础_数据类型、基本语法篇
  • Go学习笔记_环境搭建
  • Markdown学习
  • Markdown下载客户端
  • JDK,JRE,JVM三者的区别
  • 2020-12-01
  • 2020-12-02
  • 比较三个数字,求出最大值
  • Scanner限制次数猜数字
  • ArrayList,随机抽取6个数字在【1-33】中的随机数,并且遍历
  • 利用ArrayList遍历集合
  • 用一个大集合存入20个随机数字,然后筛选其中的偶数元素,放到小集合中
  • String的用法截取,转换,切割
  • 由{1,2,3}转换成[word1#word2#word3#]]
  • 4月23日世界读书日 网络营销论坛推荐《正在爆发的营销革命》
  • HTML5新特性总结
  • Intervention/image 图片处理扩展包的安装和使用
  • JavaScript异步流程控制的前世今生
  • Java精华积累:初学者都应该搞懂的问题
  • js对象的深浅拷贝
  • k8s如何管理Pod
  • MQ框架的比较
  • Perseus-BERT——业内性能极致优化的BERT训练方案
  • ViewService——一种保证客户端与服务端同步的方法
  • 工作中总结前端开发流程--vue项目
  • 回流、重绘及其优化
  • 开源中国专访:Chameleon原理首发,其它跨多端统一框架都是假的?
  • 嵌入式文件系统
  • 如何进阶一名有竞争力的程序员?
  • 手写一个CommonJS打包工具(一)
  • 正则表达式小结
  • TPG领衔财团投资轻奢珠宝品牌APM Monaco
  • 东超科技获得千万级Pre-A轮融资,投资方为中科创星 ...
  • #Js篇:单线程模式同步任务异步任务任务队列事件循环setTimeout() setInterval()
  • #vue3 实现前端下载excel文件模板功能
  • #我与Java虚拟机的故事#连载05:Java虚拟机的修炼之道
  • $ is not function   和JQUERY 命名 冲突的解说 Jquer问题 (
  • $refs 、$nextTic、动态组件、name的使用
  • (02)Cartographer源码无死角解析-(03) 新数据运行与地图保存、加载地图启动仅定位模式
  • (C++17) std算法之执行策略 execution
  • (C语言)fgets与fputs函数详解
  • (c语言版)滑动窗口 给定一个字符串,只包含字母和数字,按要求找出字符串中的最长(连续)子串的长度
  • (第一天)包装对象、作用域、创建对象
  • (附源码)ssm基于微信小程序的疫苗管理系统 毕业设计 092354
  • (机器学习-深度学习快速入门)第三章机器学习-第二节:机器学习模型之线性回归
  • (六)什么是Vite——热更新时vite、webpack做了什么
  • (十八)三元表达式和列表解析
  • (十一)JAVA springboot ssm b2b2c多用户商城系统源码:服务网关Zuul高级篇
  • (一) storm的集群安装与配置
  • (一)使用IDEA创建Maven项目和Maven使用入门(配图详解)
  • (转)visual stdio 书签功能介绍
  • (转)创业家杂志:UCWEB天使第一步
  • * 论文笔记 【Wide Deep Learning for Recommender Systems】
  • .Net CoreRabbitMQ消息存储可靠机制