Java中如何保证线程顺序执行
只要了解过多线程,我们就知道线程开始的顺序跟执行的顺序是不一样的。如果只是创建三个线程然后执行,最后的执行顺序是不可预期的。这是因为在创建完线程之后,线程执行的开始时间取决于CPU何时分配时间片,线程可以看成是相对于的主线程的一个异步操作。
public class FIFOThreadExample {
public synchronized static void foo(String name) {
System.out.print(name);
}
public static void main(String[] args) {
Thread thread1 = new Thread(() -> foo("A"));
Thread thread2 = new Thread(() -> foo("B"));
Thread thread3 = new Thread(() -> foo("C"));
thread1.start();
thread2.start();
thread3.start();
}
}
输出结果:ACB/ABC/CBA...
那么我们该如何保证线程的顺序执行呢?
如何保证线程的顺序执行?
1. 使用Thread.join()实现
Thread.join()
的作用是让父线程等待子线程结束之后才能继续运行。以上述例子为例,main()
方法所在的线程是父线程,在其中我们创建了3个子线程A,B,C,子线程的执行相对父线程是异步的,不能保证顺序性。而对子线程使用Thread.join()
方法之后就可以让父线程等待子线程运行结束后,再开始执行父线程,这样子线程执行被强行变成了同步的,我们用Thread.join()
方法就能保证线程执行的顺序性。
public class FIFOThreadExample {
public static void foo(String name) {
System.out.print(name);
}
public static void main(String[] args) throws InterruptedException{
Thread thread1 = new Thread(() -> foo("A"));
Thread thread2 = new Thread(() -> foo("B"));
Thread thread3 = new Thread(() -> foo("C"));
thread1.start();
thread1.join();
thread2.start();
thread2.join();
thread3.start();
}
}
输出结果:ABC
2. 使用单线程线程池来实现
另一种保证线程顺序执行的方法是使用一个单线程的线程池,这种线程池中只有一个线程,相应的,内部的线程会按加入的顺序来执行。
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class FIFOThreadExample {
public static void foo(String name) {
System.out.print(name);
}
public static void main(String[] args) throws InterruptedException{
Thread thread1 = new Thread(() -> foo("A"));
Thread thread2 = new Thread(() -> foo("B"));
Thread thread3 = new Thread(() -> foo("C"));
ExecutorService executor = Executors.newSingleThreadExecutor();
executor.submit(thread1);
executor.submit(thread2);
executor.submit(thread3);
executor.shutdown();
}
}
输出结果:ABC
3. 使用volatile关键字修饰的信号量实现
上面两种的思路都是让保证线程的执行顺序,让线程按一定的顺序执行。这里介绍第三种思路,那就是线程可以无序运行,但是执行结果按顺序执行。
你应该可以想到,三个线程都被创建并start()
,这时候三个线程随时都可能执行run()
方法。因此为了保证run()
执行的顺序性,我们肯定需要一个信号量来让线程知道在任意时刻能不能执行逻辑代码。
另外,因为三个线程是独立的,这个信号量的变化肯定需要对其他线程透明,因此volatile关键字也是必须要的。
public class TicketExample2 {
//信号量
static volatile int ticket = 1;
//线程休眠时间
public final static int SLEEP_TIME = 1;
public static void foo(int name){
//因为线程的执行顺序是不可预期的,因此需要每个线程自旋
while (true) {
if (ticket == name) {
try {
Thread.sleep(SLEEP_TIME);
//每个线程循环打印3次
for (int i = 0; i < 3; i++) {
System.out.println(name + " " + i);
}
} catch (InterruptedException e) {
e.printStackTrace();
}
//信号量变更
ticket = name%3+1;
return;
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(() -> foo(1));
Thread thread2 = new Thread(() -> foo(2));
Thread thread3 = new Thread(() -> foo(3));
thread1.start();
thread2.start();
thread3.start();
}
}
执行结果:
1 0
1 1
1 2
2 0
2 1
2 2
3 0
3 1
3 2
4. 使用Lock和信号量实现
此种方法的思想跟第三种方法是一样的,都是不考虑线程执行的顺序而是考虑用一些方法控制线程执行业务逻辑的顺序。这里我们同样用一个原子类型信号量ticket
,当然你可以不用原子类型,这里我只是为了保证自增操作的线程安全。然后我们用了一个可重入锁ReentrantLock
。用来给方法加锁,当一个线程拿到锁并且标识位正确的时候开始执行业务逻辑,执行完毕后唤醒下一个线程。
这里我们不需要使用while进行自旋操作了,因为Lock可以让我们唤醒指定的线程,所以改成if就可以实现顺序的执行。
public class TicketExample3 {
//信号量
AtomicInteger ticket = new AtomicInteger(1);
public Lock lock = new ReentrantLock();
private Condition condition1 = lock.newCondition();
private Condition condition2 = lock.newCondition();
private Condition condition3 = lock.newCondition();
private Condition[] conditions = {condition1, condition2, condition3};
public void foo(int name) {
try {
lock.lock();
//因为线程的执行顺序是不可预期的,因此需要每个线程自旋
System.out.println("线程" + name + " 开始执行");
if(ticket.get() != name) {
try {
System.out.println("当前标识位为" + ticket.get() + ",线程" + name + " 开始等待");
//开始等待被唤醒
conditions[name - 1].await();
System.out.println("线程" + name + " 被唤醒");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(name);
ticket.getAndIncrement();
if (ticket.get() > 3) {
ticket.set(1);
}
//执行完毕,唤醒下一次。1唤醒2,2唤醒3
conditions[name % 3].signal();
} finally {
//一定要释放锁
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
TicketExample3 example = new TicketExample3();
Thread t1 = new Thread(() -> {
example.foo(1);
});
Thread t2 = new Thread(() -> {
example.foo(2);
});
Thread t3 = new Thread(() -> {
example.foo(3);
});
t1.start();
t2.start();
t3.start();
}
}
输出结果:
线程2 开始执行
当前标识位为1,线程2 开始等待
线程1 开始执行
1
线程3 开始执行
当前标识位为2,线程3 开始等待
线程2 被唤醒
2
线程3 被唤醒
3
上述的执行结果并非唯一,但可以保证打印的顺序一定是123这样的顺序。
参考文章
java 多线程 实现多个线程的顺序执行 - Hoonick - 博客园 (cnblogs.com)
Java lock锁的一些细节_笔记小屋-CSDN博客
VolatileCallSite (Java Platform SE 8 ) (oracle.com)
java保证多线程的执行顺序 - james.yj - 博客园 (cnblogs.com)
Java中如何保证线程顺序执行的更多相关文章
- java中等待所有线程都执行结束(转)
转自:http://blog.csdn.net/liweisnake/article/details/12966761 今天看到一篇文章,是关于java中如何等待所有线程都执行结束,文章总结得很好,原 ...
- java中等待所有线程都执行结束
转自:http://blog.csdn.net/liweisnake/article/details/12966761 今天看到一篇文章,是关于java中如何等待所有线程都执行结束,文章总结得很好,原 ...
- JAVA中等待所有线程都执行结束(转2)
场景: package com.java4all.mypoint; import java.util.concurrent.CountDownLatch; public class ThreadTes ...
- Android中让多个线程顺序执行探究
线程调度是指按照特定机制为多个线程分配CPU的使用权. 有两种调度模型:分时调度模型和抢占式调度模型. 分时调度模型:是指让所有的线程轮流获得cpu的使用权,并且平均分配每个线程占用的CPU的时间片. ...
- java中子类继承父类程序执行顺序
java中子类继承父类程序执行顺序 FatherTest.java public class FatherTest { private String name; public FatherTest() ...
- Java中的成员初始化顺序和内存分配过程
Java中的成员初始化顺序和内存分配过程 原帖是这样描述的: http://java.dzone.com/articles/java-object-initialization?utm_source= ...
- Java中的守护线程 & 非守护线程(简介)
Java中的守护线程 & 非守护线程 守护线程 (Daemon Thread) 非守护线程,又称用户线程(User Thread) 用个比较通俗的比如,任何一个守护线程都是整个JVM中所有非守 ...
- T1,T2,T3 三个线程顺序执行
T1,T2,T3 三个线程顺序执行 现在有 T1.T2.T3 三个线程,你怎样保证 T2 在 T1 执行完后执行,T3 在 T2 执行完后执行?(T1->T2->T3) 这个线程问题通常会 ...
- 详解线程池的作用及Java中如何使用线程池
服务端应用程序(如数据库和 Web 服务器)需要处理来自客户端的高并发.耗时较短的请求任务,所以频繁的创建处理这些请求的所需要的线程就是一个非常消耗资源的操作.常规的方法是针对一个新的请求创建一个新线 ...
随机推荐
- 攻防世界 resver catch-me
catch-me asis-ctf-quals-2016 附件给了个压缩包文件,重命名,解压,获取到elf文件 程序有两处关键比较 第一处: 这里进行动态调试,得到v3=0xB11924E1, byt ...
- 用程序员的思维了解Filecoin
程序员接触一个新技术惯用步骤: 先搜索引擎搜索一波,找个最简单的解释.如果有了个大概的概念,就前往2.否则循环1->1->1...直到有个大概的概念为止. 上官网跑一遍. 各种论坛社区溜达 ...
- Android Studio中Switch控件有关 thumb 和 track 用法
•任务 •属性 android:track:底部的图片(灰->绿) android:thumb:设置 Switch 上面滑动的滑块,也就是上图中的白色圆形滑块 •switch_thumb 点击 ...
- Tkinter教程系列01——引言和安装Tk
Tkinter教程系列01--引言和安装Tk 首发于我的个人博客 https://chens.life/tkinter-tutorial-chapter-01-introduction-and-ins ...
- 第21 章 : Kubernetes 存储架构及插件使用
Kubernetes 存储架构及插件使用 本文将主要分享以下三方面的内容: Kubernetes 存储体系架构: Flexvolume 介绍及使用: CSI 介绍及使用. Kubernetes 存储体 ...
- 痞子衡嵌入式:从头开始认识i.MXRT启动头FDCB里的lookupTable
大家好,我是痞子衡,是正经搞技术的痞子.今天痞子衡给大家介绍的是i.MXRT启动头FDCB里的lookupTable. 一个MCU内部通常有很多外设模块,这些外设模块是各MCU厂商做差异化产品的本质, ...
- 使用SignalR ASP.NET Core来简单实现一个后台实时推送数据给Echarts展示图表的功能
什么是 SignalR ASP.NET Core ASP.NET Core SignalR 是一种开放源代码库,可简化将实时 web 功能添加到应用程序的功能. 实时 web 功能使服务器端代码可以立 ...
- HTML5与CSS3新增特性笔记
HTML5 HTML5和HTML事件 注意:行内代码的为H5新增事件 Window事件属性: 针对 window 对象触发的事件(应用到 标签) onafterprint 文档打印之后运行的脚本 on ...
- 2021浙江省程序省赛(ACFGJLM题解)
A 签到,加起来就行了,记得等于属于先手赢(2A) C 题意 给八个点三维坐标,问是否在三维是立方体 思路 八个点的连成56条线,如果是立方体的话有8条,24条,24条相同的线,且都不相同 用map存 ...
- Mysql 添加 create_time, update_time 创建时间 更新时间 自动更新
# 添加 创建 更新 时间字段 ALTER TABLE `表名` ADD COLUMN `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTA ...