> Java > java지도 시간 > 멀티 스레드 순차 실행, 두 가지 유형만 알고 계십니까?

멀티 스레드 순차 실행, 두 가지 유형만 알고 계십니까?

풀어 주다: 2023-08-24 15:42:48
앞으로
1301명이 탐색했습니다.

ㅋㅋㅋ

정상 작동입니다. 세 개의 스레드를 시작하고 실행하도록 하세요.

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

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();

    }

}

로그인 후 복사

실행 결과:

1

2

3

线程2

线程1

线程3

로그인 후 복사
세 스레드의 시작 메서드를 호출합니다. 분명히 순서대로 호출되지만 각 실행의 결과는 기본적으로 다르며 무작위성이 특히 강합니다.
무엇을 해야 할까요? 아래에서는 이를 달성하기 위해 네 가지 솔루션을 사용합니다.

옵션 1

Thread의 Join 메서드를 사용하여 스레드 순서 문제를 해결할 수 있습니다.

공식 소개:

이 스레드가 죽기를 기다립니다.

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

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

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

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

它结束的条件是:

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

下面大致看看器源码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

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方法来实现线程的顺序执行。

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

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

线程1

线程2

线程3

로그인 후 복사
로그인 후 복사

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

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

方案二

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

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

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

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

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

执行: 线程1

执行: 线程2

执行: 线程3

로그인 후 복사

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

方案三

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

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

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

线程1

线程2

线程3

로그인 후 복사
로그인 후 복사

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

方案四

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

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

  • 借助Future isDone轮询以判断任务是否执行结束,并获取结果。
  • Future isDone轮询以判断任务是否执行结束,并获取结果。
  • 借助Guava类库ListenableFutureFutureCallback。(netty也有类似的实现)
  • Java 8 CompletableFuture弥补了Java在异步编程方面的弱势。

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

    Java8新增的CompletableFuture

    借助구아바类库ListenableFutureFutureCallback 。(netty也有类似的实现)

    Java 8 CompletableFuture弥补了Java는 异步编程方face的弱势。🎜

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

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

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

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

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

    1

    2

    3

    4

    5

    6

    7

    8

    9

    10

    11

    12

    13

    14

    15

    16

    17

    18

    19

    20

    21

    22

    23

    24

    25

    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

    执行 : 线程1

    执行 : 线程2

    执行 : 线程3

    로그인 후 복사

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

    总结

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

    멀티 스레드 순차 실행, 두 가지 유형만 알고 계십니까?

    위 내용은 멀티 스레드 순차 실행, 두 가지 유형만 알고 계십니까?의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

    관련 라벨:
    본 웹사이트의 성명
    본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.
    인기 튜토리얼
    더>
    최신 다운로드
    더>
    웹 효과
    웹사이트 소스 코드
    웹사이트 자료
    프론트엔드 템플릿