多线程之Runnable、Callable和Future的详解和区别

Runnable、Callable和Future的详解和区别

在程序运行中,执行的时间和用户的体验是密切相关的,人们不希望用一个经常卡顿的网站或应用,这时候多线程能给程序带来质的提升。

一、Runnable

简介
Runnable接口只有一个抽象的run()方法,此方法是在Thread.start()的时候由JVM调用run方法,创建一个线程,并调用run方法。
例子

public class RunnableTest {
    public static void main(String[] args) {
       Runnable runnable = () -> {
            try {
                System.out.println("thread1"+Thread.currentThread().getName());
                Thread.sleep(5000);
                System.out.println("处理完成");
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        };
        System.out.println("thread2"+Thread.currentThread().getName());
        new Thread(runnable).start();
        System.out.println("thread3"+Thread.currentThread().getName());
    }
}

运行结果

thread2main
thread3main
thread1Thread-0
处理完成

结论
主线程不会等待run方法执行完成,而是直接执行完成。

二、Callable和Future

简介
Callable和Future,它俩很有意思的,一个产生结果,一个拿到结果。
Callable接口类似于Runnable,从名字就可以看出来了,但是Runnable不会返回结果,并且无法抛出返回结果的异常,而Callable功能更强大一些,被线程执行后,可以返回值,这个返回值可以被Future拿到,也就是说,Future可以拿到异步执行任务的返回值。

例子

public class CallableTest {

    public static void main(String[] args) throws Exception {
        Callable<Integer> callable2 = new Callable<Integer>() {
            @Override
            public Integer call() throws Exception {
                System.out.println("Thread1"+Thread.currentThread().getName());
                Thread.sleep(1000);
                return  new Random().nextInt(100);
            }
        };
        FutureTask<Integer> futureTask = new FutureTask<Integer>(callable2);
        System.out.println("Thread2"+Thread.currentThread().getName());
        new Thread(futureTask).start();
        System.out.println(futureTask.get());
        System.out.println("Thread2"+Thread.currentThread().getName());
    }
}

运行结果

Thread2main
Thread1Thread-0
17
Thread2main

结论
调用futureTask的get方法会让主线程等待子线程拿到返回值再去执行下一步。但是一般不会立刻调用FutureTask的get方法,而是主线程处理其他操作,需要用到返回值再去调用。

注意
到这里可能有人会说了,调用FutureTask的get方法会让主线程等待,那不是还不如Runnable接口好?

其实这里只是夸张,把子线程计算时间设置大了点。

假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再通过FutureTask的get方法得到。

三、实战:Spring整合线程池,给Callable传参,并获取返回值

public class ApplicationTests {

	@Resource(name = "taskExecutor")
	Executor taskExecutor;

	@Test
	public void contextLoads() throws ExecutionException, InterruptedException {
		test();
	}
	public void test() throws ExecutionException, InterruptedException {
		List<String> list = new ArrayList<>();
		List<Future<String>> results = new ArrayList<>();

		list.add("aaa");
		list.add("bbb");
		list.add("ccc");
		list.add("ddd");
		list.stream().forEach(item->{
			System.out.println("主线程参数:" + item);
			ThreadPoolTaskExecutor threadPoolTaskExecutor =(ThreadPoolTaskExecutor) taskExecutor;
			//submit的时候,callable内部的call方法已经在开始计算了。
			results.add(threadPoolTaskExecutor.submit(new ApplicationTests.CallableTest(item)));
		});
		System.out.println("主线程执行其他任务。。。花费3秒");
		Thread.sleep(3000);
		System.out.println("主线程执行其他任务完毕。。。开始使用返回值");
	}
	class CallableTest implements Callable<String> {
		private String stringArg;
		
		public CallableTest(String item) {
			System.out.println("【主线程给Callable接口的call方法传参】:"+item+",当前线程:"+Thread.currentThread().getName());
			this.stringArg = item;
		}
		
		@Override
		public String call() throws Exception {
			String returnStr = "Hello: "+stringArg;
			System.out.println("【Callable接口的call,返回值】"+returnStr+"当前线程:"+Thread.currentThread().getName());
			return returnStr;
		}
	}
}

运行结果

主线程参数:aaa
【主线程给Callable接口的call方法传参】:aaa,当前线程:main
主线程参数:bbb
【主线程给Callable接口的call方法传参】:bbb,当前线程:main
主线程参数:ccc
【主线程给Callable接口的call方法传参】:ccc,当前线程:main
主线程参数:ddd
【主线程给Callable接口的call方法传参】:ddd,当前线程:main

主线程执行其他任务。。。花费3秒
【Callable接口的call,返回值】Hello: aaa当前线程:Executor-1
【Callable接口的call,返回值】Hello: bbb当前线程:Executor-1
【Callable接口的call,返回值】Hello: ccc当前线程:Executor-1
【Callable接口的call,返回值】Hello: ddd当前线程:Executor-1
主线程执行其他任务完毕。。。开始使用返回值

结论
子线程的运行并不会被主线程阻塞

假设有一个很耗时的返回值需要计算,并且这个返回值不是立刻需要的话,那么就可以使用这个组合,用另一个线程去计算返回值,而当前线程在使用这个返回值之前可以做其它的操作,等到需要这个返回值时,再使用它。

注意
如果子线程Callable接口中的call方法执行时间比主线程长,如果主线程要调用Future的get方法获取返回值,那么主线程会等待子线程计算出值。

四、线程池配置

1、application.properties

#线程池配置
threadpool.corePoolSize=10
threadpool.keepAliveSeconds=10
threadpool.maxPoolSize=30
threadpool.queueCapacity=2000

2、java配置代码

@Configuration
@EnableAsync
@EnableScheduling
public class AsyncConfiguration implements AsyncConfigurer {

    private final Logger log = LoggerFactory.getLogger(AsyncConfiguration.class);

    @Value("${threadpool.corePoolSize}")
    private int corePoolSize;
    @Value("${threadpool.keepAliveSeconds}")
    private int keepAliveSeconds;
    @Value("${threadpool.maxPoolSize}")
    private int maxPoolSize;
    @Value("${threadpool.queueCapacity}")
    private int queueCapacity;

    @Override
    @Bean(name = "taskExecutor")
    public Executor getAsyncExecutor() {
        System.setProperty("rocketmq.client.log.loadconfig","false");
        log.debug("Creating Async Task Executor");
        ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();
        executor.setMaxPoolSize(corePoolSize);
        executor.setKeepAliveSeconds(keepAliveSeconds);
        executor.setKeepAliveSeconds(maxPoolSize);
        executor.setQueueCapacity(queueCapacity);
        executor.setThreadNamePrefix("Executor-");
        return  executor;
    }

    @Override
    public AsyncUncaughtExceptionHandler getAsyncUncaughtExceptionHandler() {
        return new SimpleAsyncUncaughtExceptionHandler();
    }

}
  • 6
    点赞
  • 13
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值