Maison > Java > javaDidacticiel > le corps du texte

Il existe quatre options pour assurer l'exécution séquentielle du multi-thread. Combien en connaissez-vous ?

Libérer: 2023-08-17 16:45:54
avant
1573 Les gens l'ont consulté


Histoire

La semaine dernière, un camarade de classe a rencontré une telle question lors de l'entretien :

Il y a trois fils T1, T2 et T3 Comment assurer une exécution séquentielle ?

Fonctionnement normal, démarrez trois threads et laissez-les s'exécuter.

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();
    }
}
Copier après la connexion

Résultats d'exécution :

线程2
线程1
线程3
Copier après la connexion

Appelez les méthodes de démarrage des trois threads. Évidemment, elles sont appelées dans l'ordre, mais les résultats de chaque exécution sont fondamentalement différents et le caractère aléatoire est particulièrement fort.

Que faire ? Ci-dessous, nous utilisons quatre solutions pour y parvenir.

Option 1

Nous pouvons utiliser la méthode join dans Thread pour résoudre le problème de l'ordre des threads. Présentons brièvement la méthode join.

Introduction officielle :

Attend que ce fil meure.

Attend que ce fil se termine, ce qui signifie que le fil en cours attend la fin de ce fil avant de continuer l'exécution.

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;
        }
   }
}
Copier après la connexion

下面我们使用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();
    }
}
Copier après la connexion

运行结果:

线程1
线程2
线程3
Copier après la connexion
Copier après la connexion

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

下面我们来看看另外一种方案: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();
            }
        }
    }
}
Copier après la connexion

运行结果:

执行: 线程1
执行: 线程2
执行: 线程3
Copier après la connexion

关于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();
    }
}
Copier après la connexion

运行结果:

线程1
线程2
线程3
Copier après la connexion
Copier après la connexion

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

方案四

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

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

  • 借助Future isDone轮询以判断任务是否执行结束,并获取结果。
  • 借助Guava类库ListenableFutureFutureCallback。(netty也有类似的实现)

Java 8 CompletableFuture弥补了Java在异步编程方面的弱势。

在Java中异步编程,不一定非要使用rxJava,Java本身的库中的CompletableFuture可以很好的应对大部分的场景。

Java8新增的CompletableFuture则借鉴了Netty等对Future的改造,简化了异步编程的复杂性,并且提供了函数式编程的能力 。

使用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());
        }
    }
}
Copier après la connexion

运行结果:

执行 : 线程1
执行 : 线程2
执行 : 线程3
Copier après la connexion

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

总结

关于多个线程顺序执行,不管是对于面试,还是工作,关于多线程顺序执行的解决方案都是非常有必要掌握的。:

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Étiquettes associées:
source:Java后端技术全栈
Déclaration de ce site Web
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn
Tutoriels populaires
Plus>
Derniers téléchargements
Plus>
effets Web
Code source du site Web
Matériel du site Web
Modèle frontal