多线程面试题:循环打印ABC

那天面试,面试官让我写这么一道题,有三个线程ABC,让我打印10ABC。这题看起相当的简单,以至于我没写出来哈哈哈。而且那个狗屎在线编辑器想好好写代码是不可能的,因为我已经习惯了一套行云流水的快捷键,到在线编译器框框里面我感觉我就是原始人,无从下手。
好了,吐槽就这么多,这个题目还贴心的放了模板。

public class TestThread {
    public static void main(String[] args) {
        MABC mabc = new MABC();
        Thread thread1 = new Thread(new ThreadAbc(mabc, 'A'));
        Thread thread2 = new Thread(new ThreadAbc(mabc, 'B'));
        Thread thread3 = new Thread(new ThreadAbc(mabc, 'C'));
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class MABC {
}

class ThreadAbc implements Runnable {     
}

简单分析一下这个题的目,首先,是看代码规范吧,这个没得说的,其次就是考验多线程的应用,再就是这个ABC怎么保证顺序,而且多个线程执行的是同一块代码。
话不多说,上代码吧

public class TestThread {
    public static void main(String[] args) {
        MABC mabc = new MABC();
        Thread thread1 = new Thread(new ThreadAbc(mabc, 'A'));
        Thread thread2 = new Thread(new ThreadAbc(mabc, 'B'));
        Thread thread3 = new Thread(new ThreadAbc(mabc, 'C'));
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class MABC {
    public int times = 10;
    public int state = 1;
}

class ThreadAbc implements Runnable {
    private MABC mabc;
    private char c;

    public ThreadAbc(MABC mabc, char c) {
        this.mabc = mabc;
        this.c = c;
    }

    @Override
    public void run() {
        out:
        while (true) {
            synchronized (mabc) {
                /*循环跳出条件*/
                if (mabc.times == 0) {
                    break out;
                }
                    switch (c) {
                        case 'A': {
                            if (mabc.state == 1) {
                                System.out.print(c);
                                mabc.state = 2;
                            } 
                            break;
                        }
                        case 'B': {
                            if (mabc.state == 2) {
                                System.out.print(c);
                                mabc.state = 3;
                            } 
                            break;
                        }
                        case 'C': {
                            if (mabc.state == 3) {
                                System.out.print(c);
                                mabc.state = 1;
                                mabc.times--;
                            }
                            break;
                        }
                        default:
                    }
            }
        }
    }
}

这样功能就实现了,注意了,这里Synchronized代码块要是放到while循环外面会怎样?对的,会一直锁住这个线程哈。里面的赋值语句也可以这么写:

 mabc.state = mabc.state%3+1;

面试官:这么写有什么问题没?我:功能都实现了能有啥问题?没问题。面试官:好的我们聊点别的。

想一想是有问题的哈,问题在于锁争抢,拿到锁了不等于万事大吉,有没有轮到你打印是另一回事呢,所以可以做些优化,如果当前线程不具备修改状态的资格,就wait()释放锁把机会赶紧让给别人,避免白白的争抢锁,都没轮到你凑啥热闹啊你。对run()方法做如下优化:

    public void run() {
        out:
        while (true) {
            synchronized (mabc) {
                /*循环跳出条件*/
                if (mabc.times == 0) {
                    mabc.notify();
                    break out;
                }
                try {
                    switch (c) {
                        case 'A': {
                            if (mabc.state == 1) {
                                System.out.print(c);
//                                mabc.state = 2;
                                mabc.state = mabc.state%3+1;
                            }
                            mabc.wait();
                            break;
                        }
                        case 'B': {
                            if (mabc.state == 2) {
                                System.out.print(c);
//                                mabc.state = 3;
                                mabc.state = mabc.state%3+1;

                            }
                            mabc.wait();
                            break;
                        }
                        case 'C': {
                            if (mabc.state == 3) {
                                System.out.print(c);
//                                mabc.state = 1;
                                mabc.state = mabc.state%3+1;
                                mabc.times--;
                            }
                            /*这里为什么写法和上面不一样?*/
                            mabc.notify();
                            break;
                        }
                        default:
                    }
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }
    }

这里肯定有些疑问的,比如循环跳出条件为什么要notify()?为什么case 'C'wait()notify()

首先说循环跳出条件为什么要notify(),因为我们线程公用了这段代码,里面有wait(),那么就意味着某个线程wait()之后其他线程修改状态让while可以跳出,但是这个wait()的线程却没办法知道了,所以需要其他人来唤醒。

为什么case 'C'wait()notify(),因为咱们得留一个线程来唤醒别人,另外notify()不像Reentrantlock这样可以多条件绑定,他不能唤醒指定的线程,也就是说唤醒的线程可能还是不满足条件的,这也是为啥咱们在mabc类里面要定义一个状态字段了。

既然已经已经提到了Reentrantlock的多条件绑定,那么一定有方法解决锁争抢问题。解决的思路是这样,我们给每个线程加上一个条件ConditionA、B、CA执行完了就赶紧去叫BB完了就取叫CC完了叫A,以此循环往复。

public class TestThread {
    public static ReentrantLock lock = new ReentrantLock();
    public static Condition conditionA = lock.newCondition();
    public static Condition conditionB = lock.newCondition();
    public static Condition conditionC = lock.newCondition();

    public static void main(String[] args) {
        MABC mabc = new MABC();
        Thread thread1 = new Thread(new ThreadAbc(mabc, 'A'));
        Thread thread2 = new Thread(new ThreadAbc(mabc, 'B'));
        Thread thread3 = new Thread(new ThreadAbc(mabc, 'C'));
        thread1.start();
        thread2.start();
        thread3.start();
    }
}

class MABC {
    public int times = 10;
    public int state = 1;
}

class ThreadAbc implements Runnable {
    private MABC mabc;
    private char c;

    public ThreadAbc(MABC mabc, char c) {
        this.mabc = mabc;
        this.c = c;
    }
    public void run() {
        out:
        while (true) {
            TestThread.lock.lock();
            System.out.println("\nthread"+c+":"+mabc.state+":"+mabc.times);
            /*循环跳出条件*/
            if (mabc.times == 0) {
                TestThread.conditionA.signal();
                TestThread.conditionB.signal();
                TestThread.conditionC.signal();
                TestThread.lock.unlock();
                break out;
            }
            try {
                switch (c) {
                    case 'A': {
                            System.out.print(c);
                            TestThread.conditionB.signal();
                            TestThread.conditionA.await();
                        break;
                    }
                    case 'B': {
                            System.out.print(c);
                            TestThread.conditionC.signal();
                            TestThread.conditionB.await();
                        break;
                    }
                    case 'C': {
                            System.out.print(c);
                            mabc.times--;
                            TestThread.conditionA.signal();
                            TestThread.conditionC.await();
                        break;
                    }
                    default:

                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                TestThread.lock.unlock();
            }
        }
    }
}

这就相当的简单了,A说完事了,老哥B你开始吧,该我了叫我,我先休息了。B开始了同时也给C说,C也这样跟A说,就这默契的配合把活儿干完了。

最后,不知道细心的大家有没有发现这段代码:

	out:
        while (true) {
            synchronized (mabc) {
                /*循环跳出条件*/
                if (mabc.times == 0) {
                    break out;
                }
                ....

其实就是Javagoto哈,叫做标签Label,如果你在多层的if里面想要一步跳出去,也可以这样break : lable来结束的。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值