Java面试题:多个线程交替执行

Java面试题:多个线程交替执行

前言

最近在一些技术群里看到有很多小伙伴面试的时候碰到这个多线程的笔试题,实现两个线程交替打印,或者是实现多个线程的交替打印这种类似的题目。

本文提供三种解题思路和实现

  • 利用 Condition 类实现
  • 利用公平锁实现
  • 超过 2 个线程,利用队列来保证线程执行的顺序

项目环境

1.Condition 版本

  • 缺陷是超过 2 个线程之后无法保证执行的顺序
public class ThreadAlternateConditionDemo {

    public static void main(String[] args) {
        Lock lock = new ReentrantLock();// 默认非公平
        Condition condition = lock.newCondition();
        ConditionThread conditionThread = new ConditionThread(lock, condition, 100);
        new Thread(conditionThread, "Thread-A").start();
        new Thread(conditionThread, "Thread-B").start();
        new Thread(conditionThread, "Thread-C").start();
        new Thread(conditionThread, "Thread-D").start();
    }

    static class ConditionThread implements Runnable {

        Lock lock;

        Condition condition;

        int count;

        public ConditionThread(Lock lock, Condition condition, int count) {
            this.lock = lock;
            this.condition = condition;
            this.count = count;
        }

        @Override
        public void run() {
            for (int i = 0; i < count; i++) {
                try {
                    lock.lock();
                    condition.signal();
                    System.out.println(Thread.currentThread().getName() + "-> 执行");
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

}

执行结果:
在这里插入图片描述

2.公平锁

  • 这里使用 ReentrantLock 中的公平锁
  • 缺陷是超过 2 个线程之后无法保证执行的顺序
public class ThreadAlternateFairSyncLockDemo {
    public static void main(String[] args) {
        Lock lock = new ReentrantLock(true);// 公平
        Task task = new Task(lock, 10);
        new Thread(task, "Thread-A").start();
        new Thread(task, "Thread-B").start();
        new Thread(task, "Thread-C").start();
    }

    static class Task implements Runnable {

        Lock lock;

        int count;

        public Task(Lock lock, int count) {
            this.lock = lock;
            this.count = count;
        }

        @Override
        public void run() {
            for (int i = 0; i < count; i++) {
                try {
                    lock.lock();
                    System.out.println(Thread.currentThread().getName() + "-> 执行" + i);
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

执行结果:
在这里插入图片描述

3.如何解决线程打印顺序的问题

先来讨论一下在第一个 Conidtion 的例子中这个乱序问题如何产生的?

相关代码如下:

                try {
                    lock.lock();
                    condition.signal();
                    System.out.println(Thread.currentThread().getName() + "-> 执行");
                    condition.await();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }

假设线程A执行完打印,之后condition.await() 挂起线程A,最后 lock.unlock() 释放锁,此时有线程B、线程C、线程D三个线程来抢占锁,无法保证线程B一定抢占到锁,所以会产生乱序打印的效果,但是如果只有 A、B 两个线程,那么就不会有这个问题。

解题思路如下:

  • 预先设置一个队列,将 A、B、C按顺序存放在队列中。

  • 每次执行,先从队列中取一个值,判断这个值是否和当前线程的标识相等,如果相等就执行打印,如果不相等释放锁,让其他线程继续抢占锁。

实现代码如下:

  • 队列采用的 LinkedBlockingQueue 无界队列,也可以用其他的队列(不一定是阻塞队列)
  • 循环退出条件 queue.peek() != null 队列没有元素
  • queue.peek() 返回队列的头元素但并不删除
  • 线程执行打印之后,通过 queue.poll() 删除已经执行的队列元素
  • atomicInteger 只是为了打印循环的次数
public class ThreadAlternateConditionOrderDemo {

    private static LinkedBlockingQueue<String> queue = new LinkedBlockingQueue();

    static {
        try {
            for (int i = 0; i < 100; i++) {
                queue.put("A");
                queue.put("B");
                queue.put("C");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }

    private static AtomicInteger atomicInteger = new AtomicInteger(0);

    public static void main(String[] args) throws InterruptedException {

        Lock lock = new ReentrantLock();// 默认非公平
        Condition condition = lock.newCondition();
        ConditionThread conditionThreadA = new ConditionThread(lock, condition, "A");
        ConditionThread conditionThreadB = new ConditionThread(lock, condition, "B");
        ConditionThread conditionThreadC = new ConditionThread(lock, condition, "C");
        new Thread(conditionThreadA, "Thread-A").start();
        new Thread(conditionThreadB, "Thread-B").start();
        new Thread(conditionThreadC, "Thread-C").start();

    }

    static class ConditionThread implements Runnable {

        Lock lock;

        Condition condition;

        String threadFlag;

        public ConditionThread(Lock lock, Condition condition, String threadFlag) {
            this.lock = lock;
            this.condition = condition;
            this.threadFlag = threadFlag;
        }

        @Override
        public void run() {
            while (queue.peek() != null) {
                try {
                    lock.lock();
                    condition.signal();
                    String element = queue.peek();// 拿到阻塞队列头部的元素的 flag
                    if (null != element && element.equals(threadFlag)) {// 如果和当前线程的 flag 相同,则执行线程
                        queue.poll();// 删除这个头部的元素
                        System.out.println(Thread.currentThread().getName() + "-> 执行");
                        atomicInteger.incrementAndGet();
                        System.out.println(atomicInteger.get());
                        condition.await();
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

}

执行结果:
在这里插入图片描述
可以看到执行到 300 次,顺序依然是 A -> B-> C。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章