美文网首页java多线程
java两个线程交替打印奇偶数

java两个线程交替打印奇偶数

作者: someoneYoung | 来源:发表于2019-02-01 14:22 被阅读24次

引言

java面试中经常会遇到这个问题,如何用两个线程交替打印奇偶数。线程A打印1,线程B打印2,线程A打印3,线程B打印4...这个问题的解题思路是协调两个线程的执行时机,线程A与线程B需要交替执行。实现的方式很多,常用的方法是采用wait/notify方法。
本文记录了笔者解决这个问题的过程,并解释过程中遇到的坑以及如何填坑的,以供朋友们参考。

一种有问题的实现方式

代码思路是这样的:既然要两个线程交替打印奇偶数,那就让两个线程共享一个count,当数字是奇数是线程A打印,当数字是偶数时线程B打印,执行完打印操作后自增count,并利用wait/notify方法去阻塞和唤醒线程。接下来看代码:

public class WrongCountDemo {
    static class PrintOdd implements Runnable {
        private Integer count;
        public PrintOdd(Integer count) {
            this.count = count;
        }

        @Override
        public void run() {
            try {
                synchronized (count) {
                    while (count <= 100) {
                        if (count % 2 == 1) {
                            count.wait();
                        } else {
                            System.out.println("PrintOdd thread print..." + count++);
                            count.notify();
                        }
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    static class PrintEven implements Runnable {
        private Integer count;
        public PrintEven(Integer count) {
            this.count = count;
        }

        @Override
        public void run() {
            try {
                synchronized (count) {
                    while (count <= 100) {
                        if (count % 2 == 0) {
                            count.wait();
                        } else {
                            System.out.println("PrintEven thread print..." + count++);
                            count.notify();
                        }
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }

    public static void main(String[] args) {
        Integer count = 1;
        PrintOdd printOdd = new PrintOdd(count);
        PrintEven printEven = new PrintEven(count);
        new Thread(printOdd).start();
        new Thread(printEven).start();
    }
}

这段代码并不能获得期望的结果,执行结果如下:

PrintEven thread print...1
java.lang.IllegalMonitorStateException
at java.lang.Object.notify(Native Method)
at com.young.print.WrongCountDemo$PrintEven.run(WrongCountDemo.java:52)
at java.lang.Thread.run(Thread.java:745)

问题分析

代码只打印了数字1就抛了异常IllegalMonitorStateException。
查看异常栈,这个异常是打印完数字1后调用count.notify()抛出的。

     * @throws  IllegalMonitorStateException  if the current thread is not
     *               the owner of this object's monitor.
     * @see        java.lang.Object#notifyAll()
     * @see        java.lang.Object#wait()
     */
    public final native void notify();

查看notify方法的源码会发现,IllegalMonitorStateException异常的抛出机制是当前调用线程不是对象锁的持有者。
这就是问题的根源所在,虽然代码里通过synchronized关键字锁住了count对象,但由于count对象执行完自增操作后对象发生变化,所以执行count.notify()时的cout对象已经不是线程synchronized锁住的那个count对象。
那如何修改代码呢?答案是重新定义一个wrapper对象包住Count对象,count属性自增而wrapper对象始终不变。

正确的实现方式

public class CountDemo {
    static class Wrapper {
        private Integer count;
        public Wrapper(Integer count) {
            this.count = count;
        }
        public Integer getCount() {
            return count;
        }
        public void setCount(Integer count) {
            this.count = count;
        }
    }
    static class PrintOdd implements Runnable {
        private Wrapper wrapper;
        public PrintOdd(Wrapper wrapper) {
            this.wrapper = wrapper;
        }
        @Override
        public void run() {
            try {
                synchronized (wrapper) {
                    while (wrapper.getCount() <= 100) {
                        if (wrapper.getCount() % 2 == 0) {
                            wrapper.wait();
                        } else {
                            System.out.println("PrintOdd thread print..." + wrapper.getCount());
                            wrapper.setCount(wrapper.getCount() + 1);
                            wrapper.notify();
                        }
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    static class PrintEven implements Runnable {
        private Wrapper wrapper;
        public PrintEven(Wrapper wrapper) {
            this.wrapper = wrapper;
        }
        @Override
        public void run() {
            try {
                synchronized (wrapper) {
                    while (wrapper.getCount() <= 100) {
                        if (wrapper.getCount() % 2 == 1) {
                            wrapper.wait();
                        } else {
                            System.out.println("PrintEven thread print..." + wrapper.getCount());
                            wrapper.setCount(wrapper.getCount() + 1);
                            wrapper.notify();
                        }
                    }
                }
            } catch (Exception ex) {
                ex.printStackTrace();
            }
        }
    }
    public static void main(String[] args) {
        Wrapper wrapper = new Wrapper(1);
        PrintOdd printOdd = new PrintOdd(wrapper);
        PrintEven printEven = new PrintEven(wrapper);
        new Thread(printOdd).start();
        new Thread(printEven).start();
    }
}

代码执行结果如下:

PrintOdd thread print...1
PrintEven thread print...2
PrintOdd thread print...3
PrintEven thread print...4
...(省略展示)...
PrintOdd thread print...99
PrintEven thread print...100

wait/notify正确的使用姿势

synchronized (sharedObject) { 
    while (condition) { 
         // 条件不满足,当前线程阻塞等待
         sharedObject.wait(); 
    } 
    // 执行条件满足时的逻辑
    doSomething();
    // 通知阻塞的线程可以唤醒
    sharedObject.notify();
}

利用ReentrantLock锁实现

众所周知,除了synchronized关键字的加锁方式,还可以使用Lock锁实现对共享变量的同步控制。利用Lock对象还可以生成Condition条件,Condition条件中包含了await/signal方法,可以用来替代Object对象的wait/notify方法。
下面的代码演示了利用ReentrantLock可重入锁实现两个线程交替打印奇偶数的实现,逻辑与前面的代码相似。

public class CountLockDemo {

    public static void main(String[] args) {
        ReentrantLock lock = new ReentrantLock();
        Condition condition = lock.newCondition();
        Wrapper wrapper = new Wrapper(1);
        new Thread(new PrintOdd(lock, condition, wrapper)).start();
        new Thread(new PrintEven(lock, condition, wrapper)).start();
    }

    static class Wrapper {
        private Integer count;
        public Wrapper(Integer count) {
            this.count = count;
        }
        public Integer getCount() {
            return count;
        }
        public void setCount(Integer count) {
            this.count = count;
        }
    }

    static class PrintOdd implements Runnable {
        private volatile Wrapper wrapper;
        private ReentrantLock lock;
        private Condition condition;
        public PrintOdd(ReentrantLock lock, Condition condition, Wrapper wrapper) {
            this.lock = lock;
            this.condition = condition;
            this.wrapper = wrapper;
        }

        @Override
        public void run() {
            while (wrapper.getCount() <= 100) {
                lock.lock();
                try {
                    if (wrapper.getCount() % 2 == 0) {
                        condition.await();
                    } else {
                        System.out.println("PrintOdd thread print..." + wrapper.getCount());
                        wrapper.setCount(wrapper.getCount() + 1);
                        condition.signal();
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }

    static class PrintEven implements Runnable {
        private volatile Wrapper wrapper;
        private ReentrantLock lock;
        private Condition condition;
        public PrintEven(ReentrantLock lock, Condition condition, Wrapper wrapper) {
            this.lock = lock;
            this.condition = condition;
            this.wrapper = wrapper;
        }

        @Override
        public void run() {
            while (wrapper.getCount() <= 100) {
                lock.lock();
                try {
                    if (wrapper.getCount() % 2 == 1) {
                        condition.await();
                    } else {
                        System.out.println("PrintEven thread print..." + wrapper.getCount());
                        wrapper.setCount(wrapper.getCount() + 1);
                        condition.signal();
                    }
                } catch (Exception ex) {
                    ex.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        }
    }
}

总结

使用synchronized关键字的时候,要保证被锁住的对象不会发生变更。本文所使用的Integer对象以及String之类的基本对象,执行过程中都容易发生对象变更,synchronized使用时要选择不变化的对象。

相关文章

  • java两个线程交替打印奇偶数

    引言 java面试中经常会遇到这个问题,如何用两个线程交替打印奇偶数。线程A打印1,线程B打印2,线程A打印3,线...

  • LOCK--交替打印奇偶数

    两个线程交替打印奇偶数 public class TwoThreadLock { static Locklock ...

  • N个线程交替输出的问题

    1、两个不同的线程交替打印Kao、La。2、两个线程,一个打印100内的奇数一个打印100内的偶数,交替执行。 N...

  • 线程交替打印奇偶数

        面试经常性问到要求手写线程交替打印奇偶数,其实就是考察对线程的灵活控制,本次就用2种方式交替打印奇偶数。并...

  • 一道线程面试题

    2个线程交替打印1-100内的数,线程A打印偶数,线程B打印奇数 打印结果如下: 实现代码

  • 两个线程交替打印0-100的奇偶数

    题目:两个线程,其中一个线程打印奇数,另一个打印偶数,交替输出0-100 方法1:自旋判断 开启两个线程,每个线程...

  • 用程序实现两个线程交替打印 0~100 的奇偶数

    下班途中刷新闻时看到一道面试题:用程序实现两个线程交替打印 0~100 的奇偶数。看到“多线程交替”字样,瞬间就想...

  • 两个线程交替打印奇偶数

    拿到锁,我们就打印 打印完,唤醒其他线程,自己就休眠 新建2个线程1个只处理偶数,第二个只处理奇数(用位运算)用s...

  • 两个线程交替打印奇偶数

    序言 以前看过多线程交替打印奇偶数,知道大概怎么写,实际写的时候会卡住,特此记录下来 方法一:wait, noti...

  • java如何让多个线程交替执行?

    曾经有面试官问过这样一个问题,如何让两个线程交替打印奇数和偶数?如何让多个线程交替执行? 回答这个问题之前需要先了...

网友评论

    本文标题:java两个线程交替打印奇偶数

    本文链接:https://www.haomeiwen.com/subject/afcdsqtx.html