多线程在Java开发领域算是比较常见, 工作中的业务代码都会使用多线程来提高执行效率, 充分的利用cpu多核的特性, 把电脑的性能发挥到极致。如何创建多线程呢?其实很简单, Java 提供了4种方式。
1、继承 Thread 类,重写run方法
2、实现Runnable接口,重写run方法,实现Runnable接口的实现类的实例对象作为Thread 构造函数的target
3、通过Callable和futureTask 创建线程
4、通过线程池创建线程
前面两种可以归结为一类,没有返回值, 原因很简单,通过重写run方法, run方法的返回值都是void, 所以没有返回结果。
方式1:继承Thread 类的线程实现方式如下
public class ThreadR extends Thread { public ThreadR() { //编写子类的构造方法,可缺省 System.out.println("编写子类的构造方法"); } @Override public void run() { //编写自己的线程代码 System.out.println(Thread.currentThread().getName()); } public static void main(String[] args) { ThreadR threadDemo01 = new ThreadR(); threadDemo01.setName("我是自定义的线程1"); threadDemo01.start(); System.out.println(Thread.currentThread().toString()); } }
执行结果:
编写子类的构造方法
Thread[main,5,main]
我是自定义的线程1
方式2:通过实现Runnable 接口,实现run方法,接口的实现类的实例对象作为Thread的target作为参数传入带参的Thread构造函数,通过调用start()方法启动线程。
实现Runnable接口的线程实现方式如下
public class ThreadRun implements Runnable { public static void main(String[] args) { System.out.println(Thread.currentThread().getName()); Thread thread = new Thread(new ThreadRun()); thread.start(); } @Override public void run() { System.out.println(Thread.currentThread().getName() + " 通过接口的方式实现的 "); } }
运行的结果为:
main
Thread-0 通过接口的方式实现的
线程实现方式3:通过Callable和futureTask 创建线程
a:创建Callable 接口的实现类, 并实现Call 方法
b:创建Callable实现类的实现, 使用futureTask 类包装Callable 对象, 该FutureTask 对象封装了Callable对象的Call 方法的返回值
c:使用FutureTask对象作为Thread对象的target创建并启动线程
d:调用FutureTask对象的get()来获取子线程执行结束的返回值
public class CallFutureV { public static void main(String[] args) { Callable<Object> callable = new CallFutureVK<Object>(); FutureTask<Object> futureTask = new FutureTask<Object>(callable); Thread thread = new Thread(futureTask); System.out.println(" 通过Callable 的方式创建 " + Thread.currentThread().getName()); thread.start(); } static class CallFutureVK<Object> implements Callable<Object>{ /** * 重写call 方法 * @return * @throws Exception */ @Override public Object call() throws Exception { System.out.println(Thread.currentThread().getName()+"------ 通过实现Callable接口 通过 futureTask 包装器实现的线程"); return null; } } }
程序运行结果:
通过Callable 的方式创建main
Thread-0------ 通过实现Callable接口 通过 futureTask 包装器实现的线程
方式4:通过线程池来创建
执行结果如下:
线程池参数定义的说明
ThreadPoolExecutor 参数介绍
ThreadPoolExecutor 最多可以设置7个参数, 如下代码显示
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueue<Runnable> workQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler) { // 省略... }
7个参数代表的含义如下:
参数1:corePoolSize
核心线程数, 线程池中始终存活的线程数。
参数2:maximumPoolSize
最大线程数,线程池中允许的最大线程数, 当线程池的任务队列满了之后可以创建的最大线程数。
参数3:keepAliveTime
最大线程数可以存活的时间,当线程中没有任务执行时, 最大线程就会销毁一部分, 最终保持核心线程数量的线程。
参数4:unit:
单位是和参数3存活时间配合使用的,合在一起用于设定线程的存活时间, 参数keepAliveTime 的时间单位有以下7种可选:
TimeUnit.DAYS:天 TimeUnit.HOURS:小时 TimeUnit.MINUTES:分 TimeUnit.SECONDS:秒 TimeUnit.MILLISECONDS:毫秒 TimeUnit.MICROSECONDS:微妙 TimeUnit.NANOSECONDS:纳秒
参数5:workQueue
一个阻塞队列,用来存储线程池等待执行的任务,均为线程安全, 包含7种类型
ArraryBlockingQueue:一个由数组结构组成的有界阻塞队列。 LinkedBlockingQueue: 一个由链表结构组成的有界阻塞队列。 SynchronousQueue:一个不存储元素的阻塞队列,即直接提交给线程不保存它们。 PriorityBlockingQueue:一个支持优先级排序的无界阻塞队列。 DelayQueue:一个使用优先级队列实现的无界阻塞队列,只有在延迟期满时才能 从中提取元素。 LinkedTransferQueue:一个由链表结构组成的无界阻塞队列。与SynchronousQueue类似 还含有非阻塞方法。 LinkedBlockingDeque:一个由链表结构组成的双向阻塞队列。
较常用的是 LinkedBlockingQueue 和 Synchronous , 线程池的排队策略与 BlockingQueue 有关
参数6:threadFactory
线程工厂, 用来创建线程,默认为正常优先级, 非守护线程。
参数7:handler
拒绝策略, 拒绝处理任务时的策略, 系统提供了4种可选
AbortPolicy:拒绝并抛出异常。 CallerRunsPolicy:使用当前调用的线程来执行此任务。 DiscardOldestPolicy:抛弃队列头部(最旧)的一个任务, 并执行当前任务。 DiscardPolicy:忽略并抛弃当前任务。
默认策略为 AbortPolicy
总结:
创建线程的4种方式也都介绍了, 但最主要的还是通过线程池来创建线程的方式。 这种方式的好处是能解决频繁创建线程带来的损耗。 创建线程池去管理线程,创建回收这种方式最大的解决了资源浪费的情况。
下节重点介绍线程池的方式使用和线程池创建后的执行流程。