①创建一个类继承Thread,然后重写run方法,(注意:void没有返回值)
运行结果:
②实现Runnable接口,然后重写run方法,(注意:void没有返回值)
运行结果:
这里我要多插几句话,一般我们都会使用java8提供的lamda表达式去使用Runnable接口的方式去创建线程,我先把代码写下来,然后我会在另一篇博客内容写为什么Runnable接口就可以这么写,以及什么情况下可以这样写。
运行结果:
③实现Callable接口,然后重写call方法,(注意:T泛型,有返回值)
运行结果:
结果也标明,确实开始时间是2023-11-17 03:56:20,结束时间是2023-11-17 03:56:30
证明确实是10s之后才打印,所以得出结论:FutureTask实现类的get()方法(严谨来说是Future接口的get(),因为FutureTask implements Future接口)是阻塞的。
④通过Executors.newXXXThreadPool();
总共有四种线程池
-FixedThreadPool 一池固定线程n'数量
-SingleThreadExecutor 一池单线程
-CachedThreadPool 一池不固定线程数量
-ScheduledThreadPool 定时
这几个线程池我也会在另一篇博客上面详说,这里就先知道一下,混个眼熟。
⑤自定义线程池,new ThreadPoolExecutor(...参数),然后将任务提交调用.submit()或者.execute()
这里ThreadPoolExecutor的有参构造器总共有3个,如下:
这里是6个参数,缺少了RejectedExecutionHandler参数,但是该构造器最终还是会调用7个参数的构造器,只是把RejectedExecutionHandler传了默认的handler。
这里也是6个参数缺少了ThreadFactory参数,但是该构造器最终还是会调用7个参数的构造器,只是把ThreadFactory传了默认的defaultFactory。
最后一个就是7个参数的了。
那么这些参数到底代表什么含义呢?在另外一篇博客,我也会详说的。
这时候我们选择5个参数的自定义线程池。
运行结果:
所以最终创建线程的几种方式总共就这五种,现在我要说一下第三种实现Callable接口刚刚留下的问题了。为什么必须得请出FutureTask呢?
首先我们看一下Thread的构造器:我们可以通过在IDEA中使用快捷键CRTL+SHIFT+N搜索,输入Thread,我们会发现,看了一圈下来,Thread的构造器都是接收的Runnable接口对象,没有能直接接收Callable接口对象的。所以这时候我们就不能直接传入Callable接口对象了。那么我们还是从Runnable接口对象入手,看看Callable接口和Runnable接口是否存在父子继承关系,如果是Callable接口继承Runnable接口的话,那么我们也可以直接传入Callable接口对象。但是很遗憾,Callable和Runnable接口并不是这种关系,所以我们还是只能从Runnable的子接口入手,我们发现Runnable接口有一个子接口叫做Future接口,那么我们就可以直接往Thread(Runnable runnable)传入Future接口对象,而Future接口的实现类有现成的了,叫做FutureTask<T>,那么我们是不是可以直接这样子:
FutureTask futureTask = new FutureTask();
然后将future对象放到Thread(Runnable runnable)参数中。但是即使这样和Callable接口还是没产生关联啊,我们再看一下,这个FutureTask,它的构造器有一个可以接收Callable接口对象的。诺:
看到没有,所以我们是不是可以直接这样。
FutureTask futureTask = new FutureTask(Callable接口对象) ;
new Thread(futureTask).start() ;
所以我们这就是为什么需要请出FutureTask的原因了。
总结:
创建线程的方式一共有5种:
①创建一个类继承Thread,然后重写run方法,(注意:void没有返回值)
②创建一个类实现Runnable接口,然后重写run方法,(注意:void没有返回值)
③实现Callable接口,然后重写call方法,(注意:T泛型,有返回值)
④通过Executors.newXXXThreadPool();
⑤自定义线程池,new ThreadPoolExecutor(...参数),然后将任务提交调用.submit()或者.execute()
最后:如果觉得我写的不错的话,麻烦给个赞赞,谢谢啦。