并发编程20
回顾
指令重排的原理
-
// 场景:在多核CPU下,a被缓存到core2下面 // core1首先把a=1执行完,然后放到store buffer中 // // 修改变量a,异步通知core2, a=1; // load变量a,此时可能是0,导致b是一个错误的值 b=a+1; if(b==1){ doSomething(); } core1首先把a=1执行完, core1 core2 | | | | |--> 先存到store buffer中 交互比较的频繁 | | V V cache-同步a失效的通知(a==1时执行)-> cache | ^ | | ----> b= a+1 ----
-
异步的store buffer,导致最终执行的效果看上去像进行了指令重排
-
怎么解决?执行b=a+1时,从store buffer中拿,加1个内存屏障
多线程效率
-
什么时候采用多线程?多线程效率问题?
-
通过jmh测试
-
怎么创建jmh?--通过下面的命令创建一个项目 mvn archetype.generate -DinteractiveMode=false -DarchetypeGroupId=org.openjdk.jmh -DarchetypeArtifactId=jmh-java-benchmark-archetype -DgroupId=enjoy -DartifactId=enjoy -Dversion=1.0 // 打jar包 mvn clean pakage // 测试多线程速度 java -jar -Xms1g benchmark.jar // 多核情况下 c方法:0.020 d方法:0.041 // 单核情况下 // 通过虚拟机配置实现单核效果 c方法和d方法实现效果差不多,实际单线程效果更好,因为多线程存在线程切换,而线程切换很消耗性能 多线程跟CPU硬件相关,CPU核数越多效率越高
-
import org.openjdk.jmh.annotations.*; import java.util.Arrays; import java.util.concurrent.FutureTask; /** * 什么时候采用多线程 * * 多线程的效率问题 */ @Fork(1) @BenchmarkMode(Mode.AverageTime) @Warmup(iterations=3) // 预热3次 @Measurement(iterations=5) // 执行5次 public class MyBenchmark { // 创建了个一亿的数组 static int[] ARRAY = new int[1000_000_00]; static { // 往ARRAY这个数组里面填充1 Arrays.fill(ARRAY, 1); } /** * 4个线程 对1亿数据累加 * * 分而治之 普通线程池不会有任务密取----不能充分利用多核CPU的并行特点 * * * @return * @throws Exception */ @Benchmark public int c() throws Exception { int[] array = ARRAY; //1 FutureTask<Integer> t1 = new FutureTask<>(()->{ int sum = 0; for(int i = 0; i < 250_000_00;i++) { sum += array[0+i]; } return sum; }); //1s FutureTask<Integer> t2 = new FutureTask<>(()->{ int sum = 0; for(int i = 0; i < 250_000_00;i++) { sum += array[250_000_00+i]; } return sum; }); FutureTask<Integer> t3 = new FutureTask<>(()->{ int sum = 0; for(int i = 0; i < 250_000_00;i++) { sum += array[500_000_00+i]; } return sum; }); FutureTask<Integer> t4 = new FutureTask<>(()->{ int sum = 0; for(int i = 0; i < 250_000_00;i++) { sum += array[750_000_00+i]; } return sum; }); new Thread(t1).start(); new Thread(t2).start(); new Thread(t3).start(); new Thread(t4).start(); return t1.get() + t2.get() + t3.get()+ t4.get(); } /** * 单线程 对1一亿次累加 * @return * @throws Exception */ @Benchmark public int d() throws Exception { int[] array = ARRAY; FutureTask<Integer> t1 = new FutureTask<>(()->{ int sum = 0; for(int i = 0; i < 1000_000_00;i++) { sum += array[0+i]; } return sum; }); new Thread(t1).start(); return t1.get(); } }
-
forkjoin除了分而治之(任务拆分),还有任务窃取(有的线程空闲时可以去窃取任务)
forkjoin的api理解
-
package BingFaBianCheng.bingFaBianCheng20.xx; import lombok.extern.slf4j.Slf4j; import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; import java.util.concurrent.atomic.AtomicBoolean; /** * 工作原理 * * * v1 直接邀请v1用户10个或者团队人数达到30个 升级到v2 * v2 * v3 * v4 * v5 * v6 * * 现在有一个人 N 注册 * 假设是v1 可能会触发很多升级 此时不需要返回结果 用RecuriveAction * */ public class TestForkJoin { public static void main(String[] args) { // AtomicBoolean // forkjoin 本身是一种线程池 // 创建出的线程池的线程数是4个 ForkJoinPool pool = new ForkJoinPool(4); // runable // 需要自己写一个类继承FutureTask // RecursiveTask类继承了FutureTask // RecursiveAction是没有返回值的 // Task1方法里面实现了1-5的累加 // 里面是递归实现的 System.out.println(pool.invoke(new Task1(5))); //5+task1(4) 4+task(3) 3+taks(2) 2+(taks1) // System.out.println(pool.invoke(new Task3(1, 5))); } } @Slf4j(topic = "e.task1") class Task1 extends RecursiveTask<Integer> { int n; public Task1(int n) { this.n = n; } @Override public String toString() { return "{" + n + '}'; } @Override protected Integer compute() { if (n == 1) { log.debug("join() {}", n); return n; } Task1 t1 = new Task1(n - 1); //分叉 t1.fork(); log.debug("fork() {} + {}", n, t1); int result = n + t1.join();//update log.debug("join() {} + {} = {}", n, t1, result); return result; } } @Slf4j(topic = "e.AddTask") class Task3 extends RecursiveTask<Integer> { int begin; int end; public Task3(int begin, int end) { this.begin = begin; this.end = end; } @Override public String toString() { return "{" + begin + "," + end + '}'; } @Override protected Integer compute() { if (begin == end) { log.debug("join() {}", begin); return begin; } if (end - begin == 1) { log.debug("join() {} + {} = {}", begin, end, end + begin); return end + begin; } int mid = (end + begin) / 2; Task3 t1 = new Task3(begin, mid); t1.fork(); Task3 t2 = new Task3(mid + 1, end); t2.fork(); log.debug("fork() {} + {} = ?", t1, t2); int result = t1.join() + t2.join(); log.debug("join() {} + {} = {}", t1, t2, result); return result; } }
-
比较low的一种任务拆分
-
线程t1 5+task(4) t1执行最后的join,并返回 | ^ fork | | join V | 线程t2 4+task(3) t2执行join,并返回 | ^ fork | | join V | 线程t3 3+task(2) t3执行join,并返回 | ^ fork | | join V | 线程t0 2+task(1) t0执行join,并返回 | ^ V | 实际只有join(1),由t3执行,并返回
-
Task3是一种二分的拆法,更加高效
-
@Slf4j(topic = "e.AddTask") class Task3 extends RecursiveTask<Integer> { int begin; int end; public Task3(int begin, int end) { this.begin = begin; this.end = end; } @Override public String toString() { return "{" + begin + "," + end + '}'; } @Override protected Integer compute() { if (begin == end) { log.debug("join() {}", begin); return begin; } if (end - begin == 1) { log.debug("join() {} + {} = {}", begin, end, end + begin); return end + begin; } int mid = (end + begin) / 2; Task3 t1 = new Task3(begin, mid); t1.fork(); Task3 t2 = new Task3(mid + 1, end); t2.fork(); log.debug("fork() {} + {} = ?", t1, t2); int result = t1.join() + t2.join(); log.debug("join() {} + {} = {}", t1, t2, result); return result; } }
-
10亿的累计通过forkjoin实现
-
package BingFaBianCheng.bingFaBianCheng20.xx; import lombok.extern.slf4j.Slf4j; import java.util.ArrayList; import java.util.List; import java.util.concurrent.*; import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; import java.util.stream.LongStream; @Slf4j(topic = "e") public class ForkJoinTest2 extends RecursiveTask<Long> { private Long start; // 1 private Long end; // 1990900000 // 临界值 // 每一个任务只执行10000个数的相加 private Long temp = 10000L; public ForkJoinTest2(Long start, Long end) { this.start = start; this.end = end; } // 计算方法 @Override protected Long compute() { ReentrantLock lock = new ReentrantLock(); //tiao Condition condition = lock.newCondition(); lock.newCondition(); //小于一万了就不再拆了 if ((end-start)<temp){ Long sum = 0L; for (Long i = start; i <= end; i++) { sum += i; } return sum; }else { // forkjoin 递归 long middle = (start + end) / 2; // 中间值 ForkJoinTest2 task1 = new ForkJoinTest2(start, middle); task1.fork(); // 拆分任务,把任务压入线程队列 ForkJoinTest2 task2 = new ForkJoinTest2(middle+1, end); task2.fork(); // 拆分任务,把任务压入线程队列 return task1.join() + task2.join(); } } public static void main(String[] args) throws ExecutionException, InterruptedException { //6596 // test1(); //4872 // test2(); //160 test3(); } /** * 单线程的直接10亿累加 */ public static void test1(){ Long sum = 0L; long start = System.currentTimeMillis(); for (Long i = 1L; i <= 10_0000_0000; i++) { sum += i; } long end = System.currentTimeMillis(); System.out.println("sum="+sum+" 时间:"+(end-start)); } //最基本的forkjoin 二分查找拆分任务 //拆分任务 关键在于算法 你自己写 public static void test2(){ long start = System.currentTimeMillis(); ForkJoinPool forkJoinPool = new ForkJoinPool(); ForkJoinTask<Long> task = new ForkJoinTest2(0L, 10_0000_0000L); Long sum = forkJoinPool.invoke(task); // ForkJoinTask<Long> submit = forkJoinPool.submit(task);// 提交任务 // Long sum = submit.get(); long end = System.currentTimeMillis(); System.out.println("sum="+sum+" 时间:"+(end-start)); } public static void test3(){ long start = System.currentTimeMillis(); // jdk8 stream流式计算 // 内部实现的更加优雅的拆分方法 long sum = LongStream.rangeClosed(0L, 10_0000_0000L).parallel().reduce(0, Long::sum); long end = System.currentTimeMillis(); System.out.println("sum="+sum+"时间:"+(end-start)); } }
-
forkjoin使用cpu密集型计算,需要通过算法设计更加优秀的拆分
gc实现
- 内存分配运行时数据区—c语言的内存分配
- c语言实现堆 c语言实现栈
- 标记清除
- 当内存不够的时候回收
高并发的问题
-
分布式、redis(缓存)、mq、微服务、nginx(负载均衡)
-
高并发与讲的课程并发编程是两回事
-
并发编程解决的问题
- 数据迁移,第三方对接,一亿个用户,表结构不同,采用多线程解决
- excel 3g 商品数,解析excel
待解决内容
- 无界队列—源码
- threadLocal—源码
- completableFuture
- CopyOnWrite 并发容器—一种设计模式、以及结合mq或者redis怎么使用
- mq的队列进行操作,队列长度达到一定长度后删除数据。。。
- disruptor
一些重点
-
currentHashMap
-
线程池如何保证核心线程不被销毁
- 核心线程是阻塞创建
- 空闲线程是超时机制创建
-
synchronized关键字底层原理
-
synchronized与lock的区别
-
公平锁和非公平锁
- 公平锁是一朝排队,永远排队
- 非公平锁,永远不排队,直接拿锁,但是进入队列后就没有公平锁和非公平锁的区别了,都是根据顺序执行了
-
volatile
- 为jmm服务的一个关键字,主要解决可见性和重排序,所有的重排序都是为了可见性服务
- 重排序有三个级别,编译器级别、执行引擎级别、内存级别
-
aqs—抽象的同步队列
- 说出几个关键结构,node内部类(head、tail双向队列),state记录锁的状态,判断是不是等于0,等于0是自由状态,直接获取锁,exclusiveThread是持有锁的线程,还有唤醒机制(park机制(parkEvent.park方法)—系统调用—重量锁(用户态到内核态的切换))
-
synchronized关键字
- mutext_lock
- spinlock(linux中的函数),java实现的是通过死循环,自旋锁需要站在两个角度考虑,os级别,大部分系统都实现了自旋,语言级别,可以通过死循环实现,lock这把锁中的自旋是通过for(;;)实现
-
Condtion原理(lock.newCondition())
- 获取锁拿不到锁的线程,会去entrylist中等待,拿到锁但是执行条件不满足会进入waiting,满足条件后去竞争队列排队,等待获取到资源就执行
-
解决高并发—并发安全问题、雪崩问题
- 集群,分布式、微服务(最大的优点是容错)
- 架构设计—多维度解决问题—熔断其他业务
- 业务代码,mq集群、redis集群
-
4个微服务,买两台应用服务器(最节约最合理的情况,贵一点的1w多一个月),一台mq,一台redsi,一台数据库,一台网管服务器(包含netty服务)
- 高可用雪崩一般是tomcat崩了,所以两台服务器一般够了
- mq—死信路由(死信交换机),死信队列不严谨