千家信息网

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

发表于:2024-09-22 作者:千家信息网编辑
千家信息网最后更新 2024年09月22日,这篇文章主要讲解了"java怎么实现两个线程按顺序交替输出1-100",文中的讲解内容简单清晰,易于学习与理解,下面请大家跟着小编的思路慢慢深入,一起来研究和学习"java怎么实现两个线程按顺序交替输
千家信息网最后更新 2024年09月22日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:1thread_0 num:3thread_1 num:3thread_1 num:5thread_1 num:6thread_0 num:5thread_0 num:8thread_0 num:9thread_1 num:8thread_0 num:11thread_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这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是,小编将为大家推送更多相关知识点的文章,欢迎关注!

0