Rumah  >  Artikel  >  Java  >  Pelaksanaan berurutan berbilang benang, hanya tahu dua jenis?

Pelaksanaan berurutan berbilang benang, hanya tahu dua jenis?

Java后端技术全栈
Java后端技术全栈ke hadapan
2023-08-24 15:42:481213semak imbas


Cerita

Minggu lepas seorang rakan sekelas menghadapi soalan seperti itu dalam temu bual:

memastikan tiga utas, T2, T1 dan T

Operasi biasa, mulakan tiga utas dan biarkan ia dilaksanakan.

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

Hasil larian:

线程2
线程1
线程3

Panggil kaedah permulaan bagi tiga utas Jelas sekali mereka dipanggil mengikut urutan, tetapi keputusan setiap larian pada asasnya berbeza, dan rawak sangat kuat.

Apa yang perlu dilakukan? Di bawah ini kami menggunakan empat penyelesaian untuk mencapai ini.

Pilihan 1

Kita boleh menggunakan kaedah join dalam Thread untuk menyelesaikan masalah pesanan benang.

Pengenalan rasmi:

Tunggu thread ini mati.

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

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问世前想要实现任务的回调,一般有以下两种方式:

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

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

    Java8新增的CompletableFuture

    借助Jam batu类库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());
            }
        }
    }

    运行结果:

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

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

    总结

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

    Pelaksanaan berurutan berbilang benang, hanya tahu dua jenis?

    Atas ialah kandungan terperinci Pelaksanaan berurutan berbilang benang, hanya tahu dua jenis?. Untuk maklumat lanjut, sila ikut artikel berkaitan lain di laman web China PHP!

    Kenyataan:
    Artikel ini dikembalikan pada:Java后端技术全栈. Jika ada pelanggaran, sila hubungi admin@php.cn Padam