Java 多线程交替打印

LARRY1024 / 2024-01-25 / 原文

目录
  • 题目
    • 方案一:synchronized
    • 方法二:ReentrantLock
    • 方法三:ReentrantLock + Condition(非公平锁)
    • 方法四:ReentrantLock + Condition(公平锁)
    • 方法五:Semaphore

题目

使用三个线程 T1、T2、T3,如何让他们按顺序交替打印 10 次 A B C。

方案一:synchronized

public class SynchronizedLockPrinter {
    static class Printer {
        private final Object lock = new Object();
        private int count = 0;
        public void print(int n, int target, char content) {
            for (int i = 0; i < n; ) {
                synchronized (lock) {
                    while (count % 3 != target) {
                        try {
                            lock.wait();
                        } catch (Exception e) {
                            System.out.println(e);
                        }
                    }
                    System.out.print(content);
                    count++;
                    i++;
                    lock.notifyAll();
                }
            }
        }

        public void print() {
            new Thread(()-> {print(10, 0, 'A');}).start();
            new Thread(()-> {print(10, 1, 'B');}).start();
            new Thread(()-> {print(10, 2, 'C');}).start();
        }
    }

    public static void main(String [] args) {
        new Printer().print();
    }
}

注意,这里需要通过 volatile 关键自

方法二:ReentrantLock

public class SignalLockPrinter {
    static class Printer {
        private final Lock lock = new ReentrantLock();
        private volatile int count = 0;

        public void print(int n, int target, char content) {
            for (int i = 0; i < n; ) {
                lock.lock();
                try {
                    while (count % 3 == target) {
                        System.out.print(content);
                        count++;
                        i++;
                    }
                } catch (Exception e) {
                    System.out.println(e);
                } finally {
                    lock.unlock();
                }
            }
        }

        public void print() {
            new Thread(()-> {print(10, 0, 'A');}).start();
            new Thread(()-> {print(10, 1, 'B');}).start();
            new Thread(()-> {print(10, 2, 'C');}).start();
        }
    }

    public static void main(String [] args) {
        new Printer().print();
    }
}

方法三:ReentrantLock + Condition(非公平锁)

public class UnfairLockConditionPrinter {
    static class ConditionPrinter {
        private final Lock lock = new ReentrantLock(); // 非公平锁
        private volatile int count = 0;
        private final int threadNumber = 10;
        private final Condition condition1 = lock.newCondition();
        private final Condition condition2 = lock.newCondition();
        private final Condition condition3 = lock.newCondition();

        public void print(int target, String content, Condition current, Condition next) {
            for (int i = 0; i < threadNumber; i++) {
                lock.lock();
                try {
                    // 执行临界区代码前判断:防止锁被不满足条件的线程抢占
                    while (count % 3 != target) {
                        current.await(); // 条件等待并释放锁
                    }
                    System.out.print(content);
                    count++; // 注意:这不是一个原子操作
                    next.signal(); // 唤醒一个等待该条件的线程
                } catch (Exception e) {
                    System.out.println(e);
                } finally {
                    lock.unlock();
                }
            }
        }

        public void print() {
            new Thread(() -> {print(0, "A", condition1, condition2);}).start();
            new Thread(() -> {print(1, "B", condition2, condition3);}).start();
            new Thread(() -> {print(2, "C", condition3, condition1);}).start();
        }
    }

    public static void main(String[] args) {
        new ConditionPrinter().print();

    }
}

注意,

  • unlock() 并不会阻塞当前线程,所以,当 A 线程在释放锁后,线程状态并没有改变,所以 A 线程还会去尝试获取一次锁,如果获取锁失败,就会进入阻塞状态;如果获取成功,就会进入条件等待状态。

  • count++ 不是一个原子操作,但是由于同时只有一个线程在执行,所以结果是正确的。

    实际使用的时候,可以替换为 LongAdderAtomicInteger 等原子类。

优化:改成公平锁,可以减少锁的竞争程度。

方法四:ReentrantLock + Condition(公平锁)

public class FairLockConditionPrinter {
    static class ConditionPrinterEnhance {
        private final Lock lock = new ReentrantLock(true);
        private final Condition condition = lock.newCondition();
        private volatile int count = 0;
        private final int threadNumber = 10;
        public void print(int target, char content) {
            lock.lock();
            try {
                for (int i = 0; i < threadNumber; i++) {
                    while (count % 3 != target) {
                        condition.await();
                    }
                    System.out.println(Thread.currentThread().getName() + ": " + content + " " + count);
                    count++;
                    condition.signal();
                }
            } catch (Exception e) {
                System.out.println(e);
            } finally {
                lock.unlock();
            }
        }

        public void print() {
            char content = 'A';
            for (int i = 0; i < 3; i++) {
                final int k = i;
                new Thread(() -> print( k, (char) (content + k))).start();
            }
        }
    }

    public static void main(String[] args) {
        new ConditionPrinterEnhance().print();
    }
}

方法五:Semaphore

public class SemaphorePrinter {

    static class Printer {
        private final int n = 10;
        private final Semaphore s1 = new Semaphore(1);
        private final Semaphore s2 = new Semaphore(0);
        private final Semaphore s3 = new Semaphore(0);
        private void print(char content, Semaphore current, Semaphore next) {
            for (int i = 0; i < n; i++) {
                try {
                    current.acquire();
                    System.out.print(content);
                    next.release();
                } catch (Exception e) {
                    System.out.println(e);
                }
            }
        }

        public void print() {
            new Thread(() -> {print('A', s1, s2);}).start();
            new Thread(() -> {print('B', s2, s3);}).start();
            new Thread(() -> {print('C', s3, s1);}).start();
        }
    }

    public static void main(String [] args) {
        new Printer().print();
    }
}