java中使用三个线程依次打印1,2,3

本文探讨了如何使用Lock API、JMM机制以及AtomicInteger保证线程安全,使三个线程A、B、C按顺序打印1、2、3。作者反思了面试中的难题,重点在于理解原子性和可见性在并发编程中的应用。

面试这道题没写出来,无了,真想一头撞死,太菜了。。。不太适合这个行业

目录

第一种:使用Lock

第二种:JMM解决

 第三种:使用AtomicInteger,自带并发安全

总结


有三个线程A,B,C, 三个线程轮流打印出1,2,3;其中线程A只能打印1, 线程B只能打印2, 线程C只能打印3

第一种:使用Lock

思路:使用java中的Lock, 可以通过Lock获取Condition(监听器),这个Condition可以监听注册在上面的线程对象,通过await()和notifyAll()操作线程对象的状态,可以完成“选择性通知”

public class PrintThread {

    private Lock lock = new ReentrantLock();
    private int flag = 1;  // 控制执行哪个线程

    private Condition condition1 = lock.newCondition();
    private Condition condition2 = lock.newCondition();
    private Condition condition3 = lock.newCondition();


    public void printA() {

        lock.lock();

        try {
            while (flag != 1) {
                condition1.await();
            }
            System.out.println(Thread.currentThread().getName() + " : 1");
            flag = 2;
            condition1.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }

    }

    public void printB() {
        lock.lock();

        try {
            while (flag != 2) {
                condition2.await();
            }
            System.out.println(Thread.currentThread().getName() + " : 2");
            flag = 3;
            condition2.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }


    public void printC() {
        lock.lock();

        try {
            while (flag != 3) {
                condition3.await();
            }
            System.out.println(Thread.currentThread().getName() + " : 3");
            flag = 1;
            condition3.signalAll();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
}

class Main {
    public static void main(String[] args) {
        PrintThread printThread = new PrintThread();

       for (int i = 0; i < 10; i++) {
           new Thread(() -> printThread.printA(), "A").start();

           new Thread(() -> printThread.printB(), "B").start();

           new Thread(() -> printThread.printC(), "C").start();
       }
    }
}

第二种:JMM解决

由于线程的循环打印,很大程度遇到的是并发问题,而对于JMM的应对措施主要针对 原子性,可见性,指令重排

想到上面这三点,我第一想法是处理懒汉式单例模式的双重检测,所以写了下面这种方式:

public class PrintThreadByFlag {

    private volatile int flag = 1;


    public void printA() {

        while (true) {
            if (flag == 1) {
                synchronized (this) {
                    if (flag == 1) {
                        System.out.println(Thread.currentThread().getName() + ": 1");
                        flag = 2;
                    }
                }
            }
        }

    }

    public void printB() {
        while (true) {
            if (flag == 2) {
                synchronized (this) {
                    if (flag == 2) {
                        System.out.println(Thread.currentThread().getName() + ": 2");
                        flag = 3;
                    }
                }
            }
        }
    }

    public void printC() {
        while (true) {
            if (flag == 3) {
                synchronized (this) {
                    if (flag == 3) {
                        System.out.println(Thread.currentThread().getName() + ": 3");
                        System.out.println("-----------------------------------");
                        flag = 1;
                    }
                }
            }
        }
    }
}

class MainByFlag {
    public static void main(String[] args) {
        PrintThreadByFlag printThreadByFlag = new PrintThreadByFlag();

        new Thread(() -> printThreadByFlag.printA(), "A").start();

        new Thread(() -> printThreadByFlag.printB(), "B").start();

        new Thread(() -> printThreadByFlag.printC(), "C").start();
    }
}

从结果来看似乎没有问题:

 第三种:使用AtomicInteger,自带并发安全

使用atomic中的AtomicInteger,本身是线程安全的,并且AtomicInteger中的value也是volatile可见的

/**
 * 使用AtomicInteger,本身保证原子性
 */
public class AtomicThread extends Thread{

    private AtomicInteger currentCount;
    private String name;


    private static final Integer maxCount = 100;

    private static String[] chars = {"1", "2", "3"};

    public AtomicThread(String name, AtomicInteger currentCount) {
        this.name = name;
        this.currentCount = currentCount;
    }

    @Override
    public void run() {

        while (currentCount.get() < maxCount) {
            if (this.name.equals(chars[currentCount.get() % 3])) {
                System.out.println(this.name);
                currentCount.getAndIncrement();
                if (this.name.equals("3")) {
                    System.out.println("-------------------");
                }
            }

        }
    }
}

class MainByAtomicThread {
    public static void main(String[] args) {

        AtomicInteger target = new AtomicInteger(0);
        new AtomicThread("1", target).start();
        new AtomicThread("2", target).start();
        new AtomicThread("3", target).start();
    }
}

 总体来看,123的输出顺序是没有问题,但是这个分割线有点毛病。。并且输出的总量有时候大于maxCount。。。

总结

感觉这道题主要从JMM方面考虑,保证原子性,可见性,指令重排;

想想第二种方法,之前也学过单例模式的线程安全处理,但自己就是想不到;想到了ReetLock但是API却忘了,真是。。。

<think>在Java中,要让三个线程依次执行(即线程1执行完后线程2再执行,线程2执行完后线程3再执行),可以使用线程的join()方法或者使用锁机制(如ReentrantLock)和条件变量(Condition)来实现。下面分别展示两种方法: ### 方法1:使用join()方法 join()方法使得当前线程等待调用join()的线程执行完毕后再继续执行。 ```java public class SequentialThreadsWithJoin { public static void main(String[] args) { Thread t1 = new Thread(() -> { System.out.println("线程1执行"); }); Thread t2 = new Thread(() -> { try { t1.join(); // 等待t1执行完毕 System.out.println("线程2执行"); } catch (InterruptedException e) { e.printStackTrace(); } }); Thread t3 = new Thread(() -> { try { t2.join(); // 等待t2执行完毕 System.out.println("线程3执行"); } catch (InterruptedException e) { e.printStackTrace(); } }); // 启动线程,注意启动顺序并不影响执行顺序 t1.start(); t2.start(); t3.start(); } } ``` ### 方法2:使用ReentrantLock和Condition 通过锁和条件变量,我们可以精确控制线程的执行顺序。 ```java import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.ReentrantLock; public class SequentialThreadsWithLock { private static ReentrantLock lock = new ReentrantLock(); private static Condition condition1 = lock.newCondition(); private static Condition condition2 = lock.newCondition(); private static volatile int state = 1; // 状态标识 public static void main(String[] args) { Thread t1 = new Thread(() -> { lock.lock(); try { // 确保是第一个执行 while (state != 1) { condition1.await(); } System.out.println("线程1执行"); state = 2; // 更新状态 condition2.signal(); // 唤醒等待condition2的线程(即t2) } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }); Thread t2 = new Thread(() -> { lock.lock(); try { while (state != 2) { condition2.await(); } System.out.println("线程2执行"); state = 3; // 更新状态 condition2.signal(); // 这里唤醒t3,因为t3也在等待condition2 } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }); Thread t3 = new Thread(() -> { lock.lock(); try { while (state != 3) { condition2.await(); // 注意这里t3也在等待condition2 } System.out.println("线程3执行"); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } }); t1.start(); t2.start(); t3.start(); } } ``` ### 方法3:使用单个线程池(ExecutorService) 如果希望任务依次执行,可以使用线程线程池,这样提交的任务会按照提交顺序依次执行。 ```java import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; public class SequentialThreadsWithSingleThreadPool { public static void main(String[] args) { ExecutorService executor = Executors.newSingleThreadExecutor(); executor.submit(() -> System.out.println("线程1执行")); executor.submit(() -> System.out.println("线程2执行")); executor.submit(() -> System.out.println("线程3执行")); executor.shutdown(); } } ``` ### 总结 - **join方法**:简单直观,但需要线程间有明确的依赖关系(即后一个线程需要持有前一个线程的引用)。 - **锁和条件变量**:更灵活,可以控制复杂的执行顺序,但代码稍复杂。 - **单线程线程池**:最简单,但严格来说并不是多线程并发,而是顺序执行任务(因为只有一个线程)。 > 注意:多线程顺序执行通常违背了并发设计的初衷,但在某些特定场景下(如初始化顺序、任务依赖)是必要的。 §§相关问题§§ 1. Java中除了join()和锁机制,还有哪些方法可以控制线程执行顺序? 2. 使用join()方法时,如果线程在执行过程中抛出异常会怎样? 3. 在ReentrantLock和Condition的方案中,为什么需要while循环检查状态而不是if? 4. 单线程线程池与多线程顺序执行有何区别?各自适用什么场景? 5. 如何实现三个线程循环依次执行(例如t1->t2->t3->t1->...)?</think>### Java中实现三个线程依次执行的方法 在Java中实现线程顺序执行(线程1→线程2→线程3)的核心是**线程同步机制**,以下是三种常用方法: --- #### 方法1:使用 `join()` 方法(简单直接) ```java public class JoinExample { public static void main(String[] args) { Thread t1 = new Thread(() -> System.out.println("线程1执行完成")); Thread t2 = new Thread(() -> System.out.println("线程2执行完成")); Thread t3 = new Thread(() -> System.out.println("线程3执行完成")); try { t1.start(); t1.join(); // 主线程等待t1结束 t2.start(); t2.join(); // 等待t2结束 t3.start(); t3.join(); } catch (InterruptedException e) { e.printStackTrace(); } } } ``` --- #### 方法2:使用 `synchronized` + `wait()/notify()`(经典同步) ```java public class WaitNotifyExample { private static int currentThread = 1; public static void main(String[] args) { Object lock = new Object(); new Thread(() -> runTask(lock, 1, 2)).start(); new Thread(() -> runTask(lock, 2, 3)).start(); new Thread(() -> runTask(lock, 3, 1)).start(); } private static void runTask(Object lock, int target, int next) { synchronized (lock) { while (currentThread != target) { try { lock.wait(); // 等待轮到当前线程 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } System.out.println("线程" + target + "执行完成"); currentThread = next; // 设置下一个执行线程 lock.notifyAll(); // 唤醒所有等待线程 } } } ``` --- #### 方法3:使用 `ReentrantLock` + `Condition`(高性能方案) ```java import java.util.concurrent.locks.*; public class ConditionExample { private static int current = 1; private static final ReentrantLock lock = new ReentrantLock(); private static final Condition[] conditions = { lock.newCondition(), lock.newCondition(), lock.newCondition() }; public static void main(String[] args) { new Thread(() -> runTask(1, 2)).start(); new Thread(() -> runTask(2, 3)).start(); new Thread(() -> runTask(3, 1)).start(); } private static void runTask(int id, int next) { lock.lock(); try { while (current != id) { conditions[id - 1].await(); // 等待当前线程的条件 } System.out.println("线程" + id + "执行完成"); current = next; conditions[next - 1].signal(); // 唤醒下一个线程 } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { lock.unlock(); } } } ``` --- ### 关键特性对比 | 方法 | 优点 | 缺点 | |--------------------|--------------------------|--------------------------| | `join()` | 实现简单,无需同步原语 | 阻塞主线程,扩展性差 | | `wait()/notify()` | 经典线程通信模型 | 需处理虚假唤醒问题 | | `ReentrantLock` | 高性能,精确控制唤醒 | 代码复杂度高 | > **最佳实践**: > - 简单场景用 `join()` > - 复杂顺序控制用 `ReentrantLock+Condition` > - 避免使用已过时的 `Thread.suspend()`/`resume()`
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值