欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

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

程序员文章站 2022-03-23 22:08:42
Runnable、Callable和Future的详解和区别在程序运行中,执行的时间和用户的体验是密切相关的,人们不希望用一个经常卡顿的网站或应用,这时候多线程能给程序带来质的提升。一、Runnable简介Runnable接口只有一个抽象的run()方法,此方法是在Thread.start()的时候由JVM调用run方法,创建一个线程,并调用run方法。例子public class RunnableTest { public static void main(String[] arg...

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

}

本文地址:https://blog.csdn.net/qq_40205337/article/details/109959474

相关标签: IO java