Home  >  Article  >  Java  >  Multi-threaded sequential execution, only know two types?

Multi-threaded sequential execution, only know two types?

Java后端技术全栈
Java后端技术全栈forward
2023-08-24 15:42:481165browse


Story

上A classmate Zhou encountered this question during an interview:

There are three threads T1, T2, and T3. How to ensure sequential execution?

Routine operation, start three threads and let them execute.

public class ThreadDemo {
    public static void main(String[] args) {

        final Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1");
            }
        });

        final Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2");
            }
        });

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程3");
            }
        });
        t1.start();
        t2.start();
        t3.start();
    }
}

Running results:

线程2
线程1
线程3

Calling the start method of the three threads is obviously called in order, but the results of each run are basically different, and the randomness is particularly special powerful.

How to do it? Below we use four solutions to achieve this.

Option 1

We can use the join method in Thread to solve the thread order problem. Let’s briefly introduce it below Let’s look at the join method.

Official introduction:

Waits for this thread to die.

等待这个线程结束,也就是说当前线程等待这个线程结束后再继续执行 。

join()方法是Thread中的一个public方法,它有几个重载版本:

  • join()
  • join(long millis)   //参数为毫秒
  • join(long millis,int nanoseconds)  //第一参数为毫秒,第二个参数为纳秒

join()方法实际是利用了wait()方法(wait方法是Object中的),只不过它不用等待notify()/notifyAll(),且不受其影响。

它结束的条件是:

  • 等待时间到
  • 目标线程已经run完(通过isAlive()方法来判断)

下面大致看看器源码:

public final void join() throws InterruptedException {
    //调用了另外一个有参数的join方法
    join(0);
}
public final synchronized void join(long millis) throws InterruptedException {
    long base = System.currentTimeMillis();
    long now = 0;
    if (millis < 0) {
        throw new IllegalArgumentException("timeout value is negative");
    }
    //0则需要一直等到目标线程run完
    if (millis == 0) {
        // 如果被调用join方法的线程是alive状态,则调用join的方法
        while (isAlive()) {
            // == this.wait(0),注意这里释放的是
            //「被调用」join方法的线程对象的锁
            wait(0);
        }
    } else {
         // 如果目标线程未run完且阻塞时间未到,
        //那么调用线程会一直等待。
        while (isAlive()) {
            long delay = millis - now;
            if (delay <= 0) {
                break;
            }
            //每次最多等待delay毫秒时间后继续争抢对象锁,获取锁后继续从这里开始的下一行执行,
            //也可能提前被notify() /notifyAll()唤醒,造成delay未一次性消耗完,
            //会继续执行while继续wait(剩下的delay)
            wait(delay);
            // 这个变量now起的不太好,叫elapsedMillis就容易理解了
            now = System.currentTimeMillis() - base;
        }
   }
}

下面我们使用join方法来实现线程的顺序执行。

public class ThreadDemo {
    public static void main(String[] args) {

        final Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1");
            }
        });

        final Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //等待线程t1执行完成后
                    //本线程t2 再执行
                    t1.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程2");
            }
        });

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    //等待线程t2执行完成后
                    //本线程t3 再执行
                    t2.join();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                System.out.println("线程3");
            }
        });
        t3.start();
        t2.start();
        t1.start();
    }
}

运行结果:

线程1
线程2
线程3

不管你运行多少次上面这段代码,结果始终不变,所以,我们就解决了多个线程按照顺序执行的问题了。

下面我们来看看另外一种方案:CountDownLatch

方案二

我们先来说一下CountDownLatch,然后再来使用CountDownLatch是怎么解决多个线程顺序执行的。

CountDownLatch是一种同步辅助,在AQS基础之上实现的一个并发工具类,让我们多个线程执行任务时,需要等待线程执行完成后,才能执行下面的语句,之前线程操作时是使用 Thread.join方法进行等待 。

CountDownLatch能够使一个线程在等待另外一些线程完成各自工作之后,再继续执行。它相当于是一个计数器,这个计数器的初始值就是线程的数量,每当一个任务完成后,计数器的值就会减一,当计数器的值为 0 时,表示所有的线程都已经任务了,然后在 CountDownLatch 上等待的线程就可以恢复执行接下来的任务。

下面我们就用CountDownLatch来实现多个线程顺序执行:

import java.util.concurrent.CountDownLatch;

/**
 *  公众号:面试专栏
 * @author 小蒋学
 *  CountDownLatch 实现多个线程顺序执行
 */
public class ThreadDemo {
    public static void main(String[] args) {

        CountDownLatch countDownLatch1 = new CountDownLatch(0);

        CountDownLatch countDownLatch2 = new CountDownLatch(1);

        CountDownLatch countDownLatch3 = new CountDownLatch(1);


        Thread t1 = new Thread(new Work(countDownLatch1, countDownLatch2),"线程1");
        Thread t2 = new Thread(new Work(countDownLatch2, countDownLatch3),"线程2");
        Thread t3 = new Thread(new Work(countDownLatch3, countDownLatch3),"线程3");

        t1.start();
        t2.start();
        t3.start();
    }

    static class Work implements Runnable {
        CountDownLatch cOne;
        CountDownLatch cTwo;

        public Work(CountDownLatch cOne, CountDownLatch cTwo) {
            super();
            this.cOne = cOne;
            this.cTwo = cTwo;
        }

        @Override
        public void run() {
            try {
                cOne.await();
                System.out.println("执行: " + Thread.currentThread().getName());
            } catch (InterruptedException e) {
                e.printStackTrace();
            }finally {
                cTwo.countDown();
            }
        }
    }
}

运行结果:

执行: 线程1
执行: 线程2
执行: 线程3

关于CountDownLatch实现多个线程顺序执行就这样实现了,下面我们再用线程池来实现。

方案三

在Executors 类中有个单线程池的创建方式,下面我们就用单线程池的方式来实现多个线程顺序执行。

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 *  公众号:面试专栏
 * @author 小蒋学
 *  CountDownLatch 实现多个线程顺序执行
 */
public class ThreadDemo {
    public static void main(String[] args) {
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程1");
            }
        },"线程1");

        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程2");
            }
        },"线程2");

        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("线程3");
            }
        });

        ExecutorService executor = Executors.newSingleThreadExecutor();
        // 将线程依次加入到线程池中
        executor.submit(t1);
        executor.submit(t2);
        executor.submit(t3);
        // 及时将线程池关闭
        executor.shutdown();
    }
}

运行结果:

线程1
线程2
线程3

这样我们利用单线程池也实现了多个线程顺序执行的问题。下面再来说一种更牛的方案。

方案四

最后一种方案是使用CompletableFuture来实现多个线程顺序执行。

在Java 8问世前想要实现任务的回调,一般有以下两种方式:

  • Use Future isDonepolling to determine whether the task execution is completed and obtain the result.
  • With the help of Guava class libraryListenableFuture, FutureCallback. (netty also has a similar implementation)

Java 8 CompletableFuture makes up for Java's weakness in asynchronous programming.

For asynchronous programming in Java, you do not have to use rxJava. CompletableFuture in Java's own library can handle most scenarios very well.

Java8The new CompletableFuture draws on the transformation of Future by Netty and others, simplifying the complexity of asynchronous programming and providing functional programming Ability.

使用Future获得异步执行结果时,要么调用阻塞方法get(),要么轮询看isDone()是否为true,这两种方法都不是很好,因为主线程也会被迫等待。

从Java 8开始引入了CompletableFuture,它针对Future做了改进,可以传入回调对象,当异步任务完成或者发生异常时,自动调用回调对象的回调方法。

接下来我们就使用CompletableFuture来实现多个线程顺序执行。

import java.util.concurrent.CompletableFuture;

/**
 *  公众号:面试专栏
 * @author 小蒋学
 *  CountDownLatch 实现多个线程顺序执行
 */
public class ThreadDemo {
    public static void main(String[] args)  {
        Thread t1 = new Thread(new Work(),"线程1");
        Thread t2 = new Thread(new Work(),"线程2");
        Thread t3 = new Thread(new Work(),"线程3");

        CompletableFuture.runAsync(()-> t1.start())
                .thenRun(()->t2.start())
                .thenRun(()->t3.start());
    }

    static class Work implements Runnable{
        @Override
        public void run() {
            System.out.println("执行 : " + Thread.currentThread().getName());
        }
    }
}

运行结果:

执行 : 线程1
执行 : 线程2
执行 : 线程3

到此,我们就使用CompletableFuture实现了多个线程顺序执行的问题。

总结

关于多个线程顺序执行,不管是对于面试,还是工作,关于多线程顺序执行的解决方案都是非常有必要掌握的。也希望下次面试官再问:多线程顺序执行问题的时候,你的表情应该是这样的:

Multi-threaded sequential execution, only know two types?

The above is the detailed content of Multi-threaded sequential execution, only know two types?. For more information, please follow other related articles on the PHP Chinese website!

Statement:
This article is reproduced at:Java后端技术全栈. If there is any infringement, please contact admin@php.cn delete