3个线程循环打印ABC

本文探讨了如何使用线程循环打印ABC,重点在于避免死锁和确保正确顺序。通过使用不同对象锁和单一状态锁实现线程间的协调。同时指出在使用wait和notify时需要注意同步区域,以及避免嵌套锁导致的问题。文章提供了代码示例并分析了可能的性能影响。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

三个线程循环打印ABC多次,要求同时开启多个线程

核心思路:三个线程分别打印A,B,C;一个执行完后唤醒另外一个线程。主要使用Object.wait()Object.notify()这两个方法。

实现方法

  1. A->B, B->C,C->A之间状态的转换分别使用不同的对象锁
  2. 不同状态之间的转换使用同一个状态锁,完成后使用notifyAll,并在自己的线程中检查,是否满足执行条件,不满足则继续wait。

代码实现

  1. 使用不同的对象锁

这种方法其实是有问题的,由于嵌套的同步对象,存在死锁的风险,在两个的时候就比较容易提现出来。3个线程就不太容易复现,因为要3个线程同时进入Thread.yeild()那段代码,然后同时唤醒,都想要获取next的监视器锁,但是都获取不到。

public class CrycleThreader {

    private void test() {
        LockHolder A = new LockHolder();
        LockHolder B = new LockHolder();
        LockHolder C = new LockHolder();
        A.setReady(true);
        CycleClass cycleA = new CycleClass(A, B, "A", 10);
        CycleClass cycleB = new CycleClass(B, A, "B", 10);
        // CycleClass cycleC = new CycleClass(C, A, "C", 10);
        cycleA.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        cycleB.start();
        // cycleC.start();
    }

    public static void main(String[] args) {
        new CrycleThreader().test();
        // new CrycleThreader().testFail();
    }

    //
    private class CycleClass extends Thread {
        private volatile LockHolder done;
        private volatile LockHolder next;
        private String name;
        private int count;

        /**
         * 
         */
        public CycleClass(LockHolder done, LockHolder next, String name, int count) {
            this.done = done;
            this.next = next;
            this.name = name;
            this.count = count;
        }

        @Override
        public void run() {
            while (count-- > 0) {
                synchronized (done) {
                    while (!done.isReady()) {
                        try {
                            done.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    done.setReady(false);
                    next.setReady(true);
                    System.out.println(name + "--> " + count);//如果放到next.notify()后面 会不正常的
                    //Thread.yield();
                    synchronized (next) {
                        next.notify();
                    }

                }
            }
        }
    }

    private class LockHolder {
        private boolean ready = false;

        /**
         * Getter method for property <tt>ready</tt>.
         * 
         * @return property value of ready
         */
        public boolean isReady() {
            return ready;
        }

        /**
         * Setter method for property <tt>ready</tt>.
         * 
         * @param ready
         *            value to be assigned to property ready
         */
        public void setReady(boolean ready) {
            this.ready = ready;
        }

    }
}
  1. 使用同一个监视器锁,

一个线程结束后唤醒所有的线程,让其自己判断是否要往下执行。封装了锁持有器,所有的线程都对锁同步,在这个对象中显示出接下来该执行哪个。只有一个锁,不会有死锁的情况,但是每次都是notifyAll,在多线程的情况下可能会造成线程切换上下文换入换出。 但是在我的机子上3个线程2比1块

public class CycleHandler {
    // time cost 3722
    public void doTest() {
        String A = "A";
        String B = "B";
        String C = "C";
        int count = 100000;
        LockHolder holder = new LockHolder();
        holder.setReadyObj(A);
        CountDownLatch latch = new CountDownLatch(1);
        Cycle cycleA = new Cycle(A, B, holder, count, latch);
        Cycle cycleB = new Cycle(B, C, holder, count, latch);
        Cycle cycleC = new Cycle(C, A, holder, count, latch);
        long startTime = System.currentTimeMillis();

        cycleA.start();
        cycleB.start();
        cycleC.start();
        latch.countDown();
        try {
            cycleA.join();
            cycleB.join();
            cycleC.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        long endTime = System.currentTimeMillis();

        System.out.println("execute time: " + (endTime - startTime));
    }

    public static void main(String[] args) {
        new CycleHandler().doTest();
    }

    private class Cycle extends Thread {
        private final LockHolder lockHoder;
        private Object next;
        private Object cur;
        private int count;
        private CountDownLatch latch;

        public Cycle(Object cur, Object next, LockHolder holder, int count, CountDownLatch lacth) {
            this.cur = cur;
            this.next = next;
            this.lockHoder = holder;
            this.count = count;
            this.latch = lacth;
        }

        @Override
        public void run() {
            try {
                latch.await();
            } catch (InterruptedException e1) {
                // logger.error("", e);
                e1.printStackTrace();
            }
            while (count-- > 0) {
                synchronized (lockHoder) {
                    while (!lockHoder.getReadyObj().equals(cur)) {
                        try {
                            lockHoder.wait();
                        } catch (InterruptedException e) {
                            e.printStackTrace();
                        }
                    }
                    System.out.println(cur.toString() + "--> " + count);
                    lockHoder.setReadyObj(next);
                    lockHoder.notifyAll();
                }
            }
        }
    }

    private class LockHolder {

        private Object readyObj;

        public Object getReadyObj() {
            return readyObj;
        }

        public void setReadyObj(Object readyObj) {
            this.readyObj = readyObj;
        }

    }

}

其他

  1. 在使用wait和notify这两个方法必须在该对象的同步区内。否则会抛出异常IllegalMonitorStateException这两个是native的方法,看不到细节
  2. 不要使用嵌套的对象锁,可能会出现死锁。
  3. 内部类的对象可以使用外部类的属性(Outter.this.xxx)
  4. 监视器锁不能被赋值,一旦改了就不是同一个监视器锁了
  5. 采用自旋锁避免线程的异常唤醒(检查下是否满足往下执行的条件)
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值