Java多线程之以7种方式让主线程等待子线程结束

一、while循环
对于“主线程如何获取子线程总运行时间”的问题,最开始想到的是使用while循环进行轮询:

Thread t = new Thread(() -> {
    //子线程进行字符串连接操作
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over");
});
//开始计时
long start = System.currentTimeMillis();
System.out.println("start = " + start);
t.start();
long end = 0;
while(t.isAlive() == true){
    end = System.currentTimeMillis();
    try {
        Thread.sleep(10);
    }catch (InterruptedException e){
        e.printStackTrace();
    }
}
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));

二、Thread的join()方法
使用join()方法,join()方法的作用,是等待这个线程结束;(t.join()方法阻塞调用此方法的线程(calling thread),直到线程t完成,此线程再继续

long start = System.currentTimeMillis();
System.out.println("start = " + start);
t1.start();
try {
    t.join();//注意这里
} catch (InterruptedException e) {
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - Start:" + (end - start));

三、synchronized的等待唤醒机制

Object lock = new Object();
Thread t = new Thread(() -> {
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over");
    synchronized (lock) {//获取对象锁
        lock.notify();//子线程唤醒
    }
});
//计时
long start = System.currentTimeMillis();
System.out.println("start = " + start);
//启动子线程
t.start();
try {
    synchronized (lock) {//这里也是一样
        lock.wait();//主线程等待
    }
} catch (InterruptedException e) {
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));

四、CountDownLatch
使用java.util.concurrent包下的CountDownLatch类,简单来说,CountDownLatch类是一个计数器,可以设置初始线程数(设置后不能改变),在子线程结束时调用countDown()方法可以使线程数减一,最终为0的时候,调用CountDownLatch的成员方法wait()的线程就会取消BLOKED阻塞状态,进入RUNNABLE从而继续执行

int threadNumber = 1;
final CountDownLatch cdl = new CountDownLatch(threadNumber);//参数为线程个数

Thread t = new Thread(() -> {
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over");
    cdl.countDown();//此方法是CountDownLatch的线程数-1
});

long start = System.currentTimeMillis();
System.out.println("start = " + start);
t.start();
//线程启动后调用countDownLatch方法
try {
    cdl.await();//需要捕获异常,当其中线程数为0时这里才会继续运行
}catch (InterruptedException e){
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));

五、Future
线程池的submit()的返回对象Future接口有一个get()方法也可以阻塞当前线程(其实该方法主要用途是获取子线程的返回值)

ExecutorService executorService = Executors.newFixedThreadPool(1);

Thread t = new Thread(() -> {
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over");
});
long start = System.currentTimeMillis();
System.out.println("start = " + start);
Future future = executorService.submit(t);//子线程启动
try {
    future.get();//需要捕获两种异常
}catch (InterruptedException e){
    e.printStackTrace();
}catch (ExecutionException e){
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));
executorService.shutdown();

这里, ThreadPoolExecutor 是实现了 ExecutorService的方法, sumbit的过程就是把一个Runnable接口对象包装成一个 Callable接口对象, 然后放到 workQueue里等待调度执行. 当然, 执行的启动也是调用了thread的start来做到的, 只不过这里被包装掉了. 另外, 这里的thread是会被重复利用的, 所以这里要退出主线程, 需要执行以下shutdown方法以示退出使用线程池.

六、BlockingQueue
在concurrent包中,还提供了BlockingQueue(队列)来操作线程,BlockingQueue的主要的用法是在线程间安全有效的传递数据

BlockingQueue queue = new ArrayBlockingQueue(1);//数组型队列,长度为1
Thread t = new Thread(() -> {
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over");
    try {
        queue.put("OK");//在队列中加入数据
    } catch (InterruptedException e) {
        e.printStackTrace();
    }
});
long start = System.currentTimeMillis();
System.out.println("start = " + start);
t.start();
try {
    queue.take();//主线程在队列中获取数据,take()方法会阻塞队列,ps还有不会阻塞的方法
} catch (InterruptedException e) {
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));

七、CyclicBarrier
CyclicBarrier字面意思回环栅栏,通过它可以实现让一组线程等待至某个状态之后再全部同时执行。叫做回环是因为当所有等待线程都被释放以后,CyclicBarrier可以被重用。

CyclicBarrier barrier = new CyclicBarrier(2);//参数为线程数
Thread t = new Thread(() -> {
    int num = 1000;
    String s = "";
    for (int i = 0; i < num; i++) {
        s += "Java";
    }
    System.out.println("t Over");
    try {
        barrier.await();//阻塞
    } catch (InterruptedException e) {
        e.printStackTrace();
    } catch (BrokenBarrierException e) {
        e.printStackTrace();
    }
});
long start = System.currentTimeMillis();
System.out.println("start = " + start);
t.start();
try {
    barrier.await();//也阻塞,并且当阻塞数量达到指定数目时同时释放
} catch (InterruptedException e) {
    e.printStackTrace();
} catch (BrokenBarrierException e) {
    e.printStackTrace();
}
long end = System.currentTimeMillis();
System.out.println("end = " + end);
System.out.println("end - start = " + (end - start));

实际是上面这种方法是不太严谨的,因为在子线程阻塞之后如果还有代码是会继续执行的,当然本例中后面是没有代码可执行了,可以近似理解为是子线程的运行时间。

来源:https://www.cnblogs.com/lixin-link/p/10998058.html

多线程

我来吐槽

*

*