Java并发 -- Copy-on-Write模式
fork
类Unix操作系统调用fork(),会创建父进程的一个完整副本,很耗时
Linux调用fork(),创建子进程时并不会复制整个进程的地址空间,而是让父子进程共享同一个地址空间
只有在父进程或者子进程需要写入时才会复制地址空间,从而使父子进程拥有各自独立的地址空间
本质上来说,父子进程的地址空间和数据都是要隔离的,使用Copy-on-Write更多体现的是一种延时策略
Copy-on-Write还支持按需复制,因此在操作系统领域能够提升性能
Java提供的Copy-on-Write容器,会复制整个容器,所以在提升读操作性能的同时,是以内存复制为代价的
CopyOnWriteArrayList / CopyOnWriteArraySet
RPC框架
服务提供方是多实例分布式部署的,服务的客户端在调用RPC时,会选定一个服务实例来调用
这个过程的本质是负载均衡,而做负载均衡的前提是客户端要有全部的路由信息
一个核心任务就是维护服务的路由关系,当服务提供方上线或者下线的时候,需要更新客户端的路由表信息
RPC调用需要通过负载均衡器来计算目标服务的IP和端口号,负载均衡器通过路由表 ...
Java并发 -- Immutability模式
Immutability模式Immutability模式:对象一旦被创建之后,状态就不再发生变化
不可变类
将一个类所有的属性都设置成final,并且只允许存在只读方法
将类也设置成final的,因为子类可以重写父类的方法,有可能改变不可变性
包装类
String、Integer、Long和Double等基础类型的包装类都具备不可变性
这些对象的线程安全都是靠不可变性来保证的
严格遵守:_类和属性都是final的,所有方法均是只读的_
如果具备不可变性的类,需要提供修改的功能,那会创建一个新的不可变对象
这会浪费内存空间,可以通过享元模式来优化
1234567891011121314151617181920212223242526272829private final char value[];public String replace(char oldChar, char newChar) { if (oldChar != newChar) { int len = value.length; int i = -1; char[] ...
Java并发 -- Fork + Join
任务视角
线程池+Future:简单并行任务
CompletableFuture:聚合任务
CompletionService:批量并行任务
Fork/Join:_分治_
分治任务模型
分治任务模型分为两个阶段:任务分解 + 结果合并
任务分解:将任务迭代地分解为子任务,直至子任务可以直接计算出结果
任务和分解后的子任务具有相似性(算法相同,只是计算的数据规模不同,往往采用递归算法)
结果合并:逐层合并子任务的执行结果,直至获得最终结果
Fork/Join概述
Fork/Join是并行计算的框架,主要用来支持分治任务模型,Fork对应任务分解,Join对应结果合并
Fork/Join框架包含两部分:分治任务ForkJoinTask + 分治任务线程池ForkJoinPool
类似于Runnable + ThreadPoolExecutor
ForkJoinTask最核心的方法是fork和join
fork:异步地执行一个子任务
join:阻塞当前线程,等待子任务的执行结果
ForkJoinTask有两个子类:RecursiveAction + R ...
Java并发 -- CompletionService
场景1234567// 从3个电商询价,保存到数据库,串行执行,性能很慢int p1 = getPriceByS1();save(p1);int p2 = getPriceByS2();save(p2);int p3 = getPriceByS3();save(p3);
ThreadPoolExecutor + Future1234567891011ExecutorService pool = Executors.newFixedThreadPool(3);Future<Integer> f1 = pool.submit(() -> getPriceByS1());Future<Integer> f2 = pool.submit(() -> getPriceByS2());Future<Integer> f3 = pool.submit(() -> getPriceByS3());int p1 = f1.get(); // 阻塞,如果f2.get()很快,但f1.get()很慢,依旧需要等待pool.execute(() -> save(p1) ...
Java并发 -- CompletableFuture
泡茶烧水
123456789101112131415161718192021222324CompletableFuture<Void> f1 = CompletableFuture.runAsync(() -> { System.out.println("T1: 洗水壶"); sleep(1, TimeUnit.SECONDS); System.out.println("T1: 烧开水"); sleep(15, TimeUnit.SECONDS);});CompletableFuture<String> f2 = CompletableFuture.supplyAsync(() -> { System.out.println("T2: 洗茶壶"); sleep(1, TimeUnit.SECONDS); System.out.println("T2: 洗茶杯"); sleep(2, TimeUnit.SECON ...
Java并发 -- Future
ThreadPoolExecutor12345678910// 无法获取任务的执行结果public void execute(Runnable command);// 可以获取任务的执行结果// Runnable接口的run()方法没有返回值,返回的Future只能断言任务是否已经结束,类似于Thread.join()public Future<?> submit(Runnable task)// Callable接口的call()方法有返回值,可以通过调用返回的Future对象的get()方法获取任务的执行结果public <T> Future<T> submit(Callable<T> task);// 返回的Future对象f,f.get()的返回值就是传给submit方法的参数resultpublic <T> Future<T> submit(Runnable task, T result)
Future1234567891011// 取消任务boolean cancel(boolean mayInterruptIfRunn ...
Java并发 -- 线程池
创建线程
创建普通对象,只是在JVM的堆里分配一块内存而已
创建线程,需要调用操作系统内核的API,然后操作系统需要为线程分配一系列资源,成本很高
线程是一个重量级对象,应该避免频繁创建和销毁,采用线程池方案
一般的池化资源12345678910111213141516// 假设Java线程池采用一般意义上池化资源的设计方法class ThreadPool { // 获取空闲线程 Thread acquire() { } // 释放线程 void release(Thread t) { }}// 期望的使用ThreadPool pool;Thread T1 = pool.acquire();// 传入Runnable对象T1.execute(() -> { // 具体业务逻辑});
生产者-消费者模式业界线程池的设计,普遍采用生产者-消费者模式,线程池的使用方是生产者,线程池本身是消费者
123456789101112131415161718192021222324252 ...
Java并发 -- 原子类
add12345678private long count = 0;public void add() { int idx = 0; while (idx++ < 10_000) { count += 1; }}
add()方法不是线程安全的,主要原因是count的可见性和count+=1的原子性
可见性的问题可以用volatile来解决,原子性的问题一般采用互斥锁来解决
无锁方案12345678private AtomicLong atomicCount = new AtomicLong(0);public void atomicAdd() { int idx = 0; while (idx++ < 10_000) { atomicCount.getAndIncrement(); }}
无锁方案相对于互斥锁方案,最大的好处是_性能_
互斥锁方案为了保证互斥性,需要执行加锁、解锁操作,而加锁、解锁操作本身会消耗性能
拿不到锁的线程会进入 ...
Java并发 -- 并发容器
同步容器
Java 1.5之前提供的同步容器虽然也能保证线程安全,但性能很差
Java中的容器主要分为四大类,分别为List、Map、Set和Queue,并不是所有的Java容器都是线程安全的
将非线程安全的容器变成线程安全的容器的简单方案:synchronized
把非线程安全的容器封装在对象内部,然后控制好访问路径即可
线程安全的ArrayList12345678910111213141516171819public class SafeArrayList<T> { private List<T> list = new ArrayList<>(); public synchronized T get(int idx) { return list.get(idx); } public synchronized void add(int idx, T t) { list.add(idx, t); } public synchronized boolea ...
Java并发 -- CountDownLatch + CyclicBarrier
对账系统
1234567891011// 存在未对账订单while (existUnreconciledOrders()) { // 查询未对账订单 pOrder = getPOrder(); // 查询派送订单 dOrder = getDOrder(); // 执行对账操作 Order diff = check(pOrder, dOrder); // 将差异写入差异库 save(diff);}
性能瓶颈
getPOrder()和getDOrder()最为耗时,并且两个操作没有先后顺序的依赖,可以并行处理
简单并行 - join
123456789101112131415161718192021222324// 存在未对账订单// 存在未对账订单while (existUnreconciledOrders()) { // 查询未对账订单 Thread t1 = new Thread(() -> { pOrder = getPOrder(); }); t1. ...