面试记录之synchronized的惨败经历

面试记录之synchronized的惨败经历,第1张

面试记录之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

欢迎分享,转载请注明来源:内存溢出

原文地址: https://outofmemory.cn/zaji/5707339.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-12-17
下一篇 2022-12-17

发表评论

登录后才能评论

评论列表(0条)

保存