Java 从单核到多核的多线程(并发)
JAVA 并发编程
最初计算机是单任务的,然后发展到多任务,接着出现多线程并行,同时计算机也从单cpu进入到多cpu。如下图:
多任务:其实就是利用操作系统时间片轮转使用的原理。操作系统通过将cpu的执行时间分割成多个时间片,为每个任务分配时间片,因为cpu处理速度很快,这样就用户看来好像每个任务都在同时执行,感觉有多个cpu,但本质上一个时间点只有一个任务在运行。
随着多核多线程的出现,我们可以更好的利用资源但是同时也面临着更多的多线程编程挑战。
并行编程的好处:
1)提高资源利用率,提升程序运行时间---cpu的就是利用率
2)提高程序响应速度,比如用户界面的点击按钮,就是使用多线程,服务器收到用户点击请求,将这个请求交于一个新线程(worker)去执行,这样服务器就可以继续等待用户的输入请求,否则服务器在处理上一个请求的时候是无法响应当前用户的请求的。
并行编程的代价和挑战:
1)增加内存的消耗。
2)上下文的切换会消耗额外内存,从一个线程切换到另一个线程,需要记录当前线程的数据变量,指针等,然后执行另一个线程。
3)内存数据的同步,锁,通信等问题。
线程池(ThreadPool):
我想大部分人在听到这个东西的时候会感觉很神奇,但其实ThreadPool特别简单。线程池就是我们通过人工或者手动设置内存当中线程数的数量,使得程序可以最优运行。简单理解就是这样:我们设置一个线程池的大小,比如线程池的数量为10,那么当有线程任务来临的时候我们就使用线程池的线程去执行这个任务,如果线程池的10个线程都在执行任务,就把这个任务加到等待队列,等候其他线程运行结束后再执行。
使用线程池的好处:
1)降低资源消耗,通过重复利用在线程池中已创建好的线程执行任务,减少创建、销毁线程的内存和时间开销。
2)响应速度快,因为直接使用已创建好的线程执行任务,而不是去创建线程,所以响应时间快。
3)可以更好的管理线程,因为线程是稀有资源,避免随意创建线程(一个线程要暂用1M的内存左右)
很多问题我们使用顺序编程便可以解决,但是有些问题如果能够使用多线程并行的执行其中的任务则可以很大程度的提高时间效率,所以多线程还是很有必要的。
我自己总结了JAVA并行的3个发展阶段(菜鸟总结,请体谅)
第一阶段:Thread ,Runable
第二阶段:ExecutorService执行器
第三阶段: ForkJoin并行框架(其实就是ExecutorService的升级应用而已)
并发很大一方面是为了提高程序运行速度,如果想要一个程序运行的更快,那么可以将其分为多个片段,然后在每个单独的处理器上运行多个任务。现在是多核时代我们应该掌握,
但是我们知道并发通常是用在提高单核机器的程序性能上,这个咋一听感觉有点不能理解,学过操作系统的人应该知道,从一个任务切换到另一个任务是会有上下文开销的。但是因为有了“阻塞”,使得这个问题变得有些不同。
“阻塞”通常指的是一个程序的某个任务由于要执行程序控制之外的事,比如请求I/O资源,由于需要等待请求的I/O资源,所以会导致程序的暂停,就是cpu空闲。我们知道cpu是很宝贵的资源,我们应当充分利用它才对,这时候多线程就出来了,想想啊,当某个线程阻塞导致cpu空闲,这时候操作系统就将cpu分配给其他等待的线程使得cpu无时无刻不在运行。单个进程可以有多个并发执行的任务,我们感觉好像每个任务都有自己的cpu一样,其底层机制就是切分cpu时间,通常来说不需要我们管。
从事实上来看,如果程序没有任何阻塞,那么在单处理器上的并发是没有意义的。
(1)传统的并发编程采用Thread类
- 创建Thread类子例并重写run方法
- 编写类的时候实现Runnable接口方法,也是使用run方法。
public class App {
public static class demo extends Thread
{
int x;
public demo(int x)
{
this.x=x;
}
public void run() {
System.out.print("线程"+x);
}
}
public static void main(String[] args) {
demo dem=new demo(1);
dem.start();
} }
}
public class CommonRunnable implements Runnable{
public void run() {
System.out.println("MyRunnable running");
}
无论是Thread还是Runable其实都只要我们覆盖实现Run方法就好了,但是由于java类只能继承一次而接口可以有无数个所以我们更经常使用Ruanble接口。我们调用新线程都是使用start()方法而不是run()方法。
start方法的本质:从cpu中申请另一个线程空间来执行run方法,这样是并发线程。(其实它也是会自己调用run里面的方法,但是如果我们直接调用run方法的话,那么就是单线程而已)
以上两种虽然可以实现基本的并行结构,但是对于复杂的问题就会很麻烦,因此就有了在jdk5里面引入的Excutor执行器,其实就是实现线程池。
(2)ExecutorService执行器
是指java 5中引入的一系列并发库中与executor相关的一些功能类,其中包括线程池,Executor,Executors,ExecutorService,CompletionService,Future,Callable等。
Executor用来管理Runable对象的执行。用来创建并管理一组Runable对象的线程,这组线程就叫做线程池(Thread pool)
并发编程的一种编程方式是把任务拆分为一些列的小任务,即Runnable,然后在提交给一个Executor执行,Executor.execute(Runnalbe) 。Executor在执行时使用内部的线程池完成操作。
提交或者执行任务:
1)execute(Runnable) 无返回值,无法判断一个线程任务是否已经执行完毕
2)submit(Runnable) 会返回一个Future,通过get()判断是否执行完毕
3)submit(Callable) 会返回一个result(自定义的返回值)
在Executor里面。我们可以使用Callable,Future返回结果,Future<V>代表一个异步执行的操作,通过get()方法可以获得操作的结果,如果异步操作还没有完成,则,get()会使当前线程阻塞。FutureTask<V>实现了Future<V>和Runable<V>。Callable代表一个有返回值得操作。
public class Task implements Callable<Integer> { @Override
public Integer call() throws Exception {
int sum=0;
int begin=(int) (Math.random()*10); //产生0-10的双精度随机数
for(int i=0;i<begin;i++)
{
sum+=i;
}
return sum;
} }
public class test {
/**
* @param args
*/
public static void main(String[] args) {
// TODO Auto-generated method stub
ThreadPoolExecutor myExecutor = new ThreadPoolExecutor(3, 10, 200, TimeUnit.SECONDS,
new LinkedBlockingDeque<Runnable>());
List<Future<Integer>> results = new ArrayList<Future<Integer>>();
for(int i=0;i<5;i++)
{
Task task=new Task();
Future<Integer> result = null;
result=myExecutor.submit(task);
results.add(result);
}
for (Future<Integer> f : results) {
try {
System.out.println(f.get());
} catch (Exception ex) {
// ex.printStackTrace();
f.cancel(true);
}
}
myExecutor.shutdown();
}
}
结束任务:
Shutdown : 中断所有没有正在执行的任务,等待当前正在执行的线程结束然后关闭
ShutdowmNow: 遍历线程池中的所有线程任务,然后中断它们
上面并发执行的挺好的,但是有个问题。不同的线程执行有块有慢,有的任务会提早执行完毕,因此为了利用这些提早执行完毕的线程,使用了一种工作窃取(work-stealing)算法。
(3) ForkJoin并行框架
Fork/Join框架是Java7提供了的一个用于并行执行任务的框架, 是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。是不是很像map/reduce。
在一个任务内,首先检查这个任务的大小,如果它比设定的任务阈值要大,就将这个任务分成两份或者多份。然后再使用框架执行。如果要小就直接解决问题。
Fork/Join 模式有自己的适用范围。如果一个应用能被分解成多个子任务,并且组合多个子任务的结果就能够获得最终的答案,那么这个应用就适合用 Fork/Join 模式来解决。ForkJoin是将一个问题递归分解成子问题,再将子问题并行运算后合并结果。
让我们通过一个简单的需求来使用下Fork/Join框架,需求是:计算1+2+3+4的结果。
使用Fork/Join框架首先要考虑到的是如何分割任务,如果我们希望每个子任务最多执行两个数的相加,那么我们设置分割的阈值是2,由于是4个数字相加,所以Fork/Join框架会把这个任务fork成两个子任务,子任务一负责计算1+2,子任务二负责计算3+4,然后再join两个子任务的结果。
因为是有结果的任务,所以必须继承RecursiveTask。
我们只需要关注子任务的划分和中间结果的组合。ForkJoinTask完成子任务的划分,然后将它提交给ForkJoinPool来完成应用。
如果大家有学习集成学习,那么使用Fork/Join来处理对大规模数据的投票是非常好的。比如:
结果查看:可以从下面看到随着分类器个数的增加,使用Fork/Join所提升的时间也是线性增加的。
关于Fork/Join的查考: http://blog.csdn.net/lubeijing2008xu/article/details/18036931
http://www.oracle.com/technetwork/cn/articles/java/fork-join-422606-zhs.html
http://www.ibm.com/developerworks/cn/java/j-lo-forkjoin/
http://ifeve.com/java-concurrency-thread/
Java 从单核到多核的多线程(并发)的更多相关文章
- Java多线程并发03——在Java中线程是如何调度的
在前两篇文章中,我们已经了解了关于线程的创建与常用方法等相关知识.接下来就来了解下,当你运行线程时,线程是如何调度的.关注我的公众号「Java面典」了解更多 Java 相关知识点. 多任务系统往往需要 ...
- Java 多线程 | 并发知识问答总结
写在最前面 这个项目是从20年末就立好的 flag,经过几年的学习,回过头再去看很多知识点又有新的理解.所以趁着找实习的准备,结合以前的学习储备,创建一个主要针对应届生和初学者的 Java 开源知识项 ...
- Java 多线程 并发编程
一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种 ...
- java多线程并发编程与CPU时钟分配小议
我们先来研究下JAVA的多线程的并发编程和CPU时钟振荡的关系吧 老规矩,先科普 我们的操作系统在DOS以前都是单任务的 什么是单任务呢?就是一次只能做一件事 你复制文件的时候,就不能重命名了 那么现 ...
- JAVA面试题整理(2)-多线程/并发
1.synchronized 的实现原理以及锁优化? 在JDK 5之前Java语言是靠synchronized关键字保证同步的.使用synchronized 关键字定义同步方法,或者在方法中使用syn ...
- Java中多线程并发体系知识点汇总
一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种 ...
- Java 多线程并发编程一览笔录
Java 多线程并发编程一览笔录 知识体系图: 1.线程是什么? 线程是进程中独立运行的子任务. 2.创建线程的方式 方式一:将类声明为 Thread 的子类.该子类应重写 Thread 类的 run ...
- Java 多线程 并发编程 (转)
一.多线程 1.操作系统有两个容易混淆的概念,进程和线程. 进程:一个计算机程序的运行实例,包含了需要执行的指令:有自己的独立地址空间,包含程序内容和数据:不同进程的地址空间是互相隔离的:进程拥有各种 ...
- Java多线程并发05——那么多的锁你都了解了吗
在多线程或高并发情境中,经常会为了保证数据一致性,而引入锁机制,本文将为各位带来有关锁的基本概念讲解.关注我的公众号「Java面典」了解更多 Java 相关知识点. 根据锁的各种特性,可将锁分为以下几 ...
随机推荐
- MSRA面试总结
http://blog.renren.com/share/405984844/16014442499 注:以下内容纯凭记忆,由于已经过去一个多月,不保证准确性.由于面试前没有签保密协议,本文透露了比较 ...
- cocos2dx开发笔记
1.帧动画:SpriteTest=>SpriteAnimationSplit 2.sourceinsight显示代码行 option->document option->editin ...
- Main()方法
C#是从方法Main()开始执行的.这个方法必须是类或结构的静态方法,并且其返回类型必须是int或void .虽然显式指定p山屺修饰符是很常见的,因为按照定义,必须在程序外部调用该方法,但我们给该入口 ...
- Unrecognized selector sent to instance xxxxxxx
两个界面传递参数时报这个错误,经检查发现,是因为目标视图没有关联对应的controller.
- HDU 4686 Arc of Dream(矩阵)
题目链接:http://acm.hdu.edu.cn/showproblem.php?pid=4686 题意: 思路: #include <iostream>#include <cs ...
- inline-block在ie6中的经典bug
众所周知,给元素设置 inline-block ,可以让ie下的元素出发layout:1. 但是,当给元素设置 inline-block 后,在另外一个class 样式(非设置inline-block ...
- sql2005主从数据库同步配置
网站规模到了一定程度之后,该分的也分了,该优化的也做了优化,但是还是不能满足业务上对性能的要求:这时候我们可以考虑使用主从库.主从库是两台服务器上的两个数据库,主库以最快的速度做增删改操作+最新数据的 ...
- 50个python库
50个很棒的Python模块,包含几乎所有的需要:比如Databases,GUIs,Images, Sound, OS interaction, Web,以及其他.推荐收藏. Graphical in ...
- 待实践三:MVC3下 路由的测试 使用 RouteDebug.dll 来测试判断路由是否符合
在需要进行测试路由是否匹配的项目中引用 RouteDebug.dll 并且在MVC的Global.asax里面加入一段代码 //下面这行代码一定是在 RegisterRoutes(Rou ...
- 将多个.a库合并为一个.a库的方法
如果编译了多个架构的静态库,想将它们合并为一个静态库的时候,可以用如下方法合并: sudo lipo -create /libs/ffmpeg/2.6.3/arm64/lib/libavcodec.a ...