Loading

如何保证线程的顺序执行

问题:

现在有T1、T2、T3三个线程,你怎样保证T2在T1执行完之后执行,T3在T2执行完之后执行?

方法1:使用join

使用Thread原生方法join,join方法是使所属的线程对象x正常执行run()方法中的任务,而当前线程进行无限的阻塞,等到线程x执行完成后再继续执行当前线程后面的代码。

public static void main(String[] args) {
        final Thread T1 = new Thread(new Runnable() {
            public void run() {
                System.out.println("T1 run");
            }
        });
        final Thread T2 = new Thread(new Runnable() {
            public void run() {
                System.out.println("T2 run");
                try{
                    T1.join();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("T2 run end");
            }
        });
        Thread T3 = new Thread(new Runnable() {
            public void run() {
                System.out.println("T3 run");
                try{
                    T2.join();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("T3 run end");
            }
        });
        T1.start();
        T2.start();
        T3.start();

    }

 

方法2:使用线程间通信的等待/通知机制

wait()方法是Object类的方法,该方法用来将当前线程置入“预执行队列”中,并且在wait()所在的代码行处停止执行,直到接到通知或被中断为止。

在调用wait之前,线程必须获取到该对象的对象级别锁,即只能在同步方法或同步块中调用wait()方法。在执行wait()方法后,当前线程释放锁。

private static boolean T2Run = false; //标识位,用来通知T2线程执行

    private static boolean T3Run = false;


    public static void main(String[] args) {

        Object lock1 = new Object();
        Object lock2 = new Object();

        Thread T1 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock1){
                    System.out.println("T1 run");
                    //t1 线程通知t2执行
                    T2Run = true;
                    lock1.notify();
                }
            }
        });
        Thread T2 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock1){
                    if(!T2Run){
                        System.out.println("T2 wait");
                        try {
                            lock1.wait();
                        } catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                    System.out.println("T2 run");
                    //t2 线程通知t3执行
                    synchronized (lock2){
                        T3Run = true;
                        lock2.notify();

                    }
                }
            }
        });

        Thread T3 = new Thread(new Runnable() {
            @Override
            public void run() {
                synchronized (lock2){
                    if (!T3Run){
                        System.out.println("T3 wait");
                        try {
                            lock2.wait();
                        } catch (Exception e){
                            e.printStackTrace();
                        }
                    }
                    System.out.println("T3 run");
                }
            }
        });
        T1.start();
        T2.start();
        T3.start();
    }

 

方法3:使用Conditon

关键字synchronized与wait和notify/notifyAll方法相结合可以实现等待/通知模式,类ReetrantLock也可以实现同样的功能,但需要借助于Condition对象。Condition可以实现多路通知,也就是在一个Lock对象里面可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择性的进行线程通知,在调度线程上更加灵活。在使用notify/notifyAll通知时,被通知的线程却是由JVM随机选择的。

/**
     * 使用condition
     */
    private Lock lock = new ReentrantLock();

    private Condition condition2 = lock.newCondition();

    private Condition condition3 = lock.newCondition();

    private static Boolean t2Run = false;
    private static Boolean t3Run = false;

    private void useCondition(){
        Thread T1 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock(); //获取锁
                System.out.println("T1 run");
                t2Run = true; //设置t2可以运行
                System.out.println("T1 run finish signal T2");
                condition2.signal(); //通知T2执行
                lock.unlock(); //解锁当前线程
                System.out.println("T1 unlock");
            }
        });
        Thread T2 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try{
                    if (!t2Run){
                        condition2.await(); //如果是false ,则等待
                    }
                    //若是true,则代表T2可以执行
                    System.out.println("T2 run");
                    t3Run = true;
                    condition3.signal();
                    System.out.println("T2 run finish signal T3");
                }catch (Exception e){
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }

            }
        });

        Thread T3 = new Thread(new Runnable() {
            @Override
            public void run() {
                lock.lock();
                try{
                    if (!t3Run){
                        condition3.await(); //如果是false ,则等待
                    }
                    //若是true,则代表T2可以执行
                    System.out.println("T3 run");
                }catch (Exception e){
                    e.printStackTrace();
                } finally {
                    lock.unlock();
                }
            }
        });
        T1.start();
        T2.start();
        T3.start();
    }

 

方法4:使用线程池

使用newSingleThreadExecutor线程池,由于核心线程数只有一个,所以能够顺序执行。

/**
     * 线程池
     * 核心线程数:1
     * 最大线程数:1
     * 在日常中不建议使用newSingleThreadExecutor,因为阻塞队列个数没有限制,会导致内存溢出
     *
     */
    static ExecutorService executorService = Executors.newSingleThreadExecutor();


    public static void main(String[] args) {
        Thread T1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T1 run");
            }
        });
        Thread T2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T2 run");
            }
        });
        Thread T3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T3 run");
            }
        });
        executorService.submit(T1);
        executorService.submit(T2);
        executorService.submit(T3);
        executorService.shutdown();
    }

 

方法5:使用线程的CountDownLatch

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

比较重要的方法:

CountDownLatch(int count); //构造方法,创建一个值为count 的计数器

await();//阻塞当前线程,将当前线程加入阻塞队列。

countDown();//对计数器进行递减1操作,当计数器递减至0时,当前线程会去唤醒阻塞队列里的所有线程。

/**
     * 计数器1 用于T1线程通知T2线程
     * 注意:这里个数都设置成立1 ,当T1执行完成后,执行countDown,来通知T2线程
     */
    static CountDownLatch countDownLatch1 = new CountDownLatch(1);

    /**
     * 计数器2 用于T2线程通知T3线程
     */
    static CountDownLatch countDownLatch2 = new CountDownLatch(1);

    public static void main(String[] args) {
        Thread T1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T1 run");
                countDownLatch1.countDown();
                System.out.println("T1 countDown finish");
            }
        });
        Thread T2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    countDownLatch1.await();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("T2 run");
                countDownLatch2.countDown();
            }
        });
        Thread T3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    countDownLatch2.await();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("T3 run");
            }
        });
    }

 

方法6:使用cyclicbarrier (多个线程互相等待,直到到达同一个同步点,再继续一起执行

比较重要的方法 

CyclicBarrier(int parties) //构造方法,参数表示拦截的线程的个数

CyclicBarrier(int parties, Runnable barrierAction) //也是构造方法,可以通过后面的参数,这是线程的优先级

await() //告诉CyclicBarrier自己已经到达同步点,然后当前线程被阻塞,当所有线程都到达同步点(barrier)时,唤醒所有的等待线程,一起往下继续运行,可根据参数barrierAction决定优先执行的线程

 

/**
     * 设置2个线程互相等待,直到到达同一个同步点,再继续一起执行。T1不执行完,T2就永远不会执行
     */
    static CyclicBarrier cyclicBarrier1 = new CyclicBarrier(2);

    /**
     * 设置2个线程互相等待,直到到达同一个同步点,再继续一起执行。
     */
    static CyclicBarrier cyclicBarrier2 = new CyclicBarrier(2);

    public static void main(String[] args) {
        Thread T1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T1 run");
                try{
                    cyclicBarrier1.await();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
        Thread T2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    cyclicBarrier1.await();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("T2 run");
                try{
                    cyclicBarrier2.await();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        });
        Thread T3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    cyclicBarrier2.await();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("T3 run");
            }
        });

        T1.start();;
        T2.start();
        T3.start();
    }

 

方法7:使用信号量 Semaphore

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

常用的方法:

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

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

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

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

release() //释放一个令牌,唤醒一个获取令牌不成功的阻塞线程。

/**
     * 设置信号量初始值为0 ,让T1 把信号量+1,这样,T2就可以执行了
     */
    static Semaphore semaphore1 = new Semaphore(0);

    static Semaphore semaphore2 = new Semaphore(0);

    public static void main(String[] args) {
        Thread T1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("T1 run");
                try{
                    semaphore1.release();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("T1 semaphore1 + 1");
            }
        });
        Thread T2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    semaphore1.acquire();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("T2 semaphore2 + 1");
                try{
                    semaphore2.release();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("T2 run");
            }
        });
        Thread T3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try{
                    semaphore2.acquire();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println("T3 run");
            }
        });

        T1.start();;
        T2.start();
        T3.start();
    }

 

 总结:共有7中方法

  1. 使用Thread原生方法join

  2. 使用线程间通信的等待/通知机制

  3. 使用Conditon

  4. 使用线程池

  5. 使用线程的CountDownLatch

  6. 使用cyclicbarrier (多个线程互相等待,直到到达同一个同步点,再继续一起执行
  7. 使用信号量 Semaphore

 

参考:

https://www.cnblogs.com/wenjunwei/p/10573289.html

 

posted @ 2020-10-31 15:18  冯廷鑫  阅读(3013)  评论(0编辑  收藏  举报