java怎么实现两个线程按顺序交替输出1-100

这篇文章主要讲解了“java怎么实现两个线程按顺序交替输出1-100”,文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习“java怎么实现两个线程按顺序交替输出1-100”吧!

创新互联公司是一家集网站建设,柴桑企业网站建设,柴桑品牌网站建设,网站定制,柴桑网站建设报价,网络营销,网络优化,柴桑网站推广为一体的创新建站企业,帮助传统企业提升企业形象加强企业竞争力。可充分满足这一群体相比中小企业更为丰富、高端、多元的互联网需求。同时我们时刻保持专业、时尚、前沿,时刻以成就客户成长自我,坚持不断学习、思考、沉淀、净化自己,让我们为更多的企业打造出实用型网站。

解法一

有了上面的思路,你肯定能快速写出以下代码:

public class PrintNumber extends Thread {
    private static int cnt = 0;
    private int id;  // 线程编号 
    public PrintNumber(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        while (cnt < 100) {
            while (cnt%2 == id) {
                cnt++;
                System.out.println("thread_" + id + " num:" + cnt);
            }
        }
    }

    public static void main(String[] args) {
        Thread thread0 = new PrintNumber(0);
        Thread thread1 = new PrintNumber(1);
        thread0.start();
        thread1.start();
    }
}

但当你实际运行后会发现!!!

thread_0 num:1
thread_0 num:3
thread_1 num:3
thread_1 num:5
thread_1 num:6
thread_0 num:5
thread_0 num:8
thread_0 num:9
thread_1 num:8
thread_0 num:11
thread_1 num:11
.........

不仅顺序不对,还有重复和丢失!问题在哪?回到代码中cnt++; System.out.println("thread_" + id + " num:" + cnt); 这两行,它主要包含两个动作,cnt++和输出,当cnt++执行完成后可能就已经触发了另一个线程的输出。简化下执行流程,每个时刻JVM有4个动作要执行。

  1. thread_0 cnt++

  2. thread_0 print

  3. thread_1 cnt++

  4. thread_1 print 根据Java as-if-serial语义,jvm只保证单线程内的有序性,不保证多线程之间的有序性,所以上面4个步骤的执行次序可能是 1 2 3 4,也可能是1 3 2 4,更可能是1 3 4 2,对于上面的代码而言就是最终次序可能会发生变化。另外,cnt++ 可以拆解为两行底层指令,tmp = cnt + 1; cnt = tmp,当两个线程同时执行上述指令时就会面临和1 2 3 4步骤同样的问题,…… 没错,多线程下的行为,和你女朋友的心思一样难以琢磨。 如何解决这个问题?解决方案本质上都是保证代码执行顺和我们预期的一样就行,正确的解法一和后面几个解法本质上都是同样的原理,只是实现方式不一样。

解法一正确的代码如下:

public class PrintNumber extends Thread {
    private static AtomicInteger cnt = new AtomicInteger();
    private int id;
    public PrintNumber(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        while (cnt.get() <= 100) {
            while (cnt.get()%2 == id) {
                System.out.println("thread_" + id + " num:" + cnt.get());
                cnt.incrementAndGet();
            }
        }
    }

    public static void main(String[] args) {
        Thread thread0 = new PrintNumber(0);
        Thread thread1 = new PrintNumber(1);
        thread0.start();
        thread1.start();
    }
}

上面代码通过AtomicInteger的incrementAndGet方法将cnt++的操作变成了一个原子操作,避免了多线程同时操作cnt导致的数据错误,另外,while (cnt.get()%2 == id也能保证只有单个线程才能进入while循环里执行,只有当前线程执行完inc后,下一个线程才能执行print,所以这个代码是可以满足我们交替输出的需求的。 但是,这种方法很难驾驭,如果说我吧run函数写成下面这样:

    @Override
    public void run() {
        while (cnt.get() <= 100) {
            while (cnt.get()%2 == id) {
                cnt.incrementAndGet();
                System.out.println("thread_" + id + " num:" + cnt.get());
            }
        }
    }

只需要把print和cnt.incrementAndGet()换个位置,结果就完全不一样了,先inc可能导致在print执行前下一个线程就进入执行改变了cnt的值,导致结果错误。另外这种方法其实也不是严格正确的,如果不是print而是其他类似的场景,可能会出问题,所以这种写法强烈不推荐

解法二

事实上,我们只需要cnt++和print同时只有一个线程在执行就行了,所以我们可以简单将方法一中错误的方案加上synchronized即可,代码如下:

public class PrintNumber extends Thread {
    private static int cnt = 0;
    private int id;  // 线程编号
    public PrintNumber(int id) {
        this.id = id;
    }

    @Override
    public void run() {
        while (cnt <= 100) {
            while (cnt%2 == id) {
                synchronized (PrintNumber.class) {
                    cnt++;
                    System.out.println("thread_" + id + " num:" + cnt);
                }
            }
        }
    }

    public static void main(String[] args) {
        Thread thread0 = new PrintNumber(0);
        Thread thread1 = new PrintNumber(1);
        thread0.start();
        thread1.start();
    }
}

这里我用了synchronized关键词将cnt++和print包装成了一个同步代码块,可以保证只有一个线程可以执行。这里不知道有没有人会问,cnt需不需要声明为volatile,我的回答是不需要,因为synchronized可以保证可见性。

大家有没有发现,我上面代码中一直都用了while (cnt.get()%2 == id)来判断cnt是否是自己要输出的数字,这就好比两个小孩轮流报数,每个小孩都要耗费精力时不时看看是否到自己了,然后选择是否报数,这样显然太低效了。能不能两个小孩之间相互通知,一个小孩报完就通知下另一个小孩,然后自己休息,这样明显对双方来说损耗的精力就少了很多。如果我们代码能有类似的机制,这里就能损耗更少的无用功,提高性能。

这就得依赖于java的wait和notify机制,当一个线程执行完自己的工作,然后唤醒另一个线程,自己去休眠,这样每个线程就不用忙等。代码改造如下,这里我直接去掉了while (cnt.get()%2 == id)

    @Override
    public void run() {
        while (cnt <= 100) {
            synchronized (PrintNumber.class) {
                cnt++;
                System.out.println("thread_" + id + " num:" + cnt);
                PrintNumber.class.notify();
                try {
                    PrintNumber.class.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }

解法三

能用synchronized的地方就能用ReentrantLock,所以解法三和解法二本质上是一样的,就是把synchronized换成了lock而已,然后把wait和notify换成Condition的signal和await,改造后的代码如下:

public class PrintNumber extends Thread {
    private static Lock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();
    private int id;
    private static int cnt = 0;
    public PrintNumber(int id) {
        this.id = id;
    }

    private static void print(int id) {

    }

    @Override
    public void run() {
        while (cnt <= 100) {
            lock.lock();
            System.out.println("thread_" + id + " num:" + cnt);
            cnt++;
            condition.signal();
            try {
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Thread thread0 = new PrintNumber(0);
        Thread thread1 = new PrintNumber(1);
        thread0.start();
        thread1.start();
    }
}

感谢各位的阅读,以上就是“java怎么实现两个线程按顺序交替输出1-100”的内容了,经过本文的学习后,相信大家对java怎么实现两个线程按顺序交替输出1-100这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是创新互联,小编将为大家推送更多相关知识点的文章,欢迎关注!


文章名称:java怎么实现两个线程按顺序交替输出1-100
链接地址:http://hbruida.cn/article/jsjojh.html