支付宝面试----线程同步问题

272 阅读2分钟

题目

评测题目: 开启 3 个线程 1,2,3,这三个线程的输出分别为 1、2、3,每个线程将自己的 输出在屏幕上打印 10 遍,要求输出的结果必须按顺序显示。如:123123...

分析

多个线程间顺序执行,如何实现多线程同步,主要就是借助各种锁来控制。

答案

定义一个基类:

public static class BTH extends Thread {
        static long start;
        static long end;
}

解法1 对象锁+运行条件标记

static class MyThread extends BTH {
        static volatile int Flag = 1;
        static Object lock = new Object();
        private final int name;
        private final int nextFlag;
        MyThread(int name, int nextFlag) {
            this.name = name;
            this.nextFlag = nextFlag;
        }

        @Override
        public void run() {
            if (name == 1) {
                start = System.currentTimeMillis();
            }
            for (int i=0; i< 1000; i++) {
                for (;;) {
                    synchronized (lock) {
                        if (Flag == name) {
                            System.out.print(name);
                            Flag = nextFlag;
                            lock.notifyAll(); //唤醒等待的线程
                            break;
                        } else {
                            try {
                                lock.wait(); //让线程等待
                            } catch (InterruptedException e) {
                                e.printStackTrace();
                            }
                        }
                    }
                }
                Thread.yield();
            }
            if (name == 3) {
                end = System.currentTimeMillis();
            }
        }
    }

    @Test
    public void test() throws InterruptedException {
        Thread t1 = new MyThread(1, 2);
        Thread t2 = new MyThread(2, 3);
        Thread t3 = new MyThread(3, 1);
        t1.start();
        t2.start();
        t3.start();

        for(;;) {
            Thread.sleep(5000);
            break;
        }
    }

结果:循环1000次 耗时60ms

这种做法存在一个线程多次抢到锁的case,效率不高.

解法2 借助线程安全的同步计数

static class MyThread1 extends BTH {
        static AtomicInteger sFlag = new AtomicInteger(1);
        final int me;
        final int next;
        MyThread1(int who, int next) {
            me = who;
            this.next = next;
        }

        @Override
        public void run() {
            if (me == 1) {
                start = System.currentTimeMillis();
            }
            for (int i=0; i< 1000; i++) {
                for(;;) {
                    if (sFlag.get() == me) {
                        System.out.print(me);
                        sFlag.compareAndSet(me, next);
                        break;
                    }
                    Thread.yield();
                }
            }
            if (me == 3) {
                end = System.currentTimeMillis();
            }
        }
    }

    @Test
    public void test() throws InterruptedException {
        Thread t1 = new MyThread1(1, 2);
        Thread t2 = new MyThread1(2, 3);
        Thread t3 = new MyThread1(3, 1);
        t1.start();
        t2.start();
        t3.start();

        for(;;) {
            Thread.sleep(5000);
            break;
        }
    }

结果:循环1000次,耗时24ms

    借助线程安全的AntomicInteger来作为线程运行的标记,简单的实现了线程顺序执行。

解法3 使用信号量

static class Thread2 extends BTH {
        private final Semaphore mSemaphore;
        private final Semaphore mNext;
        private final int me;

        public Thread2(Semaphore my, Semaphore next, int me) {
            this.mSemaphore = my;
            this.mNext = next;
            this.me = me;
        }

        @Override
        public void run() {
            if (me == 1) {
                start = System.currentTimeMillis();
            }
            for (int i=0; i < 1000; i++) {
                try {
                    mSemaphore.acquire();
                    System.out.print(me);
                    mNext.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            if (me == 3) {
                end = System.currentTimeMillis();
            }
        }
    }

    @Test
    public void test() throws InterruptedException {
        Thread t1 ;
        Thread t2 ;
        Thread t3 ;
        {
            //case3
            Semaphore s1 = new Semaphore(1);
            Semaphore s2 = new Semaphore(0);
            Semaphore s3 = new Semaphore(0);
            t1 = new Thread2(s1, s2, 1);
            t2 = new Thread2(s2, s3, 2);
            t3 = new Thread2(s3, s1, 3);
        }
        t1.start();
        t2.start();
        t3.start();

        for(;;) {
            Thread.sleep(5000);
            break;
        }

        System.out.println("\n耗时" + (BTH.end - BTH.start));
    }

结果:循环1000次,耗时79ms

解法4 使用可重置锁

static class Thread3 extends BTH {
        static int sFlag = 1;
        private static ReentrantLock lock = new ReentrantLock();
        private final Condition mCondition;
        private final Condition nextCondition;
        private final int me;
        private final int next;

        public Thread3(int me, int next, Condition condition, Condition nextCondition) {
            this.mCondition = condition;
            this.nextCondition = nextCondition;
            this.me = me;
            this.next = next;
        }

        @Override
        public void run() {
            if (me == 1) {
                start = System.currentTimeMillis();
            }
            try {
                lock.lock();
                for (int i=0; i<1000; i++) {
                    while ( sFlag != me) {
                        mCondition.await(); //刮起当前线程
                    }
                    System.out.print(me);
                    sFlag = next;
                    nextCondition.signal(); //唤醒下一个线程
                }
            } catch (InterruptedException e) {

            } finally {
                lock.unlock();
            }
            if (me == 3) {
                end = System.currentTimeMillis();
            }
        }
    }

    @Test
    public void test() throws InterruptedException {
        Thread t1 ;
        Thread t2 ;
        Thread t3 ;
        {
            Condition c1 = Thread3.lock.newCondition();
            Condition c2 = Thread3.lock.newCondition();
            Condition c3 = Thread3.lock.newCondition();
            t1 = new Thread3(1, 2, c1, c2);
            t2 = new Thread3(2, 3, c2, c3);
            t3 = new Thread3(3, 1, c3, c1);
        }

        t1.start();
        t2.start();
        t3.start();

        for(;;) {
            Thread.sleep(5000);
            break;
        }

        System.out.println("\n耗时" + (BTH.end - BTH.start));
    }

结果:循环1000次,耗时36ms