一、导读
最近看到一个需求,假如有个公众号,需要每天向订阅者推送消息,但是公众号需要获取最新的消息来进行推送,这就需要至少两个线程来完成,一个线程用来推送消息,一个用来获取最新消息。
那么问题来了——线程发消息要用到最新策略,所以必须让获取新消息的线程先执行完,而线程的调度是随机的,执行顺序由 *** 作系统决定,我该怎么让推送消息线程在获取消息线程执行完了再执行?
这就要牵涉到线程间的通讯了,接下来的 *** 作来帮助大家了解线程间通讯在实际项目中的运用有个大致了解!
方便理解我们用下面这个例子来讲解!
背景 :我们需要先上班,才能摸鱼(或者先玩王者荣耀,才知道自己菜 )
翻译成多线程:一个线程负责上班,一个线程负责摸鱼,而这两个线程的调度是随机的,顺序由 *** 作系统决定,我们怎么保证只有上班后,再开始摸鱼?
Talk is cheap. Show me the code!
二、具体实现
文章会涉及到的方法:
- 基于join
- 基于volatile
- 基于synchronized
- 基于reentrantLock
- 基于countDownLatch
1、通过Join实现
join是Thread类的方法,底层基于wait+notify,你可以把这个方法理解成插队,谁调用谁插队,具有局限性。
适用于线程较少的场景,如果线程多了会造成无限套娃,有点麻烦,不够优雅。
public class GoldbrickingTest { //用来记录摸鱼时长 static int hour; public static void main(String[] args) { //线程1:用来上班 Thread thread1 = new Thread(() -> { System.out.println("开始上班!"); }); //线程2:用来摸鱼 Thread thread2 = new Thread(() -> { try { //让线程1插队,线程2执行到这儿时会被阻塞,直到线程1执行完 thread1.join(); } catch (Exception e) { e.printStackTrace(); } for (hour = 1; hour < 9; hour = hour + 1) { System.out.println("开始摸鱼,已经摸了" + hour + "小时"); try { Thread.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } //众所周知 摸鱼8小时就可以下班了! if (hour == 8) { System.out.println("------------------>下班了!"); break; } } }); //线程启动 thread2.start(); thread1.start(); } }
2、通过volatile实现
public class GoldbrickingTest02 { //定义一个共享变量用来线程间通信,用volatile修饰,保证它内存可见 static volatile boolean flag = false; //用来记录摸鱼时长 static int hour; public static void main(String[] args) { //线程1:用来上班 Thread thread1 = new Thread(() -> { while (true) { if (!flag) { System.out.println("开始上班!"); // 通知thread2你可以执行了 flag = true; break; } } }); //线程2:用来摸鱼 Thread thread2 = new Thread(() -> { while (true) { if (flag) { for (hour = 1; hour < 9; hour = hour + 1) { System.out.println("开始摸鱼,已经摸了" + hour + "小时"); try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } //众所周知 摸鱼8小时就可以下班了! if (hour == 8) { System.out.println("------------------>下班了!"); break; } } break; } } }); //线程启动 thread2.start(); thread1.start(); } }
3、通过Synchronized实现
synchronized需要配合wait、notify方法一起使用,它俩都是Object类的通讯方法。
public class GoldbrickingTest03 { //用来记录摸鱼时长 static int hour; public static void main(String[] args) { GoldbrickingTest03 goldbrickingTest03 = new GoldbrickingTest03(); goldbrickingTest03.execute(); } public void execute() { //线程1:用来上班 Thread thread1 = new Thread(() -> { synchronized (this) { try { System.out.println("开始上班!"); //唤醒等待中的thread2 notify(); } catch (Exception e) { e.printStackTrace(); } } }); //线程2:用来摸鱼 Thread thread2 = new Thread(() -> { synchronized (this) { try { //让线程等待 wait(); for (hour = 1; hour < 9; hour = hour + 1) { System.out.println("开始摸鱼,已经摸了" + hour + "小时"); Thread.sleep(200); //众所周知 摸鱼8小时就可以下班了! if (hour == 8) { System.out.println("------------------>下班了!"); break; } } } catch (InterruptedException e) { e.printStackTrace(); } } }); thread2.start(); thread1.start(); } }
4、通过ReentrantLock实现
ReentrantLock是juc包下的并发工具,需结合Condition的await()和signal(),底层原理与上面的wait和notify类似。
public class GoldbrickingTest04 { //用来记录摸鱼时长 static int hour; public static void main(String[] args) { //实例化一个锁和Condition ReentrantLock lock = new ReentrantLock(); Condition condition = lock.newCondition(); //线程1:用来上班 Thread thread1 = new Thread(() -> { lock.lock(); System.out.println("开始上班!"); // 唤醒等待中的线程 condition.signal(); lock.unlock(); }); //线程2:用来摸鱼 Thread thread2 = new Thread(() -> { lock.lock(); try { //让线程等待 condition.await(); for (hour = 1; hour < 9; hour = hour + 1) { System.out.println("开始摸鱼,已经摸了" + hour + "小时"); Thread.sleep(100); //众所周知 摸鱼8小时就可以下班了! if (hour == 8) { System.out.println("------------------>下班了!"); break; } } } catch (Exception e) { e.printStackTrace(); } finally { lock.unlock(); } }); //线程启动 thread2.start(); thread1.start(); } }
5、通过CountDownLatch实现
这也是juc包下的并发工具,主要有两个常用方法,countDown和await
原理:countDownLatch这个类使一个线程等待其他线程各自执行完毕后再执行。
是通过一个计数器来实现的,计数器的初始值是线程的数量。每当一个线程执行完毕后,计数器的值就-1,当计数器的值为0时,表示所有线程都执行完毕,然后在闭锁上等待的线程就可以恢复工作了。
public class GoldbrickingTest05 { //用来记录摸鱼时长 static int hour; public static void main(String[] args) { //实例化一个CountDownLatch,count设置为1,也就是说,只要调用一次countDown方法就会唤醒线程 CountDownLatch countDownLatch = new CountDownLatch(1); //线程1:用来上班 Thread thread1 = new Thread(() -> { //计数器减一 countDownLatch.countDown(); System.out.println("开始上班!"); }); //线程2:用来摸鱼 Thread thread2 = new Thread(() -> { try { //阻塞当前线程,计数器为0时被唤醒 countDownLatch.await(); for (hour = 1; hour < 9; hour = hour + 1) { System.out.println("开始摸鱼,已经摸了" + hour + "小时"); Thread.sleep(100); //众所周知 摸鱼8小时就可以下班了! if (hour == 8) { System.out.println("------------------>下班了!"); break; } } } catch (Exception e) { e.printStackTrace(); } }); //线程启动 thread1.start(); thread2.start(); } }
以上就是关于多线程执行顺序的小练习,可以尝试去运行一下,有错误欢迎指出,共同进步!
好文章 记得 收藏+点赞+关注 !!!
推荐阅读:
- 由银行填表时返回上一步引发的对回溯算法的思考(java实现回溯算法)
- 通过停车场计算车费案例练习JavaIO流
参考文章:https://blog.csdn.net/qq_33709582/article/details/121900989
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)