主线程等待子线程执行完成的三种方法介绍

举个场景,用户表t_user根据userid做了分库分表,数据散列在了三个库表里t_user1、t_user2、t_user3,此时的count(t_user)需要count(t_user1) + count(t_user2) + count(t_user3)。我们为了提高性能,减少count(t_user)的响应时长,可以通过多线程并行执行各个子count,然后在主线程里将各个子任务的结果集进行合并。
如何让主线程等待子线程执行完成,本文介绍三种方式,并分析利弊。


1.thread.join()


thread.join()的作用是让当前线程(调用该方法所在的线程)阻塞住直到该thread线程终止。

1.不使用线程池
package com.fanfan.concurrent.threadjoin;

/**
 * Created by fanfan on 2017/3/19.
 */
public class UserServiceWithNoThreadPool {

    public int count() throws Exception {
        int[] counts = {0, 0, 0};
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                counts[0] = count1();
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                counts[1] = count2();
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                counts[2] = count3();
            }
        });
        t1.start();
        t2.start();
        t3.start();

        t1.join();
        t2.join();
        t3.join();

        return counts[0] + counts[1] + counts[2];
    }

    private int count1() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 10;
    }

    private int count2() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 100;
    }

    private int count3() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 1000;
    }

    public static void main(String[] args) throws Exception {
        UserServiceWithNoThreadPool userService = new UserServiceWithNoThreadPool();
        System.out.println(userService.count());
    }
}
2.使用线程池

在使用线程池的时候,我们就不能直接操作线程对象了,也就拿不到线程的句柄。另外,线程池里的线程处理完任务之后不是立即终止而是阻塞等待其它任务的执行,而thread.join()是要求线程终止,所以本质上thread.join()不适合应用在使用线程池的情况。


2.CountDownLatch


调用thread.join()方法必须等thread执行完毕,当前线程才能继续往下执行,虽然能够达到我们的目的,但是,还不是特别灵活。比如,在使用线程池的情况下,就无法使用。
JDK1.5开始提供了CountDownLatch这个类,通过计数器提供了更加灵活的控制,只要检测到计数器为0,当前线程就可以往下执行而不用管相应的thread是否执行完毕。
CountDownLatch的原理是这样的:比如,我们创建一个计数器为3的实例,让各个线程任务持有这个CountDownLatch实例,当每个任务完成自己的工作后,调用countDownLatch.countDown()方法将计数器减1。countDownLatch.await()方法会一直阻塞直到计数器为0,主线程才会继续往下执行。

1.不使用线程池
package com.fanfan.concurrent.countdownlatch;

import java.util.concurrent.CountDownLatch;

/**
 * Created by fanfan on 2017/3/19.
 */
public class UserServiceWithNoThreadPool {

    public int count() throws Exception {

        final CountDownLatch latch = new CountDownLatch(3);

        int[] counts = {0, 0, 0};
        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                counts[0] = count1();
                latch.countDown();
            }
        });
        Thread t2 = new Thread(new Runnable() {
            @Override
            public void run() {
                counts[1] = count2();
                latch.countDown();
            }
        });
        Thread t3 = new Thread(new Runnable() {
            @Override
            public void run() {
                counts[2] = count3();
                latch.countDown();
            }
        });
        t1.start();
        t2.start();
        t3.start();
        latch.await();
        return counts[0] + counts[1] + counts[2];
    }

    private int count1() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 10;
    }

    private int count2() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 100;
    }

    private int count3() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 1000;
    }

    public static void main(String[] args) throws Exception {
        UserServiceWithNoThreadPool userService = new UserServiceWithNoThreadPool();
        System.out.println(userService.count());
    }
}
2.使用线程池

package com.fanfan.concurrent.countdownlatch;

import java.util.concurrent.*;

/**
 * Created by fanfan on 2017/3/19.
 */
public class UserServiceWithThreadPool {

    private ExecutorService executorService;

    public int count() throws Exception {

        final CountDownLatch latch = new CountDownLatch(3);

        int[] counts = {0, 0, 0};
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                counts[0] = count1();
                latch.countDown();
            }
        });
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                counts[1] = count2();
                latch.countDown();
            }
        });
        executorService.submit(new Runnable() {
            @Override
            public void run() {
                counts[2] = count3();
                latch.countDown();
            }
        });
        latch.await();
        return counts[0] + counts[1] + counts[2];
    }

    private int count1() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 10;
    }

    private int count2() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 100;
    }

    private int count3() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 1000;
    }

    public ExecutorService getExecutorService() {
        return executorService;
    }

    public void setExecutorService(ExecutorService executorService) {
        this.executorService = executorService;
    }

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        com.fanfan.concurrent.callable.UserServiceWithThreadPool userService = new com.fanfan.concurrent.callable.UserServiceWithThreadPool();
        userService.setExecutorService(executorService);
        System.out.println(userService.count());
        executorService.shutdown();
    }
}


3.Feture、FetureTask和Callable


上面两种方法都有一个缺陷:在任务执行完毕之后无法获取执行结果,如果需要获取执行结果,就必须通过共享变量或者使用线程通信的方式来达到效果,使用起来比较麻烦。
而自JDK1.5开始,就提供了Future和Callable,通过它们可以在任务执行完毕之后得到任务执行结果。

1.不使用线程池
package com.fanfan.concurrent.callable;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

/**
 * Created by fanfan on 2017/3/19.
 */
public class UserServiceWithNoThreadPool {

    public int count() throws Exception {
        FutureTask<Integer> futureTask1 = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return count1();
            }
        });
        FutureTask<Integer> futureTask2 = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return count2();
            }
        });
        FutureTask<Integer> futureTask3 = new FutureTask<Integer>(new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                return count3();
            }
        });
        new Thread(futureTask1).start();
        new Thread(futureTask2).start();
        new Thread(futureTask3).start();
        return futureTask1.get() + futureTask2.get() + futureTask3.get();
    }

    private int count1() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 10;
    }

    private int count2() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 100;
    }

    private int count3() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 1000;
    }

    public static void main(String[] args) throws Exception {
        UserServiceWithNoThreadPool userService = new UserServiceWithNoThreadPool();
        System.out.println(userService.count());
    }
}
2.使用线程池
package com.fanfan.concurrent.callable;

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

/**
 * Created by fanfan on 2017/3/19.
 */
public class UserServiceWithThreadPool {

    private ExecutorService executorService;

    public int count() throws Exception {
        Future<Integer> future1 = executorService.submit(new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                return count1();
            }
        });
        Future<Integer> future2 = executorService.submit(new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                return count2();
            }
        });
        Future<Integer> future3 = executorService.submit(new Callable<Integer>() {

            @Override
            public Integer call() throws Exception {
                return count3();
            }
        });
        return future1.get() + future2.get() + future3.get();
    }

    private int count1() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 10;
    }

    private int count2() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 100;
    }

    private int count3() {
        try {Thread.sleep(3000L);} catch (InterruptedException e) {e.printStackTrace();}
        return 1000;
    }

    public ExecutorService getExecutorService() {
        return executorService;
    }

    public void setExecutorService(ExecutorService executorService) {
        this.executorService = executorService;
    }

    public static void main(String[] args) throws Exception {
        ExecutorService executorService = Executors.newFixedThreadPool(3);
        UserServiceWithThreadPool userService = new UserServiceWithThreadPool();
        userService.setExecutorService(executorService);
        System.out.println(userService.count());
        executorService.shutdown();
    }
}


总结

  1. 多线程的场景里,我们尽可能的使用线程池,而不要直接new Thread()。因为直接new Thread()除了创建线程耗时之外,更严重的问题是会造成线程数量不可控,成百上千的线程甚至更高,很容易占满内存,占满CPU。
  2. thread.join必须等待子线程执行完毕,主线程才能继续,灵活性不足,不能在线程池场景里面使用。
  3. CountDownLatch是JDK1.5开始提供的,弥补了thread.join的不足,使用起来更加灵活。可以适应于一些更加灵活的场合,比如不要求线程任务全部执行完毕,而只是在线程任务执行到某个阶段,主线程就停止阻塞继续向下执行。
  4. 为了方便获取子线程的执行结果,JDK1.5开始,提供了Future和Callable。如果遇到此类场景,优先使用Future和Callable。

  • 2
    点赞
  • 9
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值