这篇文章主要讲解了“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个动作要执行。
thread_0 cnt++
thread_0 print
thread_1 cnt++
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这一问题有了更深刻的体会,具体使用情况还需要大家实践验证。这里是亿速云,小编将为大家推送更多相关知识点的文章,欢迎关注!
免责声明:本站发布的内容(图片、视频和文字)以原创、转载和分享为主,文章观点不代表本网站立场,如果涉及侵权请联系站长邮箱:is@yisu.com进行举报,并提供相关证据,一经查实,将立刻删除涉嫌侵权内容。