Java中控制多线程顺序执行

  • 一、概述
  • 二、普通示例
  • 三、控制示例
    • 3.1、设置线程优先级
    • 3.2、使用线程类的join()
      • 3.2.1、在主线程join()
      • 3.2.2、在子线程join()
    • 3.3、使用等待/通知的机制来阻塞线程
      • 3.3.1、使用synchronized+Object.wait()+Object.notify()
      • 3.3.2、ReetrantLock+Condition
      • 3.3.3、CountDownLatch
      • 3.3.4、Semaphore
    • 3.4、使用单例线程池newSingleThreadExecutor
  • 四、Cyclicbarrier

一、概述

Java的线程调度策略是基于线程优先级的抢占式调度,同优先级的线程的执行顺序是看JVM心情的、不可预测的。那有什么办法来控制多线程按照开发者意愿的顺序执行呢?方法总比问题多:

  • 首先设置线程的优先级肯定是一种选择,但最多只能控制10个不同优先级线程的顺序执行。
  • Thread类中有提供一个插队的方法join(),那就可以利用这个插队的方法将线程排好执行顺序。
    • 主线程join()
    • 子线程join()
  • Java中有线程间的等待/通知的机制,那就做成一个等待/通知来控制线程的执行顺序。
    • synchronized+Object.wait()+Object.notify()
    • ReetrantLock+Condition
    • CountDownLatch
    • Semaphore
  • Java中有提供一个单例线程池newSingleThreadExecutor,它就是解决这个问题的优选。

在网上有些文章说Cyclicbarrier也可以实现控制顺序,但研究下来并不是同意义上的顺序,Cyclicbarrier是标记若干个目标点(目标点有需满足的线程数),在并发时,需等到规定线程数到达标记点,然后才可以继续往下执行。那谁先到达目标点还是要看JVM的心情啊,并且目标点后,所有线程继续执行,那执行顺序也是不受控的。(代码在最后有提供,可参考)

二、普通示例

这里先给一个不加任何控制操作的三个线程执行示例,方便对照测试:

public class ThreadSequence {volatile static Thread t0, t1, t2;public static void main(String[] args) {//初始化线程usePriority();//启动线程t0.start();t1.start();t2.start();}private static void usePriority() {t0 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "执行完成");}});t1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "执行完成");}});t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName() + "执行完成");}});}}

可以运行多几次,就会发现会出现顺序错乱的现象。

三、控制示例

3.1、设置线程优先级

通过设置线程的优先级来控制执行顺序,但不能设置同一优先级,所以这个方案最多支持10个线程的顺序执行。优先级1-10从高到低顺序执行。【t0->t1->t2】

public class ThreadSequence {volatile static Thread t0,t1,t2;public static void main(String[] args) {usePriority();t0.setPriority(3);t1.setPriority(2);t2.setPriority(1);t1.start();t0.start();t2.start();}private static void usePriority(){t0 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行完成");}});t1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行完成");}});t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行完成");}});}
}

3.2、使用线程类的join()

Thread类中定义的join()方法,调用此方法使所属的线程对象进入执行run()方法中任务,而当前线程进入无限的阻塞,直到join()的线程执行完成。

3.2.1、在主线程join()

在主线程按顺序join()子线程,来控制线程按顺序执行:【t0->t1->t2】

public class ThreadSequence {volatile static Thread t0,t1,t2;public static void main(String[] args) {//使用joinuseJoin();t0.start();t0.join();t1.start();t1.join();t2.start();}//使用joinprivate static void useJoin(){t0 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行完成");}});t1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行完成");}});t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行完成");}});}}

3.2.2、在子线程join()

在子线程前置子线程的join(),来控制线程按顺序执行:【t2->t1->t0】

public class ThreadSequence {volatile static Thread t0,t1,t2;public static void main(String[] args) {//使用joinuseJoin();t0.start();t1.start();t2.start();}//使用joinprivate static void useJoin(){t0 = new Thread(new Runnable() {@Overridepublic void run() {try {t1.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"执行完成");}});t1 = new Thread(new Runnable() {@Overridepublic void run() {try {t2.join();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"执行完成");}});t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行完成");}});}}

3.3、使用等待/通知的机制来阻塞线程

3.3.1、使用synchronized+Object.wait()+Object.notify()

wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。
在调用wait()之前,线程必须获取到该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。【t0->t1->t2】

public class ThreadSequence {volatile static Thread t0,t1,t2;static boolean t1Run = false,t2Run = false; //定义n-1个运行条件static Object lock1 = new Object(),lock2 = new Object(); //定义n-1个锁资源public static void main(String[] args) {useWait();t2.start();t0.start();t1.start();}private static void useWait(){t0 = new Thread(new Runnable() {@Overridepublic void run() {//t0执行synchronized (lock1){System.out.println(Thread.currentThread().getName()+"执行");//通知t1执行t1Run = true;//释放lock1lock1.notify();System.out.println(Thread.currentThread().getName()+"执行完成");}}});t1 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (lock1){if (!t1Run){try {//lock1等待lock1.wait();System.out.println(Thread.currentThread().getName()+"进入等待");} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"执行");synchronized (lock2) {//通知t2执行t2Run = true;//释放lock2lock2.notify();}System.out.println(Thread.currentThread().getName()+"执行完成");}}});t2 = new Thread(new Runnable() {@Overridepublic void run() {synchronized (lock2){if (!t2Run){//t2执行try {//lock2等待lock2.wait();System.out.println(Thread.currentThread().getName()+"进入等待");} catch (InterruptedException e) {e.printStackTrace();}}System.out.println(Thread.currentThread().getName()+"执行");System.out.println(Thread.currentThread().getName()+"执行完成");}}});}
}

3.3.2、ReetrantLock+Condition

Condition可以实现多路通知,也就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。在使用notify/notifyAll通知时,被通知的线程却是由JVM随机选择的。【t0->t1->t2】

public class ThreadSequence {volatile static Thread t0,t1,t2;static boolean t1Run = false,t2Run = false; //定义n-1个运行条件static Lock lock = new ReentrantLock();static Condition condition1 = lock.newCondition();static Condition condition2 = lock.newCondition();public static void main(String[] args) {useCondition();t2.start();t0.start();t1.start();}private static void useCondition(){t0 = new Thread(new Runnable() {@Overridepublic void run() {//t0获取锁lock.lock();System.out.println(Thread.currentThread().getName()+"执行");//设置t1可运行t1Run = true;//通知t1运行condition1.signal();//t0释放锁lock.unlock();System.out.println(Thread.currentThread().getName()+"执行完成");}});t1 = new Thread(new Runnable() {@Overridepublic void run() {//t1获取锁lock.lock();try {//是否可运行状态if (!t1Run){//不可运行状态,进入等待condition1.await();System.out.println(Thread.currentThread().getName()+"进入等待");}System.out.println(Thread.currentThread().getName()+"执行");//设置t2为可运行t2Run = true;//通知t2运行condition2.signal();}catch (Exception e){e.printStackTrace();}finally {//t1释放锁lock.unlock();}System.out.println(Thread.currentThread().getName()+"执行完成");}});t2 = new Thread(new Runnable() {@Overridepublic void run() {//t2获取锁lock.lock();try {//是否可运行状态if (!t2Run){//不可运行状态,进入等待condition2.await();System.out.println(Thread.currentThread().getName()+"进入等待");}System.out.println(Thread.currentThread().getName()+"执行");}catch (Exception e){e.printStackTrace();}finally {//t2释放锁lock.unlock();}System.out.println(Thread.currentThread().getName()+"执行完成");}});}}

3.3.3、CountDownLatch

ountDownLatch 的作用是:当一个线程需要另外一个或多个线程完成后,再开始执行。比如主线程要等待一个子线程完成环境相关配置的加载工作,主线程才继续执行,就可以利用 CountDownLatch 来实现。
用到的方法:

  • CountDownLatch(int count): 构造方法,创建一个值为count 的计数器
  • await(): 阻塞当前线程,将当前线程加入阻塞队列。
  • countDown(): 对计数器进行递减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程。
    【t0->t1->t2】
public class ThreadSequence {volatile static Thread t0,t1,t2;/*** 计数器1 用于T0线程通知T1线程* 计数器2 用于T1线程通知T2线程* 注意:这里个数都设置成立1 ,当T0执行完成后,执行countDown,来通知T1线程*/static CountDownLatch countDownLatch1 = new CountDownLatch(1);static CountDownLatch countDownLatch2 = new CountDownLatch(1);public static void main(String[] args) {useCountDownLatch();t2.start();t1.start();t0.start();}private static void useCountDownLatch(){t0 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"执行");//唤醒阻塞线程t1countDownLatch1.countDown();System.out.println(Thread.currentThread().getName()+"执行完成");}});t1 = new Thread(new Runnable() {@Overridepublic void run() {try {countDownLatch1.await();System.out.println(Thread.currentThread().getName()+"进入等待");} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"执行");//唤醒阻塞线程t2countDownLatch2.countDown();System.out.println(Thread.currentThread().getName()+"执行完成");}});t2 = new Thread(new Runnable() {@Overridepublic void run() {try {//t2等待countDownLatch2.await();System.out.println(Thread.currentThread().getName()+"进入等待");} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"执行");System.out.println(Thread.currentThread().getName()+"执行完成");}});}
}

3.3.4、Semaphore

Semaphore计数信号量,常用于限制可以访问某些资源(物理或逻辑的)线程数目。
用到的方法:

  • Semaphore(int permits): 构造方法,permits就是允许同时运行的线程数目

  • public Semaphore(int permits,boolean fair): permits就是允许同时运行的线程数目 ,fair 是否为公平锁,如果是公平锁,那么获得锁的顺序与线程启动顺序有关

  • void acquire(): 从此信号量获取一个许可,在提供一个许可前一直将线程阻塞,否则线程被中断。

  • tryAcquire(): 尝试获得令牌,返回获取令牌成功或失败,不阻塞线程。

  • release() : 释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。
    【t0->t1->t2】

public class ThreadSequence {volatile static Thread t0,t1,t2;static Semaphore semaphore1 = new Semaphore(0);static Semaphore semaphore2 = new Semaphore(0);public static void main(String[] args) {useSemaphore();t1.start();t0.start();t2.start();}private static void useSemaphore(){t0 = new Thread(new Runnable() {@Overridepublic void run() {try {//释放资源,semaphore1加1semaphore1.release();} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"执行完成");}});t1 = new Thread(new Runnable() {@Overridepublic void run() {try {//获取资源,semaphore1减1,为0时进入阻塞semaphore1.acquire();} catch (InterruptedException e) {e.printStackTrace();}try {//释放资源,semaphore2加1semaphore2.release();} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"执行完成");}});t2 = new Thread(new Runnable() {@Overridepublic void run() {try {//获取资源,semaphore2减1 为0时进入阻塞semaphore2.acquire();} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"执行完成");}});}
}

3.4、使用单例线程池newSingleThreadExecutor

使用newSingleThreadExecutor线程池,由于核心线程数只有一个,所以只能按提交顺序执行。【t0->t1->t2】

public class ThreadSequence {volatile static Thread t0,t1,t2;//创建单例的线程池static ExecutorService executorService = Executors.newSingleThreadExecutor();public static void main(String[] args) {//按指定顺序提交useSingleThreadExecutor();executorService.submit(t0);executorService.submit(t1);executorService.submit(t2);//销毁executorService.shutdown();}//使用单例线程池-newSingleThreadExecutorprivate static void useSingleThreadExecutor(){t0 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"0执行完成");}});t1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"1执行完成");}});t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"2执行完成");}});}
}

四、Cyclicbarrier

public class ThreadSequence {volatile static Thread t0,t1,t2;/*** 设置n-1个等待标志* 设置n-1个线程互相等待,直到到达同一个同步点,再继续一起执行。T0不执行完,T1就永远不会执行*/static CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);static CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);public static void main(String[] args) {useCyclicBarrier();t1.start();t0.start();t2.start();}//使用Cyclicbarrierprivate static void useCyclicBarrier(){t0 = new Thread(new Runnable() {@Overridepublic void run() {try {cyclicBarrier1.await();System.out.println(Thread.currentThread().getName()+"到达目的1,进入等待");} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"执行");System.out.println(Thread.currentThread().getName()+"执行完成");}});t1 = new Thread(new Runnable() {@Overridepublic void run() {try {cyclicBarrier1.await();System.out.println(Thread.currentThread().getName() + "到达目的1,进入等待");System.out.println(Thread.currentThread().getName() + "执行");System.out.println(Thread.currentThread().getName() + "执行完成");cyclicBarrier2.await();System.out.println(Thread.currentThread().getName() + "到达目的2,进入等待");} catch (Exception e) {e.printStackTrace();}}});t2 = new Thread(new Runnable() {@Overridepublic void run() {try {cyclicBarrier2.await();System.out.println(Thread.currentThread().getName()+"到达目的2,进入等待");} catch (Exception e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+"执行");System.out.println(Thread.currentThread().getName()+"执行完成");}});}
}

Java中控制多线程顺序执行相关推荐

  1. c++11 多线程 顺序执行_前阿里P8架构师总结的一些关于Java多线程的编程经验丨干货...

    推荐阅读 Java程序员备战"金九银十"必备的面试技巧(附阿里Java岗面试题) 一.认识多任务.多进程.单线程.多线程 要认识多线程就要从操作系统的原理说起. 以前古老的DOS操 ...

  2. Java中的多线程编程(超详细总结)

    文章目录 Java中的多线程编程(超详细总结) 一.线程与多线程的概念 二.线程与进程之间的关系 三.一个线程的生命周期 四.多线程的目的和意义 五.线程的实现的方式 Java中的多线程编程(超详细总 ...

  3. JAVA中的多线程(一)

    JAVA中的多线程(一) 进程:是一个正在执行中的程序 每一个进程执行都有一个执行的顺序,该顺序是一个执行路径,或者叫控制单元 线程:就是进程中的一个独立的控制单元 线程在控制着进程的执行 一个进程中 ...

  4. Java中的多线程基本介绍

    在 Java 中,多线程是指同时执行两个或多个线程以最大限度地利用 CPU 的过程. Java 中的线程是一个轻量级进程,只需要较少的资源即可创建和共享进程资源. 多线程和多进程用于 Java 中的多 ...

  5. JAVA中的多线程(八):线程的优先级和yield方法

    JAVA中的多线程(八):线程的优先级和yield方法 优先级代表着抢资源的频率 所有线程默认优先级是5 yield()临时释放线程的执行权 1 class Demo implements Runna ...

  6. JAVA中的多线程与运动仿真(1)——用JAVA来放一场烟花

    JAVA中的多线程与运动仿真(1)--用JAVA来放一场烟花 一.实现效果的简单展示: 初步实现的动态效果为在鼠标点击之后,点击之处出现一簇小球,然后向不同方向散开变大. 利用这一效果,再在后续增加颜 ...

  7. java中实现多线程的三种方式

    java中实现多线程的三种方式 1.实现多线程的方法: 在java中实现多线程的两途径:继承Thread类,实现Runable接口(Callable) 2.继承Thread类实现多线程: ​ 继承类T ...

  8. Java基础——深入理解Java中的多线程(超级详细,值得你看)

    Java中的多线程 进程(process)是程序的一次执行过程,或是正在运行的有一个程序,或是正在运行的一个程序.是一个动态的过程:有它自身的产生.存在和消亡的过程.--生命周期. 线程(thread ...

  9. JAVA中实现多线程

    一,JAVA中实现多线程(一) 1,在Java中负责线程的这个功能的是Java.lang.Thread 这个类 2,可以通过创建 Thread 的实例来创建新的线程. 3,每个线程都是通过某个特定Th ...

最新文章

  1. 想要学习Python爬虫的你,真的了解爬虫最基础的知识储备吗?
  2. python调用离线百度语音识别_python调用百度语音识别api
  3. Fiddler代理手机抓包
  4. 系统测试:单元测试相关知识笔记
  5. 当你使用微信和QQ的时候,请不要忘记ICQ这个伟大的公司!
  6. maven中pom文件中scope的作用
  7. 用户画像方法论与工程化解决方案 pdf_《用户画像》作者:赵宏田
  8. MIS系统(13)- 系统管理之权限管理
  9. Linux 系统编程 --文件IO-write()、read()、lseek()函数
  10. 【建议收藏】15755字,讲透MySQL性能优化(包含MySQL架构、存储引擎、调优工具、SQL、索引、建议等等)
  11. UG\NX二次开发 获取曲线上某个位置的点坐标、切线矢量、主法线矢量、副法线矢量 UF_MODL_ask_curve_props
  12. 银河麒麟加完全自主的龙芯指令集,组合渡劫能否成功
  13. IDEA提交不显示Git文件呈现红色
  14. Mysql统计分组后每组数据与每组数量区别
  15. UEFI显示BMP图片
  16. ui设计岗位招聘要求有哪些?
  17. Yeelight LED智能灯泡(彩光版)代码控制
  18. vue打包时报错Ineffective mark-compacts near heap limit Allocation failed-JavaScript heap out of memory
  19. 企业通讯录让沟通协作效率更高
  20. 单片机项目之智能计算器

热门文章

  1. 北京公交为什么愿意降价
  2. 在 UI 自动化中调用浏览器 API 的方法与使用场景
  3. VMware安装XP
  4. CSS3渐变Gradients
  5. 浏览器滚动条样式修改
  6. 为计算机奠定了基础,计算机应用基础 习题
  7. SpringBoot - 获取Get请求参数详解(附样例:非空、默认值、数组、对象)
  8. 用户和计算机硬盘系统,技术宅教你一个固态硬盘硬盘装几十个电脑操作系统
  9. 今天公布!英语四六级考试成绩
  10. 【mysql】连表查询(内连接,左连接,右连接,全外连接)