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里面。我们可以使用CallableFuture返回结果,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所提升的时间也是线性增加的。

本文转自:http://www.cnblogs.com/GuoJiaSheng/p/3950001.html

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 203,547评论 6 477
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,399评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 150,428评论 0 337
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,599评论 1 274
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,612评论 5 365
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,577评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,941评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,603评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,852评论 1 297
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,605评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,693评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,375评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,955评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,936评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,172评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 43,970评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,414评论 2 342

推荐阅读更多精彩内容