阿里非典型程序员一枚 ,记录平平无奇程序员在大厂的打怪升级之路。 一起学习Java、大数据、数据结构算法(公众号同名)
题目
在多个线程中循环打印特定字符并保持顺序的三种方法
在Java多线程编程中,有时候我们需要多个线程按照特定的顺序执行某些任务,比如循环打印特定的字符。这通常要求线程间进行协调,以确保任务按照预期的顺序执行。下面我们将介绍三种实现这一功能的方法。
在多个线程中循环打印特定字符并保持顺序的三种方法
在Java多线程编程中,有时候我们需要多个线程按照特定的顺序执行某些任务,比如循环打印特定的字符。这通常要求线程间进行协调,以确保任务按照预期的顺序执行。下面我们将介绍三种实现这一功能的方法。
方法一:synchronized(配合volatile关键字)
volatile关键字可以确保多线程环境中变量的可见性,但它并不保证原子性。因此,在使用volatile时,我们需要结合其他同步机制来实现顺序控制。
public class VolatilePrinting { private volatile int currentThreadIndex = 0; private final int numThreads = 3; private final char[] chars = {'A', 'B', 'C'}; public static void main(String[] args) { VolatilePrinting volatilePrinting = new VolatilePrinting(); Thread[] threads = new Thread[volatilePrinting.numThreads]; for (int i = 0; i < volatilePrinting.numThreads; i++) { final int threadIndex = i; threads[i] = new Thread(() -> { while (true) { synchronized (volatilePrinting) { while (volatilePrinting.currentThreadIndex % volatilePrinting.numThreads != threadIndex) { try { volatilePrinting.wait(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } } System.out.print(volatilePrinting.chars[threadIndex]); volatilePrinting.currentThreadIndex++; if (volatilePrinting.currentThreadIndex >= 100) { // 假设打印100个字符后结束 break; } volatilePrinting.notifyAll(); } } }); } for (Thread thread : threads) { thread.start(); } } }
在这个例子中,我们使用了一个volatile变量currentThreadIndex来跟踪当前应该打印哪个字符的线程。每个线程在打印字符之前会检查这个变量,如果不是它的索引,则等待;如果是它的索引,则打印字符并增加currentThreadIndex的值,然后唤醒所有等待的线程。这样,线程们就会按照顺序打印字符。
方法二:使用Lock和Condition
Lock和Condition提供了更灵活的同步机制,可以实现更复杂的线程间协作。
import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class LockPrinting { private final Lock lock = new ReentrantLock(); private final Condition[] conditions = new Condition[3]; private int currentThreadIndex = 0; private final char[] chars = {'A', 'B', 'C'}; public LockPrinting() { for (int i = 0; i < conditions.length; i++) { conditions[i] = lock.newCondition(); } } public static void main(String[] args) { LockPrinting lockPrinting = new LockPrinting(); Thread[] threads = new Thread[3]; for (int i = 0; i < 3; i++) { final int threadIndex = i; threads[i] = new Thread(() -> { while (true) { lockPrinting.lock.lock(); try { while (lockPrinting.currentThreadIndex % 3 != threadIndex) { conditions[threadIndex].await(); } System.out.print(lockPrinting.chars[threadIndex]); lockPrinting.currentThreadIndex++; if (lockPrinting.currentThreadIndex >= 100) { // 假设打印100个字符后结束 break; } conditions[(threadIndex + 1) % 3].signal(); } catch (InterruptedException e) { Thread.currentThread().interrupt(); return; } finally { lockPrinting.lock.unlock(); } } }); } for (Thread thread : threads) { thread.start(); } } }
在这个例子中,我们为每个线程创建了一个Condition对象,用于等待和通知。当线程需要打印字符时,它会检查currentThreadIndex的值,如果不是它的索引,则调用await()方法等待;如果是它的索引,则打印字符
方法三:使用Semaphore
import java.util.concurrent.Semaphore; class PrintThread implements Runnable { private Semaphore currentSemaphore; private Semaphore nextSemaphore; private String letter; private int count; public PrintThread(Semaphore currentSemaphore, Semaphore nextSemaphore, String letter, int count) { this.currentSemaphore = currentSemaphore; this.nextSemaphore = nextSemaphore; this.letter = letter; this.count = count; } @Override public void run() { try { for (int i = 0; i < count; i++) { currentSemaphore.acquire(); System.out.print(letter); nextSemaphore.release(); } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Main { public static void main(String[] args) { Semaphore semaphoreA = new Semaphore(1); Semaphore semaphoreB = new Semaphore(0); Semaphore semaphoreC = new Semaphore(0); int count = 100; Thread threadA = new Thread(new PrintThread(semaphoreA, semaphoreB, "A", count)); Thread threadB = new Thread(new PrintThread(semaphoreB, semaphoreC, "B", count)); Thread threadC = new Thread(new PrintThread(semaphoreC, semaphoreA, "C", count)); threadA.start(); threadB.start(); threadC.start(); } }
说明:
使用Semaphore控制线程的顺序和并发输出。通过创建三个Semaphore对象,每个Semaphore对象对应一个线程。通过acquire()和release()方法来控制线程的获取和释放。初始时,只有线程A可以获取资源;线程A获取后释放并唤醒线程B,这样线程B可以获取资源并释放唤醒线程C;线程C以此类推。通过这种方式,实现了循环和顺序输出字符ABC。
通过以上三种方式,我们可以实现在多线程并发执行的环境下循环且顺序打印"ABC"100次的目标。每种方法都有其独特的实现方式,可以根据具体需求和场景选择适合的方法来实现线程的控制和顺序打印。
总结
方法 | 特点 |
使用volatile关键字 | 适用于简单的顺序控制,代码简单,但不适用于复杂的线程同步和互斥情况。 |
使用Lock和Condition | 提供了更丰富的线程同步和互斥机制,并可以精确控制线程的等待和唤醒,适用于复杂的线程交互情况。 |
使用Semaphore | 通过信号量来控制线程的获取和释放,并可以灵活地控制线程的顺序,适用于多种线程同步场景。 |