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

  • synchronized 和 wait/notify
  • ReentrantLock和Condition
  • Semaphore
  • Phaser
  • AtomicInteger
  • BlockingQueue

1. 六大解法

1.1 Synchronized 和 wait/notify

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

publicclassPrintA1B2C3_sync_wait_notify {
    privatestaticObjectlock=newObject();
    privatestaticvolatilebooleanprintNumber=false;
    privatestaticintnum=1;

    publicstaticvoidmain(String[] args) {
        ThreadA=newThread(() -> {
            synchronized (lock) {
                for (charc='A'; c <= 'Z'; c++) {
                    while (printNumber) {
                        try {
                            lock.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.print(c);
                    printNumber = true;
                    lock.notify();
                }
            }
        });

        ThreadB=newThread(() -> {
            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

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

publicclassPrintA1B2C3_lock_condition {
    privatestaticReentrantLocklock=newReentrantLock();
    privatestaticConditionletter= lock.newCondition();
    privatestaticConditionnumber= lock.newCondition();
    privatestaticintnum=1;

    publicstaticvoidmain(String[] args) {
        ThreadA=newThread(() -> {
            lock.lock();
            try {
                for (charc='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();
            }
        });

        ThreadB=newThread(() -> {
            lock.lock();
            try {
                for (inti=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

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

publicclassPrintA1B2C3_semaphore {
    privatestaticSemaphoreletter=newSemaphore(1);
    privatestaticSemaphorenumber=newSemaphore(0);

    publicstaticvoidmain(String[] args) {
        ThreadA=newThread(() -> {
            for (charc='A'; c <= 'Z'; c++) {
                try {
                    letter.acquire();
                    System.out.print(c);
                    number.release();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        });

        ThreadB=newThread(() -> {
            for (inti=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 对象,通过 acquire 和 release 方法来保证线程同步,实现了两个线程的顺序控制

1.4 Phaser

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

publicclassPrintA1B2C3_Phaser{
    privatestaticPhaserphaser=newPhaser(1);

    publicstaticvoidmain(String[] args) {
        ThreadA=newThread(() -> {
            for (charc='A'; c <= 'Z'; c++) {
                System.out.print(c);
                phaser.arriveAndAwaitAdvance();
                phaser.register();
            }
        });

        ThreadB=newThread(() -> {
            for (inti=1; i <= 26; i++) {
                phaser.arriveAndAwaitAdvance();
                System.out.print(i);
                phaser.register();
            }
        });

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

复制代码

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

1.5 AtomicInteger

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

publicclassPrintA1B2C3_AtomicIntegerextendsThread {
    privatestaticAtomicIntegerindex=newAtomicInteger(0);
    privatestaticfinalStringstr="A1B2C3D4E5";
    privateint threadId;

    publicPrintThread(int threadId) {
        this.threadId = threadId;
    }

    @Overridepublicvoidrun() {
        while (index.get() < str.length()) {
            intcurrentIdx= index.getAndIncrement();
            if (currentIdx % 2 == threadId) {
                System.out.println(str.charAt(currentIdx));
            }
        }
    }

    publicstaticvoidmain(String[] args) {
        PrintThreadthread1=newPrintThread(0);
        PrintThreadthread2=newPrintThread(1);
        thread1.start();
        thread2.start();
    }
}

复制代码

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

1.6 BlockingQueue

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

publicclassPrintA1B2C3_BlockingQueue {
    publicstaticvoidmain(String[] args) {
        BlockingQueue<String> queue = newLinkedBlockingQueue<>(1);

        newThread(() -> {
            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();

        newThread(() -> {
            while (true) {
                try {
                    Stringstr= 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原子操作类,需要根据实际需求进行选择。同时,也需要注意多线程编程中常见的问题,如线程安全、死锁、内存泄漏等,从而保证代码的质量和稳定性。

作者:喝咖啡的工匠