likes
comments
collection
share

面试题:6种解法-顺序打印A1B2C3

作者站长头像
站长
· 阅读数 12

  金不三,银不四的高频面试题,Java 中顺序打印 A1B2C3 是多线程中的一个经典面试问题,其解决方法可以锻炼程序员的多线程编程能力。本文将从多个角度,介绍 Java 中顺序打印 A1B2C3 的实现方式,总共分为如下6种方式:

  • synchronizedwait/notify
  • ReentrantLockCondition
  • Semaphore
  • Phaser
  • AtomicInteger
  • BlockingQueue

1. 六大解法

1.1 Synchronized 和 wait/notify

  synchronizedwait/notify 是 Java 中最基本的线程同步和顺序控制机制,可以用来实现顺序打印 A1B2C3。具体实现方式如下:

public class PrintA1B2C3_sync_wait_notify {
    private static Object lock = new Object();
    private static volatile boolean printNumber = false;
    private static int num = 1;

    public static void main(String[] args) {
        Thread A = new Thread(() -> {
            synchronized (lock) {
                for (char c = 'A'; c <= 'Z'; c++) {
                    while (printNumber) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.print(c);
                    printNumber = true;
                    lock.notify();
                }
            }
        });

        Thread B = new Thread(() -> {
            synchronized (lock) {
                while (num <= 26) {
                    while (!printNumber) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.print(num);
                    num++;
                    printNumber = false;
                    lock.notify();
                }
            }
        });

        A.start();
        B.start();
    }
}

  以上代码中,线程 A 负责打印字母,线程 B 负责打印数字,通过共享一个锁和一个 volatile 的布尔值,实现了两个线程的顺序控制。

1.2 ReentrantLock 和 Condition

  ReentrantLockConditionJava 中更高级的线程同步和顺序控制机制,可以用来实现顺序打印 A1B2C3。具体实现方式如下:

public class PrintA1B2C3_lock_condition {
    private static ReentrantLock lock = new ReentrantLock();
    private static Condition letter = lock.newCondition();
    private static Condition number = lock.newCondition();
    private static int num = 1;

    public static void main(String[] args) {
        Thread A = new Thread(() -> {
            lock.lock();
            try {
                for (char c = 'A'; c <= 'Z'; c++) {
                    System.out.print(c);
                    number.signal();
                    if (c < 'Z') letter.await();
                }
                number.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        });

        Thread B = new Thread(() -> {
            lock.lock();
            try {
                for (int i = 1; i <= 26; i++) {
                    System.out.print(i);
                    letter.signal();
                    if (i < 26) number.await();
                }
                letter.signal();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
    });

    A.start();
    B.start();
}

  以上代码中,线程 A 和线程 B 分别使用了两个 Condition 对象,通过 lock 来保证线程同步,实现了两个线程的顺序控制。

1.3 Semaphore

  SemaphoreJava 中另一种常用的线程同步机制,可以用来实现顺序打印 A1B2C3。具体实现方式如下:

public class PrintA1B2C3_semaphore {
    private static Semaphore letter = new Semaphore(1);
    private static Semaphore number = new Semaphore(0);

    public static void main(String[] args) {
        Thread A = new Thread(() -> {
            for (char c = 'A'; c <= 'Z'; c++) {
                try {
                    letter.acquire();
                    System.out.print(c);
                    number.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        Thread B = new Thread(() -> {
            for (int i = 1; i <= 26; i++) {
                try {
                    number.acquire();
                    System.out.print(i);
                    letter.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        A.start();
        B.start();
    }
}

  以上代码中,线程 A 和线程 B 分别使用了两个 Semaphore 对象,通过 acquirerelease 方法来保证线程同步,实现了两个线程的顺序控制

1.4 Phaser

  PhaserJava 7 中引入的线程同步机制,可以用来实现顺序打印 A1B2C3。具体实现方式如下:

public class PrintA1B2C3_Phaser{
    private static Phaser phaser = new Phaser(1);

    public static void main(String[] args) {
        Thread A = new Thread(() -> {
            for (char c = 'A'; c <= 'Z'; c++) {
                System.out.print(c);
                phaser.arriveAndAwaitAdvance();
                phaser.register();
            }
        });

        Thread B = new Thread(() -> {
            for (int i = 1; i <= 26; i++) {
                phaser.arriveAndAwaitAdvance();
                System.out.print(i);
                phaser.register();
            }
        });

        A.start();
        B.start();
    }
}

  以上代码中,线程 A 和线程 B 分别使用了一个 Phaser 对象,通过 arriveAndAwaitAdvanceregister 方法来保证线程同步,实现了两个线程的顺序控制

1.5 AtomicInteger

  也可以使用 JavaAtomicInteger类来实现顺序打印 A1B2C3 这个字符串,下面是实现的示例代码:


public class  PrintA1B2C3_AtomicInteger extends Thread {
    private static AtomicInteger index = new AtomicInteger(0);
    private static final String str = "A1B2C3D4E5";
    private int threadId;

    public PrintThread(int threadId) {
        this.threadId = threadId;
    }

    @Override
    public void run() {
        while (index.get() < str.length()) {
            int currentIdx = index.getAndIncrement();
            if (currentIdx % 2 == threadId) {
                System.out.println(str.charAt(currentIdx));
            }
        }
    }

    public static void main(String[] args) {
        PrintThread thread1 = new PrintThread(0);
        PrintThread thread2 = new PrintThread(1);
        thread1.start();
        thread2.start();
    }
}

  代码的主要思路是使用一个静态的 AtomicInteger 类型的变量 index,它会被两个线程共享。每个线程会通过自旋的方式不断地从 index 中获取当前的值,并使用 getAndIncrement() 方法来原子地增加它的值。如果当前的值是奇数,那么线程1就打印字母;如果当前的值是偶数,那么线程2就打印数字。这样就能保证 A1B2C3 这个字符串被顺序地打印出来。

1.6 BlockingQueue

  阻塞队列是一种特殊的队列,当队列为空时,从队列中获取元素的操作会被阻塞;当队列已满时,向队列中添加元素的操作会被阻塞。阻塞队列常用于生产者-消费者模式中,可以有效地协调生产者和消费者之间的速度差异,以下是借助BlockingQueue实现打印A1B2C3的代码实现:

public class PrintA1B2C3_BlockingQueue {
    public static void main(String[] args) {
        BlockingQueue<String> queue = new LinkedBlockingQueue<>(1);

        new Thread(() -> {
            String[] chars = {"A", "B", "C"};
            for (String c : chars) {
                try {
                    queue.put(c + (1 + chars[c.charAt(0) - 'A']));
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();

        new Thread(() -> {
            while (true) {
                try {
                    String str = queue.take();
                    System.out.print(str.charAt(0));
                    System.out.print(str.charAt(1));
                    if (str.length() > 2) {
                        System.out.print(str.charAt(2));
                    }
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                if (queue.isEmpty()) {
                    break;
                }
            }
        }).start();
    }
}

  在该实现中,我们使用了LinkedBlockingQueue作为阻塞队列,创建了一个大小为1的队列,因此生产者线程在往队列中放入元素时会被阻塞,直到消费者线程取出了队列中的元素。消费者线程在取出元素时也会被阻塞,直到生产者线程往队列中放入了元素

2. 总结

  顺序打印A1B2C3问题是一个典型的线程间通信同步问题,对于这个问题,常见的实现方式包括使用阻塞队列信号量Atomc原子操作类,需要根据实际需求进行选择。同时,也需要注意多线程编程中常见的问题,如线程安全、死锁、内存泄漏等,从而保证代码的质量和稳定性。