Java中保证线程顺序执行的操作代码

 更新时间:2021年05月15日 09:21:00   作者:六层楼  
本文给大家分享一篇教程关于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线程执行顺序的资料请关注脚本之家其它相关文章!

相关文章

  • SpringBoot连接Nacos集群报400问题及完美解决方法

    SpringBoot连接Nacos集群报400问题及完美解决方法

    这篇文章主要介绍了解决SpringBoot连接Nacos集群报400问题 ,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下
    2022-02-02
  • Java基础类之ArrayUtils工具类详解

    Java基础类之ArrayUtils工具类详解

    这篇文章主要介绍了java.ArrayDeque类使用方法,java.ArrayDeque类提供了可调整大小的阵列,并实现了Deque接口,感兴趣的小伙伴们可以参考一下
    2021-09-09
  • SpringSecurity整合Jwt过程图解

    SpringSecurity整合Jwt过程图解

    这篇文章主要介绍了SpringSecurity整合Jwt过程图解,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-01-01
  • SpringBoot+layuimini实现左侧菜单动态展示的示例代码

    SpringBoot+layuimini实现左侧菜单动态展示的示例代码

    Layuimini是Layui的升级版,它是专业做后台页面的框架,而且是适合PC端和移动端,以下地址可以在PC端显示,也可以在手机上显示,只不过会做自适应,本文将给大家介绍了SpringBoot+layuimini实现左侧菜单动态展示的方法,需要的朋友可以参考下
    2024-04-04
  • 高并发下restTemplate的错误分析方式

    高并发下restTemplate的错误分析方式

    这篇文章主要介绍了高并发下restTemplate的错误分析方式,具有很好的参考价值,希望对大家有所帮助。如有错误或未考虑完全的地方,望不吝赐教
    2021-10-10
  • 基于binarywang封装的微信工具包生成二维码

    基于binarywang封装的微信工具包生成二维码

    这篇文章主要介绍了基于binarywang封装的微信工具包生成二维码,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友可以参考下
    2020-11-11
  • Java过滤所有特殊字符的案例

    Java过滤所有特殊字符的案例

    这篇文章主要介绍了Java过滤所有特殊字符的相关资料,包括java中清理所有特殊字符及java正则过滤特殊字符的方法,感兴趣的朋友跟随小编一起看看吧
    2024-02-02
  • Java FTP协议实现文件下载功能

    Java FTP协议实现文件下载功能

    FTP(File Transfer Protocol)就是文件传输协议。通过FTP客户端从远程FTP服务器上拷贝文件到本地计算机称为下载,将本地计算机上的文件复制到远程FTP服务器上称为上传,上传和下载是FTP最常用的两个功能
    2022-11-11
  • SpringBoot实现elasticsearch索引操作的代码示例

    SpringBoot实现elasticsearch索引操作的代码示例

    这篇文章主要给大家介绍了SpringBoot如何实现elasticsearch 索引操作,文中有详细的代码示例,感兴趣的同学可以参考阅读下
    2023-07-07
  • 什么是RESTful API,有什么作用

    什么是RESTful API,有什么作用

    提到RESTful API大家势必或多或少听说过,但是什么是RESTful API ?如何理解RESTful API 呢?今天咱们就来聊聊这个RESTful API
    2023-11-11

最新评论