1、使用Callable接口的方式实现多线程,这是JDK5.0新增的一种创建多线程的方法
1 package com.baozi.java2; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.FutureTask; 6 7 public class ThreadNew { 8 public static void main(String[] args){ 9 //3、创建callable接口实现类的对象 10 NewThread newThread= new NewThread(); 11 //4、将此callable接口实现类的对象作为参数传递到FutureTask类的构造器中创建出一个FutureTask的实现类 12 FutureTask futureTask = new FutureTask(newThread); 13 //5、将FutureTask类的对象作为参数传递给Thread类的构造器创建一个Thread对象然后调用start()方法启动该线程 14 new Thread(futureTask).start(); 15 try { 16 //6、可以通过futureTask.get()方法获取call()方法中的返回值 17 System.out.println(futureTask.get()); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } catch (ExecutionException e) { 21 e.printStackTrace(); 22 } 23 } 24 25 } 26 //1、创建一个类实现callable接口 27 class NewThread implements Callable<Integer> { 28 //2、实现该接口中的call()方法 29 @Override 30 public Integer call() { 31 int sum = 0; 32 for (int i = 1; i <= 100; i++) { 33 if (i % 2 == 0) { 34 sum += i; 35 } 36 } 37 return sum; 38 } 39 }
2、使用Callable接口创建多线程和使用Runnable接口创建多线程的异同
相比较Runnable接口,Callable接口的功能更加强大。
- 相比较Runnable接口中需要重写的run()方法,Callable接口需要重写的call()方法有返回值
- 该方法可以抛出异常,外边的程序可以利用这个异常获取异常信息
- 支持泛型的返回值
- 需要借助FutureTask类,获取该线程的返回值
Future接口:
- 可以对具体的Runnable、Callable任务的执行结果进行取消、查询是否完成、获取结果等操作
- FutureTask是Future接口的唯一实现类
- FutureTask同时实现了Runnable、Callable接口,它既可以作为Runnable被线程执行,又可以作为Future得到Callable的返回值
3、使用线程池的方法创建多线程
1 package com.baozi.java2; 2 3 import java.util.concurrent.ExecutorService; 4 import java.util.concurrent.Executors; 5 import java.util.concurrent.ThreadPoolExecutor; 6 7 public class ThreadPool { 8 public static void main(String[] args) { 9 //1、先创建一个线程池 10 ExecutorService service = Executors.newFixedThreadPool(10); 11 //3、为线程池中线程分配任务并执行 12 ThreadPoolExecutor service1=(ThreadPoolExecutor)service; 13 System.out.println(service.getClass()); 14 service.execute(new NumberThread1()); 15 service.execute(new NumberThread2()); 16 service.execute(new NumberThread3()); 17 //4、关闭线程池 18 service.shutdown(); 19 } 20 } 21 //2、创建要执行某种操作的线程 22 class NumberThread1 implements Runnable { 23 @Override 24 public void run() { 25 for (int i = 0; i <= 100; i++) { 26 if (i % 2 == 0) { 27 System.out.println(Thread.currentThread().getName() + ":" + i); 28 } 29 } 30 } 31 } 32 33 class NumberThread2 implements Runnable { 34 @Override 35 public void run() { 36 for (int i = 0; i <= 100; i++) { 37 if (i % 2 != 0) { 38 System.out.println(Thread.currentThread().getName() + ":" + i); 39 } 40 } 41 } 42 } 43 44 class NumberThread3 implements Runnable { 45 @Override 46 public void run() { 47 for (int i = 0; i <= 100; i++) { 48 System.out.println(Thread.currentThread().getName() + ":" + i); 49 } 50 } 51 }
4、为什么要使用线程池的方法创建多线程
传统的方法创建线程,当程序需要使用多线程的时候进行创建,用完之后就会立即销毁,如果频繁的进行这样的操作会消耗大量的系统资源,严重影响程序的性能。
5、线程池的任务处理策略:
- 如果当前线程池中的线程数目小于corePoolSize,则每来一个任务,就会分配一个线程去执行这个任务;
- 如果当前线程池中的线程数目>=corePoolSize,则每来一个任务,会尝试将其添加到任务缓存队列当中,若添加成功,则该任务会等待空闲线程将其取出去执行;若添加失败(一般来说是任务缓存队列已满),则会尝试创建新的线程去执行这个任务;如果当前线程池中的线程数目达到maximumPoolSize,则会采取任务拒绝策略进行处理;
- 如果线程池中的线程数量大于 corePoolSize时,此时若某线程空闲时间超过keepAliveTime,该线程将被终止,直至线程池中的线程数目不大于corePoolSize;如果允许为核心池中的线程设置存活时间,那么核心池中的线程空闲时间超过keepAliveTime,线程也会被终止。
1 public void execute(Runnable command) { 2 if (command == null) 3 throw new NullPointerException(); 4 /* 5 * Proceed in 3 steps: 6 * 7 * 1. If fewer than corePoolSize threads are running, try to 8 * start a new thread with the given command as its first 9 * task. The call to addWorker atomically checks runState and 10 * workerCount, and so prevents false alarms that would add 11 * threads when it shouldn't, by returning false. 12 * 如果正在运行的线程数小于corePoolSize,那么将调用addWorker 方法来创建一个新的线程,并将该任务作为新线程的第一个任务来执行。 13 当然,在创建线程之前会做原子性质的检查,如果条件不允许,则不创建线程来执行任务,并返回false. 14 15 * 2. If a task can be successfully queued, then we still need 16 * to double-check whether we should have added a thread 17 * (because existing ones died since last checking) or that 18 * the pool shut down since entry into this method. So we 19 * recheck state and if necessary roll back the enqueuing if 20 * stopped, or start a new thread if there are none. 21 * 如果一个任务成功进入阻塞队列,那么我们需要进行一个双重检查来确保是我们已经添加一个线程(因为存在着一些线程在上次检查后他已经死亡)或者 22 当我们进入该方法时,该线程池已经关闭。所以,我们将重新检查状态,线程池关闭的情况下则回滚入队列,线程池没有线程的情况则创建一个新的线程。 23 * 3. If we cannot queue task, then we try to add a new 24 * thread. If it fails, we know we are shut down or saturated 25 * and so reject the task. 26 如果任务无法入队列(队列满了),那么我们将尝试新开启一个线程(从corepoolsize到扩充到maximum),如果失败了,那么可以确定原因,要么是 27 线程池关闭了或者饱和了(达到maximum),所以我们执行拒绝策略。 28 29 */ 30 31 // 1.当前线程数量小于corePoolSize,则创建并启动线程。 32 int c = ctl.get(); 33 if (workerCountOf(c) < corePoolSize) { 34 if (addWorker(command, true)) 35 // 成功,则返回 36 37 return; 38 c = ctl.get(); 39 } 40 // 2.步骤1失败,则尝试进入阻塞队列, 41 if (isRunning(c) && workQueue.offer(command)) { 42 // 入队列成功,检查线程池状态,如果状态部署RUNNING而且remove成功,则拒绝任务 43 int recheck = ctl.get(); 44 if (! isRunning(recheck) && remove(command)) 45 reject(command); 46 // 如果当前worker数量为0,通过addWorker(null, false)创建一个线程,其任务为null 47 else if (workerCountOf(recheck) == 0) 48 addWorker(null, false); 49 } 50 // 3. 步骤1和2失败,则尝试将线程池的数量有corePoolSize扩充至maxPoolSize,如果失败,则拒绝任务 51 else if (!addWorker(command, false)) 52 reject(command); 53 }
6、线程池的关闭
ThreadPoolExecutor提供了两个方法,用于线程池的关闭,分别是shutdown()和shutdownNow(),其中:
- shutdown():不会立即终止线程池,而是要等所有任务缓存队列中的任务都执行完后才终止,但再也不会接受新的任务
- shutdownNow():立即终止线程池,并尝试打断正在执行的任务,并且清空任务缓存队列,返回尚未执行的任务
7、使用线程池创建多线程的优点
- 减少了创建新线程的时间,提高程序的响应速度
- 重复利用线程池中的线程,不需要每次都创建新的线程降低资源消耗
- 便于线程的管理:
- corePoolSize:表示允许线程池中允许同时运行的最大线程数
- maximumPoolSize:线程池允许的最大线程数,他表示最大能创建多少个线程。maximumPoolSize肯定是大于等于corePoolSize
- KeepAliveTime:表示线程没有任务时最多保持多久然后停止
- ...