java并发线程框架的变迁

java并发线程框架的变迁

jdk5之前jdk5jdk7jdk8
编程接口Runnable/ThreadExecutorForkJoinCompletableFuture

jdk5之前

  • green thread 绿色线程 维基百科解释:
    1. 绿色线程是由系统运行库或者JVM调度,不依赖原生操作系统的模拟多核环境的线程,绿色线程运行于用户空间,不需要原生线程的支持。
    2. 在一个多核的环境中,原生线程可以自动为任务分配多个核,然而绿色线程不可以。
  • native thread 原生线程

编程模型(Runnable/Thread)

new Thread(new Runnable() {
    @Override
    public void run() {
        // TODO Auto-generated method stub
    }
}).start();

new Thread的局限性如下:

  • 实现的局限性
  • 缺少线程原生支持
  • 缺少锁api
  • 缺少执行完成原生支持
  • 执行结果获取困难
  • Double checking lock 不确定
  • 每次new Thread新建对象性能差
  • 线程缺乏统一管理,可能无限制新建线程,相互之间竞争,及可能占用过多系统资源导致死机或oom。
  • 缺乏更多功能,如定时执行、定期执行、线程中断。

一下列出了这么多弊端,小心脏有点受不了,毕竟我启动线程还时不时的用Runnable和new一个Thread呢,感觉太low了?不要紧,重要的是感觉到了!

jdk5(编程模型:Executors)

相比于之前解决的问题

  1. 重用存在的线程,减少对象创建、消亡的开销,性能佳 可有效控制最大并发线程数,提高系统资源的使用率,同时避免过多资源竞争,避免堵塞
  2. 提供定时执行、定期执行、单线程、并发数控制等功能
  3. 异步执行(Future接口,Future以及相关使用方法提供了异步执行任务的能力,但对于结果的获取却是不方便,只能通过阻塞或轮询的方式得到任务结果。阻塞的方式与我们理解的异步编程其实是相违背的,而轮询又会耗无谓的CPU资源。)

执行单位

  1. Callable 可返回值
  2. Runable 无返回值

四种线程池

  1. newCachedThreadPool创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
  2. newFixedThreadPool 创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待。
  3. newScheduledThreadPool 创建一个定长线程池,支持定时及周期性任务执行。
  4. newSingleThreadExecutor 创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行。
newCachedThreadPool

创建一个可缓存线程池,如果线程池长度超过处理需要,可灵活回收空闲线程,若无可回收,则新建线程。
Talk is cheap, show me the code :

package com.wenniuwuren.concurrent;  
  
import java.util.concurrent.ExecutorService;  
import java.util.concurrent.Executors;  
 
public class newCachedThreadPoolTest {  
    public static void main(String[] args) {  
        ExecutorService executorService = Executors.newCachedThreadPool();  
        for (int i = 1; i < 10000; i++)  {
        executorService.submit(new Task());  
        }
    }  
}  
class Task implements Runnable {  
    @Override  
    public void run() {  
        try {  
            Thread.sleep(5000);  
        } catch (InterruptedException e) {  
            e.printStackTrace();  
        }  
  
    }  
}  

如果创建的线程数量过多,可能会出现堆栈内存溢出,线程最大值初始化为 Integer.MAX_VALUE,如果线程不断被创建,可能会用光内存!

newFixedThreadPool

创建一个定长线程池,可控制线程最大并发数,超出的线程会在队列中等待
Talk is cheap, show me the code :

ExecutorService fixedThreadPool = Executors.newFixedThreadPool(3);
for (int i = 0; i < 10; i++) {
	final int index = i;
	fixedThreadPool.execute(new Runnable() {
		 @Override
		 public void run() {
			try {
				System.out.println(index);
				Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	});
}

以上线程允许3个任务同时执行,多出来的线程会放在线程池中等待,最大线程池数量为Integer.MAX_VALUE。

newScheduledThreadPool

创建一个定长线程池,支持定时及周期性任务执行,ScheduledExecutorService比Timer更安全,功能更强大
Talk is cheap, show me the code :

定时任务(3s之后执行):
ScheduledExecutorService scheduledThreadPool = Executors.newScheduledThreadPool(5);
scheduledThreadPool.schedule(new Runnable() {
	 @Override
	 public void run() {
		System.out.println("delay 3 seconds");
	}
}, 3, TimeUnit.SECONDS);
周期性任务执行(延迟1s执行,每隔3s执行一次):
scheduledThreadPool.scheduleAtFixedRate(new Runnable() {
	@Override
	public void run() {
		System.out.println("delay 1 seconds, and excute every 3 seconds");
	}
}, 1, 3, TimeUnit.SECONDS);
newSingleThreadExecutor

创建一个单线程化的线程池,它只会用唯一的工作线程来执行任务,保证所有任务按照指定顺序(FIFO, LIFO, 优先级)执行
Talk is cheap, show me the code :

ExecutorService singleThreadExecutor = Executors.newSingleThreadExecutor();
for (int i = 0; i < 10; i++) {
	final int index = i;
	singleThreadExecutor.execute(new Runnable() {
	 @Override
	public void run() {
		try {
			System.out.println(index);
			Thread.sleep(2000);
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
	});
}
线程池的作用:

线程池作用就是管理系统中执行线程的数量,根据系统的环境情况,可以自动或手动设置线程数量,达到运行的最佳效果,少了浪费了系统资源,多了造成系统拥挤效率不高。用线程池控制线程数量,其他线程排队等候。一个任务执行完毕,再从队列的中取最前面的任务开始执行。若队列中没有等待进程,线程池的这一资源处于等待。当一个新任务需要运行时,如果线程池中有等待的工作线程,就可以开始运行了;否则进入等待队列。

为什么要用线程池:
  1. 减少了线程创建和销毁的次数,每个工作线程可以被重复利用,可执行多个任务。
  2. 可以根据系统的承受能力,调整线程池中工作线线程的数目,防止因为消耗过多的内存,而把服务器累趴下(每个线程需要大约1MB内存,线程开的越多,消耗的内存也就越大,最后死机)。
  3. Java里面线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。
重要的类

线程池的顶级接口是Executor,但是严格意义上讲Executor并不是一个线程池,而只是一个执行线程的工具。真正的线程池接口是ExecutorService。

类型功能
ExecutorServiceinterface真正的线程池接口
ScheduledExecutorServiceinterface继承ExecutorService
能和Timer/TimerTask类似,解决那些需要任务重复执行的问题
ThreadPoolExecutorclassExecutorService的默认实现
ScheduledThreadPoolExecutorclass继承ThreadPoolExecutor的ScheduledExecutorService接口实现,周期性任务调度的类实现

jdk7(编程模型:Fork/Join)

Fork/Join框架

Fork/Join框架是Java7提供了的一个用于并行执行任务的框架,
是一个把大任务分割成若干个小任务,最终汇总每个小任务结果后得到大任务结果的框架。

理解Fork/Join框架的设计

任务的执行分为2个步骤:
1. 递归分割任务,直到任务不能再被分割,开始计算,注意特殊情况的处理。
2. 合并结果,分割的子任务分别放在双端队列里,然后几个启动线程分别从双端队列里获取任务执行。
子任务执行完的结果都统一放在一个队列里,启动一个线程从队列里拿数据,然后合并这些数据

Fork/Join框架的类

  1. ForkJoinTask:我们要使用ForkJoin框架,必须首先创建一个ForkJoin任务。它提供在任务中执行fork()和join()操作的机制,通常情况下我们不需要直接继承ForkJoinTask类,而只需要继承它的子类,Fork/Join框架提供了以下两个子类:
    - RecursiveAction:用于没有返回结果的任务。
    - RecursiveTask :用于有返回结果的任务。
  2. ForkJoinPool :ForkJoinTask需要通过ForkJoinPool来执行,任务分割出的子任务会添加到当前工作线程所维护的双端队列中,进入队列的头部。当一个工作线程的队列里暂时没有任务时,它会随机从其他工作线程的队列的尾部获取一个任务
    Talk is cheap, show me the code:
下面展示两个累加的例子:
RecursiveAction:
public class Java7RecursiveAction {
    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        //累加数
        List<Integer> nums = Arrays.asList(1,2,3,4,5,6,7,8,9,10);
        //定义一个用于线程安全的累加器
        LongAdder longAdder = new LongAdder();
        //自定义累加任务
        AddTask addTask = new AddTask(nums,longAdder);
        //forkJoinPool 调用累加任务
        forkJoinPool.invoke(addTask);
        //关闭线程池
        forkJoinPool.shutdown();
        System.out.println("1..10 RESULT="+ longAdder.longValue());
    }

    private static class AddTask extends RecursiveAction {

        private final List<Integer> nums;
        private final LongAdder longAdder;

        public AddTask(List<Integer> nums, LongAdder longAdder) {
            this.nums=nums;
            this.longAdder=longAdder;
        }

        @Override
        protected void compute() {

            int size = nums.size();
            if (size > 1){
	            //分割任务
                int part = size/2;
                List<Integer> left = nums.subList(0,part);
                AddTask left_addTask = new AddTask(left,longAdder);

                List<Integer> right = nums.subList(part,size);
                AddTask right_addTask = new AddTask(right,longAdder);
				//执行所有累加任务
                invokeAll(left_addTask,right_addTask);
            }else {
                if (size == 0){
                    return;
                }
                //当任务最细粒度时执行加法操作
                Integer value = nums.get(0);
                System.out.printf(longAdder.longValue()+"+");
                longAdder.add(value);

            }

        }
    }
}
RecursiveTask:
public class Java7RecursiveTask {
    public static void main(String[] args) {
        ForkJoinPool forkJoinPool = new ForkJoinPool();
        AddTask addTask = new AddTask(1,100);
        Future<Integer> result = forkJoinPool.submit(addTask);
        //调用异常捕获接口 捕获异常
        if (addTask.isCompletedAbnormally()){
            System.out.println(addTask.getException());
        }
        try {
            System.out.println(result.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
    }
    private static class AddTask extends RecursiveTask{
        //阈值,最少2个数相加
        private static final int THRESHOLD = 2;
        private final int start;
        private final int end;

        public AddTask(int start, int end) {
            this.start = start;
            this.end = end;
        }

        @Override
        protected Integer compute() {
            int sum = 0;
            if (end-start <= THRESHOLD){
                for (int i = start; i <= end; i++) {
                    sum += i;
                }
            }else {
                int mid = (end+start)/2;
                //创建子任务
                AddTask leftTask =new AddTask(start, mid);
                leftTask.fork();
                AddTask rightTask =new AddTask(mid+1, end);
                rightTask.fork();
				//等待子任务的执行
                int leftResult= (int) leftTask.join();
                int rightResult= (int) rightTask.join();
                //累加
                sum = leftResult  + rightResult;
            }
            //执行结果
            return sum;
        }
    }

}

Fork/Join并发框架的局限性

  • 无法手动完成
  • 阻塞式返回结果
  • 无法链式多future
  • 无法合并多个future的结果
  • 缺少异常处理,异常接口的调用在外部只能获取状态,而不能及时处理

jdk8(编程模型:CompletionStage/CompletableFuture)

Future 接口的局限性

future接口可以构建异步应用,但依然有其局限性。它很难直接表述多个Future 结果之间的依赖性。实际开发中,我们经常需要达成以下目的:

  • 将两个异步计算合并为一个——这两个异步计算之间相互独立,同时第二个又依赖于第一个的结果。
  • 等待 Future 集合中的所有任务都完成。
  • 仅等待 Future 集合中最快结束的任务完成(有可能因为它们试图通过不同的方式计算同一个值),并返回它的结果。
  • 通过编程方式完成一个 Future 任务的执行(即以手工设定异步操作结果的方式)。
  • 应对 Future 的完成事件(即当 Future 的完成事件发生时会收到通知,并能使用 Future 计算的结果进行下一步的操作,不只是简单地阻塞等待操作的结果)

操作方法

  1. 基本使用
CompletableFuture<Integer> completableFuture1 = CompletableFuture.completedFuture(1 + 1);
Integer value = completableFuture1.get();
注:若上述代码执行出现异常,执行get的时候可能会陷入无尽的等待,get()最好设置超市时间。

部分方法的作用:
2. 进行结果变换 :thenApply/thenApplyAsync
3. 结果的消耗 :thenAccept/thenAcceptAsync
4. 执行下一个操作:thenRun/thenRunAsync
5. 结合两个CompletionStage的结果,进行转化:thenCombine/thenCombineAsync
6. 结合两个CompletionStage的结果,进行消耗:thenAcceptBoth/thenAcceptBothAsync
7. 在两个CompletionStage都运行完执行:runAfterBoth/runAfterBothAsync
8. 在两个CompletionStage谁计算的快:applyToEither/applyToEitherAsync
9. 两个CompletionStage,谁计算的快,执行下一步:acceptEither/acceptEitherAsync
10. 两个CompletionStage,任何一个完成了都会执行下一步的操作:runAfterEither/runAfterEitherAsync
11. 当运行时出现了异常,可以通过exceptionally进行补偿
12. 当运行完成时,对结果的记录:whenComplete/whenCompleteAsync
13. 运行完成时,对结果的处理:handle/handleAsync
14. allOf 工厂方法接收一个由CompletableFuture 构成的数组,数组中的所有 Completable-Future 对象执行完成之后,它返回一个 CompletableFuture 对象。这意味着,如果你需要等待多个 CompletableFuture 对象执行完毕,对 allOf 方法返回的
CompletableFuture 执行 join 操作可以等待CompletableFuture执行完成。
15. CompletableFuture 对象数组中有任何一个执行完毕就不再等待,在这种情况下,你可以使用一个类似的工厂方法 :anyOf 。
16. thenCompose 方法允许你对两个异步操作进行流水线

代码示例

public class Java8CompletableFuture {
    public static void main(String[] args) throws ExecutionException, InterruptedException {
        CompletableFuture<Integer> completableFuture1 = CompletableFuture.completedFuture(1/1);
        Integer value = completableFuture1.get();
        System.out.println(value);
        CompletableFuture<Void> completableFuture2 = CompletableFuture.runAsync(new Runnable() {
            @Override
            public void run() {
                System.out.println("completableFuture2"+"thread --> "+ Thread.currentThread().getName());
            }
        });
        CompletableFuture<String> completableFuture3 = CompletableFuture.supplyAsync(new Supplier<String>() {
            @Override
            public String get() {
                return "hello word completableFuture3"+"thread --> "+ Thread.currentThread().getName();
            }
        });

        System.out.println(completableFuture3.get());

        //合并操作,第一个式自线程,之后的链都是主线程
        CompletableFuture<Void> completableFuture4 = CompletableFuture.supplyAsync(() ->{
            return "completableFuture4"+" thread 4-1 --> "+ Thread.currentThread().getName();
        }).thenApply(value1 -> {
            return value1 + " "+ LocalDateTime.now()+" thread 4-2 --> "+ Thread.currentThread().getName();
        }).thenApply( v -> {
            System.out.println(v+" thread 4-2--> "+ Thread.currentThread().getName());
            return v;
        }).thenRun(()->{});
    }
}

参考:链接:https://www.jianshu.com/p/4897ccdcb278 作者:不迷失 來源:简书
参考:链接:https://www.jianshu.com/p/6f3ee90ab7d3 作者:数齐 來源:简书
参考:链接:http://ifeve.com/talk-concurrency-forkjoin/ 作者:方 腾飞 来源:并发编程网 - ifeve.com
参考:咕泡学院讲师小马哥的视频《java8并发异步编程》
Java高并发编程是同其他技术一样都是一门艺术,这里的java并发框架只是迈向艺术殿堂的第一步,实际编程的实践和经验的积累才是迈向艺术殿堂的光明大道。感谢参考各位技术达人的博客以及视频。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值