Java面试09|多线程
1、假如有Thread1、Thread2、Thread3、Thread4四条线程分别统计C、D、E、F四个盘的大小,所有线程都统计完毕交给Thread5线程去做汇总,应当如何实现?
把相互独立的计算任务包含在一个工作单元内,无需为每个单元启动新的线程。这样处理多线程代码通常效率更高。因为不用去为每个计算单元单独启动Thread线程。执行代码的线程是重用的。
(1)任务
Callable代表了一段可以调用并返回结果的代码
Future接口用来表示异步任务,是还没有完成的任务给出的未来结果。主要方法有get()、cancel()和isDone()
FutureTask是Future接口的常用实现类,它也实现了Runnable接口,所以和Runnable和Callable一样,可以由执行者高度。
(2)执行者 通过Executors类的工厂方法获取众多执行者之一
有个关于FutureTask的好例子,如下:
public class FutureTaskExample { public static void main(String[] args) { MyCallable callable1 = new MyCallable(1000); // 要执行的任务 MyCallable callable2 = new MyCallable(2000); FutureTask<String> futureTask1 = new FutureTask<String>(callable1);// 将Callable写的任务封装到一个由执行者调度的FutureTask对象 FutureTask<String> futureTask2 = new FutureTask<String>(callable2); ExecutorService executor = Executors.newFixedThreadPool(2); // 创建线程池并返回ExecutorService实例 executor.execute(futureTask1); // 执行任务 executor.execute(futureTask2); while (true) { try { if(futureTask1.isDone() && futureTask2.isDone()){// 两个任务都完成 System.out.println("Done"); executor.shutdown(); // 关闭线程池和服务 return; } if(!futureTask1.isDone()){ // 任务1没有完成,会等待,直到任务完成 System.out.println("FutureTask1 output="+futureTask1.get()); } System.out.println("Waiting for FutureTask2 to complete"); String s = futureTask2.get(200L, TimeUnit.MILLISECONDS); if(s !=null){ System.out.println("FutureTask2 output="+s); } } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); }catch(TimeoutException e){ //do nothing } } } }
示例如下:
public class Sums { // 使用Callable比Runnable更优势的地方在于Callable可以有确切的返回值。 static class Sum implements Callable<Long> { private final long from; private final long to; Sum(long from, long to) { this.from = from; this.to = to; } @Override public Long call() { long acc = 0; for (long i = from; i <= to; i++) { acc = acc + i; } return acc; } } public static void main(String[] args) throws Exception { ExecutorService executor = Executors.newFixedThreadPool(2); // Executes the given tasks, returning a list of Futures holding their status and results when all complete List<Future<Long>> results = executor.invokeAll( asList( new Sum(0, 10), new Sum(100, 1_000), new Sum(10_000, 1_000_000) )); // 另外要注意executor服务必须被关闭。如果它没有被关闭,主方法执行完后JVM就不会退出,因为仍然有激活线程存在 executor.shutdown(); for (Future<Long> result : results) { // Waits if necessary for the computation to complete, and then retrieves its result. System.out.println(result.get()); } } }
书写如上程序时需要注意三点:
(1)Callable是接口,我们需要重写的方法为call()
(2)线程池用完一定要shutdown()
2、分析线程池的实现原理和任务的调度过程
关于线程池的实现原理可以参考如下:
(1)http://blog.csdn.net/mazhimazh/article/details/19243889
(2)http://blog.csdn.net/mazhimazh/article/details/19283171
(3)参考《Java特种兵》295页内容
线程池实现原理就是线程池与工作队列的组合,在Executor任务执行框架中就体现了这种模式。
一个简单的小例子。
package ThreadPool; import java.util.LinkedList; import java.util.List; /** * 线程池类,线程管理器:创建线程,执行任务,销毁线程,获取线程基本信息 */ public final class ThreadPool { // 线程池中默认线程的个数为5 private static int worker_num = 5; // 工作线程 private WorkThread[] workThrads; // 未处理的任务 private static volatile int finished_task = 0; // 任务队列,作为一个缓冲,List线程不安全 private List<Runnable> taskQueue = new LinkedList<Runnable>(); private static ThreadPool threadPool; // 创建具有默认线程个数的线程池 private ThreadPool() { this(5); } // 创建线程池,worker_num为线程池中工作线程的个数 private ThreadPool(int worker_num) { ThreadPool.worker_num = worker_num; workThrads = new WorkThread[worker_num]; for (int i = 0; i < worker_num; i++) { workThrads[i] = new WorkThread(); workThrads[i].start();// 开启线程池中的线程 } } // 单态模式,获得一个默认线程个数的线程池 public static ThreadPool getThreadPool() { return getThreadPool(ThreadPool.worker_num); } // 单态模式,获得一个指定线程个数的线程池,worker_num(>0)为线程池中工作线程的个数 // worker_num<=0创建默认的工作线程个数 public static ThreadPool getThreadPool(int worker_num1) { if (worker_num1 <= 0) worker_num1 = ThreadPool.worker_num; if (threadPool == null) threadPool = new ThreadPool(worker_num1); return threadPool; } // 执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定 public void execute(Runnable task) { synchronized (taskQueue) { taskQueue.add(task); taskQueue.notify(); } } // 批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定 public void execute(Runnable[] task) { synchronized (taskQueue) { for (Runnable t : task) taskQueue.add(t); taskQueue.notify(); } } // 批量执行任务,其实只是把任务加入任务队列,什么时候执行有线程池管理器觉定 public void execute(List<Runnable> task) { synchronized (taskQueue) { for (Runnable t : task) taskQueue.add(t); taskQueue.notify(); } } // 销毁线程池,该方法保证在所有任务都完成的情况下才销毁所有线程,否则等待任务完成才销毁 public void destroy() { while (!taskQueue.isEmpty()) {// 如果还有任务没执行完成,就先睡会吧 try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } // 工作线程停止工作,且置为null for (int i = 0; i < worker_num; i++) { workThrads[i].stopWorker(); workThrads[i] = null; } threadPool=null; taskQueue.clear();// 清空任务队列 } // 返回工作线程的个数 public int getWorkThreadNumber() { return worker_num; } // 返回已完成任务的个数,这里的已完成是只出了任务队列的任务个数,可能该任务并没有实际执行完成 public int getFinishedTasknumber() { return finished_task; } // 返回任务队列的长度,即还没处理的任务个数 public int getWaitTasknumber() { return taskQueue.size(); } // 覆盖toString方法,返回线程池信息:工作线程个数和已完成任务个数 @Override public String toString() { return "WorkThread number:" + worker_num + " finished task number:" + finished_task + " wait task number:" + getWaitTasknumber(); } /** * 内部类,工作线程 */ private class WorkThread extends Thread { // 该工作线程是否有效,用于结束该工作线程 private boolean isRunning = true; /* * 关键所在啊,如果任务队列不空,则取出任务执行,若任务队列空,则等待 */ @Override public void run() { Runnable r = null; while (isRunning) {// 注意,若线程无效则自然结束run方法,该线程就没用了 synchronized (taskQueue) { // 在这里提供了同步 while (isRunning && taskQueue.isEmpty()) {// 队列为空 try { taskQueue.wait(20); } catch (InterruptedException e) { e.printStackTrace(); } } if (!taskQueue.isEmpty()) r = taskQueue.remove(0);// 取出任务 } if (r != null) { r.run();// 执行任务 } finished_task++; r = null; } }// end run // 停止工作,让该线程自然执行完run方法,自然结束 public void stopWorker() { isRunning = false; } } }
//测试线程池 public class TestThreadPool { public static void main(String[] args) { // 创建3个线程的线程池 ThreadPool t = ThreadPool.getThreadPool(3); t.execute(new Runnable[] { new Task(), new Task(), new Task() }); t.execute(new Runnable[] { new Task(), new Task(), new Task() }); System.out.println(t); t.destroy();// 所有线程都执行完成才destory System.out.println(t); } // 任务类 static class Task implements Runnable { private static volatile int i = 1; @Override public void run() {// 执行任务 System.out.println("任务 " + (i++) + " 完成"); } } }
Java线程的调度分为协同式线程调度和抢占式线程调度。
ScheduleThreadPoolExecutor是Java提供的多线程调度器,它可以接收任务,并把它们安排给线程池里的线程。可以参考如下内容:
《Java特种兵》 306页
利用了多线程加上任务资源共享的方式来实现服务器端大量任务的调度。
Java面试09|多线程的更多相关文章
- Java面试专题-多线程篇(2)- 锁和线程池
- Java面试专题-多线程(3)-原子操作
- java面试:多线程
1.多线程 同步:发送一个指令需要等待返回才能发送下一条(完成一件事才能做下一件). 异步:发送一个请求不需要等待返回,随时可以再发下一条(一次进行多个事件) 线程不安全根本原因是异步,对一个 ...
- Java面试系列
如果你的面试简历是如下这样写的,请务必准备回答下面的所有问题. 面试职位:Java高级工程师 专业技能: (1)牢固掌握Java基础知识,如集合.并发.I/O等,并对Java源码有一定的研究. (2) ...
- Java面试——多线程面试题总结
)两者都在等待对方所持有但是双方都不释放的锁,这时便会一直阻塞形成死锁. //存放两个资源等待被使用 public class Resource { public static Object obj1 ...
- Java面试:投行的15个多线程和并发面试题
多线程和并发问题已成为各种 Java 面试中必不可少的一部分.如果你准备参加投行的 Java 开发岗位面试,比如巴克莱银行(Barclays).花旗银行(Citibank).摩根史坦利投资公司(Mor ...
- Java面试:投行的15个多线程和并发面试题(转)
多线程和并发问题已成为各种 Java 面试中必不可少的一部分.如果你准备参加投行的 Java 开发岗位面试,比如巴克莱银行(Barclays).花旗银行(Citibank).摩根史坦利投资公司(Mor ...
- 一篇博客带你轻松应对java面试中的多线程与高并发
1. Java线程的创建方式 (1)继承thread类 thread类本质是实现了runnable接口的一个实例,代表线程的一个实例.启动线程的方式start方法.start是一个本地方法,执行后,执 ...
- Java基础技术多线程与并发面试【笔记】
Java基础技术多线程与并发 什么是线程死锁? 死锁是指两个或两个以上的进程(线程)在执行过程中,由于竞争资源或者由于彼此通信而造成的一种阻塞的现象,若无外力作用,它们都将无法推进下去,我们就可以称 ...
随机推荐
- WCF服务发布到IIS中去 在WCF调试
第一个WCF程序 1. 新建立空白解决方案,并在解决方案中新建项目,项目类型为:WCF服务应用程序.建立完成后如下图所示: 2.删除系统生成的两个文件IService1.cs与Service1.svc ...
- Ueditor实现自定义conttoller请求或跨域请求
http://www.th7.cn/Program/java/201507/510254.shtml
- JSP EL表达式忽略方法
JSP EL表达式忽略方法: web.xml中,和jsp中:jsp中的等级要高一些: web.xml: <?xml version="1.0" encoding=" ...
- CodeForces 446B
DZY Loves Modification time limit per test 2 seconds memory limit per test 256 megabytes input stand ...
- sublime text 添加到鼠标右键功能
安装sublime text的同学可能在安装的时候忘了设置sublime text的右键功能.那我们介绍如何添加. 我们要创建一个.reg为后缀的文件sublime_addright.reg.那么…… ...
- NAT详解
1.为什么出现了NAT? IP地址只有32位,最多只有42.9亿个地址,还要去掉保留地址.组播地址,能用的地址只有36亿左右,但是当下有数以万亿的主机,没有这么多IP地址怎么办,后面有了IPv6,但是 ...
- TFS 测试用例导入、导出工具
TFS的测试管理提供了测试规划.创建.运行以及进度跟踪等功能.测试人员通过浏览器就几乎可以完成手个测试的全部过程. 用过TFS测试用例的朋友们,很多人应该都知道,在TFS的Portal中以及相应的数据 ...
- java基础知识点---size(),length(),length的区别
List<Integer> a=new ArrayList<Integer>(); a.add(1); System.out.println(a.size()); int b[ ...
- ElasticSearch5集群部署指南
本文简要介绍ES5版本集群部署时的要点. 更多相关信息请参阅官网. 部分配置未在生产环境体现. 生产中2个集群20台centOS,总数据15TB,90亿条. 实时写入5000条/s, 最大7万/s. ...
- HUST 1584 摆放餐桌
1584 - 摆放餐桌 时间限制:1秒 内存限制:128兆 609 次提交 114 次通过 题目描述 BG准备在家办一个圣诞晚宴,他用一张大桌子招待来访的客人.这张桌子是一个圆形的,半径为R.BG邀请 ...