你好,我是田哥
多线程在面试中一直是加分项,如果不能回答,很可能会影响下一场比赛,严重影响面试结果。
接下来,我们来谈谈我们项目中可以使用多线程的场景。
让我们来看看多线程使用的主要目的:
1、吞吐量:你做WEB,容器帮你做多线程,但他只能帮你做请求级别。简单来说,可能是一个请求一个线程。或者多个请求一个线程。如果是单线程,只能同时处理一个用户的请求。
2、可伸缩性:也就是说,您可以通过增加CPU核数来提高性能。如果是单线程,则使用单核程序直到死亡。当然,没有办法通过增加CPU核数来提高性能。
举个简单的例子:假设有一个请求,该请求服务端的处理需要执行三个非常缓慢的IO操作(如数据库查询或文件查询),正常顺序可能是(括号代表执行时间):
- 读取文件1 (10ms)
- 处理1的数据(1ms)
- 读取文件2 (10ms)
- 处理2的数据(1ms)
- 读取文件3 (10ms)
- 处理3的数据(1ms)
- 整合1、2、3的数据结果 (1ms)
单线程总共需要34ms。
如果你把ab放在这个请求内,、cd、ef分别分三个线程去做,只需要12ms。
因此,多线程并非没有多大用处,而是你通常应该善于找到一些优化点。然后评估方案是否应使用。假设上述问题仍然相同:但每个步骤的执行时间不同。
- 读取文件1 (1ms)
- 处理1的数据(1ms)
- 读取文件2 (1ms)
- 处理2的数据(1ms)
- 读取文件3 (28ms)
- 处理3的数据(1ms)
- 整合1、2、3的数据结果 (1ms)
单线程总共需要34ms。
如果仍按上述划分方案(上述方案与桶原理相同,耗时取决于最慢线程的执行速度),则为第三线程,执行29ms。所以最后一个请求需要30ms。与不使用单线程相比,节省4ms。但是,线程调度切换也有可能花费1、2ms。因此,该方案的优势并不明显,但也提高了程序的复杂性。不太值得。
所以现在的优化点,不是第一个例子,任务分割多线程完成。但是优化文件3的读取速度。可能是缓存和减少一些重复读取。
首先,假设所有用户都要求这个请求,这实际上相当于所有用户都需要阅读文件3。想想看,100人提出了这个请求,相当于你花在阅读这个文件上的时间是28×100=2800ms了。所以,如果你缓存了文件,只要读取了第一个用户的请求,第二个用户就不需要读取。从内部访问非常快,可能不到1ms。
伪代码:
public class MyServlet extends Servlet{ private static Map<String, String> fileName2Data = new HashMap<String, String>(); private void procesfile3(String fName){ String data = fileName2Data.get(fName); if(data==null){ data = readFromFile(fName); //耗时28ms fileName2Data.put(fName, data); } //process with data }}
它看起来不错。建立文件名和文件数据的映射。如果读取map中已经存在的数据,则无需读取文件。
但问题是,Servlet是并发的,这将导致一个非常严重的问题,一个死循环。因为,当Hashmap并发修改时,它可能会导致循环链表的组成!!!(具体来说,您可以自己阅读Hashmap源代码)如果您没有接触到太多的线程,您可能会发现服务器没有要求或巨大的卡,也不知道发生了什么!
好吧,用ConcurrentHashmap,就像他的名字一样,他是一个安全的Hashmap,可以很容易地解决问题。
public class MyServlet extends Servlet{ private static ConcurrentHashMap<String, String> fileName2Data = new ConcurrentHashMap<String, String>(); private void procesfile3(String fName){ String data = fileName2Data.get(fName); if(data==null){ data = readFromFile(fName); //耗时28ms fileName2Data.put(fName, data); } //process with data }}
这真的解决了问题吗?这样,只要用户访问文件a,另一个用户想访问文件a,他就会从filename2data中获取数据,然后不会造成死循环。
然而,如果你认为这已经结束了,那么你认为多线程太简单了,骚年!你会发现,当1000个用户第一次访问相同的文件时,他们读取了1000个文件(这是最极端的,可能只有几百个)。What the fuckin hell!!!
代码错了吗?我就这样过我的生活吗?
好好分析一下。Servlet是多线程的,所以
public class MyServlet extends Servlet{ private static ConcurrentHashMap<String, String> fileName2Data = new ConcurrentHashMap<String, String>(); private void procesfile3(String fName){ String data = fileName2Data.get(fName); ///“偶然”-- 1000个线程同时到这里,同时,data也被发现为null if(data==null){ data = readFromFile(fName); //耗时28ms fileName2Data.put(fName, data); } //process with data }}
上面注释的“偶然”是完全有可能的,所以这样做还是有问题的。
因此,您可以简单地包装一个任务来处理它。
public class MyServlet extends Servlet{ private static ConcurrentHashMap<String, FutureTask> fileName2Data = new ConcurrentHashMap<String, FutureTask>(); private static ExecutorService exec = Executors.newCacheThreadPool(); private void procesfile3(String fName){ FutureTask data = fileName2Data.get(fName); ///“偶然”-- 1000个线程同时到这里,同时,data也被发现为null if(data==null){ data = newFutureTask(fName); FutureTask old = fileName2Data.putIfAbsent(fName, data); if(old==null){ data = old; }else{ exec.execute(data); } } String d = data.get(); //process with data } private FutureTask newFutureTask(final String file){ return new FutureTask(new Callable<String>(){ public String call(){ return readFromFile(file); } private String readFromFile(String file){return "";} } }}
以上所有代码都是直接在bbs打出来的,不能保证直接运行。
多线程最多的场景:web服务器本身;各种特殊服务器(如游戏服务器);
常见的多线程应用场景:
- 后台任务,如:定期向大量(100w以上)用户发送邮件;
- 发微博、记录日志等异步处理;
- 分布式计算
好了,我做了一个网站,在网上刷面试题:
网站:www.woaijava.cc