Java多线程通过多核CPU来提升速度--更快的执行
来源:互联网 发布:linux下解压rpm文件 编辑:程序博客网 时间:2024/05/16 08:36
全文翻译自15L大神
方案1:单线程
假设有个请求,这个请求服务端的处理需要执行3个很缓慢的IO操作(比如数据库查询或文件查询),那么正常的顺序可能是(括号里面代表执行时间):
a、读取文件1 (10ms)
b、处理1的数据(1ms)
c、读取文件2 (10ms)
d、处理2的数据(1ms)
e、读取文件3 (10ms)
f、处理3的数据(1ms)
g、整合1、2、3的数据结果 (1ms)
单线程总共就需要34ms。
package cn.bellychang.d0209;public class NoConcurrence {/** * @param args * @throws InterruptedException */public static void main(String[] args) throws InterruptedException {long start = System.currentTimeMillis();int a,b,c;//读取文件1Thread.sleep(10L);//处理1的数据Thread.sleep(1L);a = 1;//读取文件2Thread.sleep(10L);//处理2的数据Thread.sleep(1L);b =1;//读取文件3Thread.sleep(10L);//处理3的数据Thread.sleep(1L);//整合1、2、3的数据结果Thread.sleep(1L);c = 1;int result = a + b + c;long end = System.currentTimeMillis();System.out.println(end - start);}}
方案2:如果你在这个请求内,把ab、cd、ef分别分给3个线程去做,就只需要12ms了。
package cn.bellychang.d0209;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;class ThreadA implements Callable<Integer> {@Overridepublic Integer call() throws Exception {// 读取文件1Thread.sleep(10L);// 处理1的数据Thread.sleep(1L);return 1;}}class ThreadB implements Callable<Integer> {@Overridepublic Integer call() throws Exception {// 读取文件2Thread.sleep(10L);// 处理2的数据Thread.sleep(1L);return 1;}}class ThreadC implements Callable<Integer> {@Overridepublic Integer call() throws Exception {// 读取文件2Thread.sleep(10L);// 处理2的数据Thread.sleep(1L);return 1;}}public class ConcurenceForMultiCPU {/** * @param args * @throws InterruptedException * @throws ExecutionException */public static void main(String[] args) throws InterruptedException, ExecutionException {long start = System.currentTimeMillis();ExecutorService exec = Executors.newCachedThreadPool();Future<Integer> f = exec.submit(new ThreadA());Future<Integer> g = exec.submit(new ThreadB());Future<Integer> h = exec.submit(new ThreadC());int result = f.get()+g.get()+h.get();long end = System.currentTimeMillis();System.out.println(end - start);}}
所以多线程不是没怎么用,而是,你平常要善于发现一些可优化的点。然后评估方案是否应该使用。
方案3:
假设还是上面那个相同的问题:但是每个步骤的执行时间不一样了。
a、读取文件1 (1ms)
b、处理1的数据(1ms)
c、读取文件2 (1ms)
d、处理2的数据(1ms)
e、读取文件3 (28ms)
f、处理3的数据(1ms)
g、整合1、2、3的数据结果 (1ms)
单线程总共就需要34ms。
package cn.bellychang.d0209;import java.util.concurrent.Callable;import java.util.concurrent.ExecutionException;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Future;class ThreadA1 implements Callable<Integer> {@Overridepublic Integer call() throws Exception {// 读取文件1Thread.sleep(1L);// 处理1的数据Thread.sleep(1L);return 1;}}class ThreadB1 implements Callable<Integer> {@Overridepublic Integer call() throws Exception {// 读取文件2Thread.sleep(1L);// 处理2的数据Thread.sleep(1L);return 1;}}class ThreadC1 implements Callable<Integer> {@Overridepublic Integer call() throws Exception {// 读取文件2Thread.sleep(28L);// 处理2的数据Thread.sleep(1L);return 1;}}public class ConcurenceForMultiCPU1 {/** * @param args * @throws InterruptedException * @throws ExecutionException */public static void main(String[] args) throws InterruptedException, ExecutionException {long start = System.currentTimeMillis();ExecutorService exec = Executors.newCachedThreadPool();Future<Integer> f = exec.submit(new ThreadA1());Future<Integer> g = exec.submit(new ThreadB1());Future<Integer> h = exec.submit(new ThreadC1());int result = f.get()+g.get()+h.get();long end = System.currentTimeMillis();System.out.println(end - start);}}
如果还是按上面的划分方案(上面方案和木桶原理一样,耗时取决于最慢的那个线程的执行速度),在这个例子中是第三个线程,执行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 processFile3(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 processFile3(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 processFile3(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 processFile3(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 "";} } }}
- Java多线程通过多核CPU来提升速度--更快的执行
- 系统加速精灵:全面提升CPU性能,让你的系统跑的更快!
- 更快的异步执行
- Java多核cpu多线程运行效率分析
- 如何使用 EGOCache 来缓存和加载网页的速度更快
- 其实FireFox的速度可以更快
- 多线程进行网络通信是否可以达到更快的速度?
- 优化代码,看看哪个执行速度更快!!!
- 优化代码,看看哪个执行速度更快!!!
- 哪些特性让Android执行速度更快
- WPF中如何创建多线程,响应速度更快
- 使CPU运行的更快--Cache
- ++num和num++哪个执行速度更快,++num为什么会比num++速度快
- java多线程问题 多核cpu遇上java多线程,求解释
- 多处理机、多核cpu、多线程cpu的区别
- 提升ASP网站的执行速度
- 提升ASP网站的执行速度
- 提升ASP网站的执行速度
- Chapter 1-03
- Chapter 2-01
- Android中的WebView常用用法
- MIC性能优化
- 美团、58、百度、阿里面经
- Java多线程通过多核CPU来提升速度--更快的执行
- [剑指offer-1518]反转链表
- JAVA的BigInteger
- 解决win10下WIFI无法连接到网络
- neuq oj 1016 Roliygu and Yilan C++
- 排序算法笔记_____2(选择排序和插入排序)
- swift算法手记-10
- 最大二分匹配
- 微信支付之HTML5页面WAP端接入