Java多线程的4种实现方式
Java有四种实现多线程的方式:
1).继承Thread类
2).实现Runnable接口
3).实现Callable接口
4).使用线程池
前三种实现方式的代码:
1 public class MultiThread { 2 //继承Thread类 3 static class MyThread1 extends Thread{ 4 @Override 5 public void run() { 6 System.out.println(Thread.currentThread().getName()+": extends Thread方式"); 7 } 8 } 9 //实现Runnable接口 10 static class MyThread2 implements Runnable{ 11 @Override 12 public void run() { 13 System.out.println(Thread.currentThread().getName()+": implements Runnable方式"); 14 } 15 } 16 //实现Callable接口 17 static class MyThread3 implements Callable<String>{ 18 @Override 19 public String call() throws Exception { 20 return Thread.currentThread().getName()+": implements Callable<V>方式"; 21 } 22 } 23 24 public static void main(String[] args) throws Exception{ 25 Thread thread1 = new MyThread1(); 26 Thread thread2 = new Thread(new MyThread2()); 27 FutureTask<String> futureTask = new FutureTask<>(new MyThread3()); 28 Thread thread3 = new Thread(futureTask); 29 30 thread1.start(); 31 thread2.start(); 32 thread3.start(); 33 System.out.println(futureTask.get()); 34 } 35 }
运行结果:
Thread-0: extends Thread方式 Thread-1: implements Runnable方式 Thread-2: implements Callable<V>方式
line26,27行当传入一个Runnable target参数给Thread后,Thread的run()方法就会调用target.run(),参考Thread类部分源代码:
public class Thread implements Runnable {
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
@Override
public void run() {
if (target != null) {
target.run();
}
}
}
1.继承Thread本质上也是实现Runnable接口。
2.实现Callable接口的任务线程能返回执行结果,而实现Runnable接口的线程不能返回结果
3.使用Callable的方式时候需要用FutureTask<>进行包装(适配),FutureTask实现了Runnable和Future,通过future里面的get()方法来得到返回值。需要注意的是get()方法是阻塞的,就是说取不到值会使主线程一直等待。
4.生产中几乎都会使用线程池。
线程池:用来控制运行的线程的数量,处理过程中将任务放入等待队列,然后在线程创建后启动这些任务。如果线程数量超过了最大数量(maximumPoolSize),超出数量的线程将会在等待队列排队等候。如果等待队列已满,再进来的任务就会按照拒绝策略拒绝。
线程池的特点:线程复用,控制最大并发数,管理线程。
线程池的优势:
1.降低资源消耗,通过复用线程来降低创建和销毁线程的消耗。
2.提高响应速度,当任务到达时不需要等待创建线程。
3.提高线程的可管理性,使用线程池可以进行统一的分配,监控和调优。
java自带的线程池的实现:
//固定数量线程的线程池 ExecutorService threadPool1 = Executors.newFixedThreadPool(cpuNum); //一个线程的线程池 ExecutorService threadPool2 = Executors.newSingleThreadExecutor(); //多个线程的线程池 ExecutorService threadPool3 = Executors.newCachedThreadPool(); //Java8新特性
ExecutorService threadPool3 = Executors.newWorkStealingPool();
上面几线程池的底层都是ThreadPoolExecutor(),ThreadPoolExecutor是线程池的核心类。ThreadPoolExecutor的构造器最多有7个可配参数:
ThreadPoolExecutor的7个参数:
- corePoolSize:核心池的大小,这个参数跟后面讲述的线程池的实现原理有非常大的关系。在创建了线程池后,默认情况下,线程池中并没有任何线程,而是等待有任务到来才创建线程去执行任务,除非调用了prestartAllCoreThreads()或者prestartCoreThread()方法,从这2个方法的名字就可以看出,是预创建线程的意思,即在没有任务到来之前就创建corePoolSize个线程或者一个线程。默认情况下,在创建了线程池后,线程池中的线程数为0,当有任务来之后,就会创建一个线程去执行任务,当线程池中的线程数目达到corePoolSize后,就会把到达的任务放到缓存队列当中;
- maximumPoolSize:线程池最大线程数,这个参数也是一个非常重要的参数,它表示在线程池中最多能创建多少个线程;
- keepAliveTime:表示线程没有任务执行时最多保持多久时间会终止。默认情况下,只有当线程池中的线程数大于corePoolSize时,keepAliveTime才会起作用,直到线程池中的线程数不大于corePoolSize,即当线程池中的线程数大于corePoolSize时,如果一个线程空闲的时间达到keepAliveTime,则会终止,直到线程池中的线程数不超过corePoolSize。但是如果调用了allowCoreThreadTimeOut(boolean)方法,在线程池中的线程数不大于corePoolSize时,keepAliveTime参数也会起作用,直到线程池中的线程数为0;
- unit:参数keepAliveTime的时间单位,有7种取值:
TimeUnit.DAYS;//天 TimeUnit.HOURS;//小时 TimeUnit.MINUTES;//分钟 TimeUnit.SECONDS;//秒 TimeUnit.MILLISECONDS;//毫秒 TimeUnit.MICROSECONDS;//微妙 TimeUnit.NANOSECONDS;//纳秒
- workQueue:一个阻塞队列,用来存储等待执行的任务,这个参数的选择也很重要,会对线程池的运行过程产生重大影响,一般来说,这里的阻塞队列有以下几种选择:ArrayBlockingQueue和PriorityBlockingQueue使用较少,一般使用LinkedBlockingQueue和Synchronous。线程池的排队策略与BlockingQueue有关。
- threadFactory:线程工厂,主要用来创建线程;
- handler:表示当拒绝处理任务时的策略,有以下四种取值:
ThreadPoolExecutor.AbortPolicy:丢弃任务并抛出RejectedExecutionException异常。 ThreadPoolExecutor.DiscardPolicy:也是丢弃任务,但是不抛出异常。 ThreadPoolExecutor.DiscardOldestPolicy:丢弃队列最前面的任务,然后重新尝试执行任务(重复此过程) ThreadPoolExecutor.CallerRunsPolicy:由调用线程处理该任务
线程池详细原理可以参考:https://www.cnblogs.com/dolphin0520/p/3932921.html