- 基础概念
- 进程、线程、协程
- Ques:
- 单核cpu设置多线程有意义嘛
- cpu密集型和 io 密集型
- 工作线程数(线程池中线程数量)是不是越大越好?设多少合适?
- 并发编程的三大特性
- 可见性
- 三级缓存
- volatile 保证可见性
- 有序性
- 对象创建过程
- volatile 防止指令重排
- 原子性
- 乐观锁(无锁、自旋锁)
- CAS 的 ABA 问题
- CAS 比较并交换的过程中如何保障线程安全的呢?
首先来讲下这三种的区别。举个例子,我们启动我们的 xx.exe ,首先是会在内存当中开辟一块空间给这个程序加载到内存当中,要启动它的话,我们的系统要找到这个程序内部的主线程进行运行。 定义: 进程是 *** 作系统进行资源分配的基本单位 线程是调度执行的基本单位,多个线程共享一个进程的资源 协程/纤程 是绿色线程,即用户管理的而不是 *** 作系统管理的线程Ques: 单核cpu设置多线程有意义嘛
有意义,线程有些 *** 作(等数据,调io啥的)不消耗cpucpu密集型和 io 密集型
cpu密集型程序指的是对cpu利用率高(大量时间用于cpu计算) io密集型(cpu利用率低(大量时间用于io调度))工作线程数(线程池中线程数量)是不是越大越好?设多少合适?
不是 ,具体线程数一般要通过模拟实际情况进行压测 公式: N(threads) = N(cpu) *U(cpu)*(1+W/C) N(cpu) 处理器的核数 U(cpu) 期望的CPU利用率 W/C 等待时间与计算时间的比率 W/C咋确定? Profiler(性能分析工具) 测算 java JProfiler Arthas (远程)并发编程的三大特性 可见性
线程将数据从主存加载到本地缓存,以后的 *** 作就读本地缓存的数据。这个时候如果有第二个线程对这个数据进行修改,第一个线程能否看到被修改的值的问题,就是并发编程的可见性问题。 针对可见性问题,我们先讲一下三级缓存:三级缓存
如图有两颗cpu,每颗cpu有两个核,
多个cpu共享内存,每个cpu独享L3缓存,
每个核独享L1,L2缓存,cpu内核共享L3缓存。
每个线程读数据的时候,会先从内存中奖数据加载到L3缓存中->L2->L1,读的时候则是从L1->l2->L3到内存
补充: 空间局部性原理:当我们用到某个值的时候,我们很快会用到相邻的值; 时间局部性是指如果程序中的某条指令一旦执行,则不久之后该指令可能再次被执行;如果某数据被访问,则不久之后该数据可能再次被访问。 因此读数据的时候会将身边的值一并读到缓存当中。一般是一个缓存行大小。一个缓存行64字节。所以 *** 作数据的时候,为了防止数据不一致,出现了缓存一致性原理。 (java8当中有@Contended注解(后续版本取消了),可以了解一下,就是将数据前后填满,保证只会读到这个数据,浪费空间换时间) 注意:缓存一致性协议≠MESI,MESI只是微软的缓存一致性协议,比较有名而已。感兴趣的可以去了解了解volatile 保证可见性
1.volatile修饰的内存,对于它的每次修改,都可见--->进行修改也同步修改主存中数据,同时通知其他用到的线程重新load数据 2.volatile修饰引用类型 (对另外的线程还是不可见)-->一般不会出现volatile修饰引用类型有序性
并发编程有序性问题:为了提高执行效率,Cpu可能会乱序执行 乱序执行的条件:as-if-serial ->不影响单线程的最终一致性对象创建过程
解释几条重要的指令 0::new->申请一块内存,设置默认值(半初始化状态) 3:特殊调用,这边是调用初始化方法 4:建立关联 引用对象和对象建立关联(位于栈内栈帧中的引用类型指针指向堆)
在实际运行当中,这些指令3、4的顺序可能会重排序–>可能会出现this溢出的情况(没有初始化已经关联起来,当场拿到值是第一步初始化的默认值)
happens-before原则 JVM规定了8种情况不允许重排(这方面内容此处跳过,感兴趣的可以去百度搜索看看)volatile 防止指令重排
volatile实现细节 jvm层面 在指令间加内存屏障,屏障两边的指令不给重排 JVM层级 LOADLOAD 读读屏障 上面读和下面读不允许换序 其他不限制 STORESTORE 写写屏障 LOADSTORE 读写屏障 STORELOAD 写读屏障原子性
原子性是指一个线程的 *** 作是不能被其他线程打断,同一时间只有一个线程对一个变量进行 *** 作。 这边先介绍几个基础概念 1.rare condition 竞争条件--->多个线程访问共享数据产生竞争 2.unconsistency 数据不一致 3.上锁的本质:将并发编程序列化,将并发 *** 作变成顺序化 *** 作 将锁内部的东西当一个原子执行 4.monitor 管程 (锁) 5.critical section 临界区 锁住的大括号内部的 如果临界区执行时间长,语句多,叫做锁锁的粒度比较粗,泛指,锁的粒度比较细
所谓上锁,就是保障临界区 *** 作的原子性(atomicity)
乐观锁(无锁、自旋锁)CAS *** 作:compare and swap/set/exchange 比较并且交换
就是一个线程对数据 *** 作时, *** 作过后将我这原来的值和内存当中的值进行比较,如果相同,则将新的值更新到内存当中 (细心的哥们可能发现了,这边的 *** 作仍然可能有坑在读和更新中间,这个时候要是有人抢先一步更新了,是否还会有多线程问题呢?这个问题如何解决的,我们等下再讲)CAS 的 ABA 问题
讲个通俗的例子,你出差了,然后临走前看了下家里的样子,出差过程中,你的某个亲戚把房子卖了,中间转了99手,最后你亲戚心虚把房子又买回来了,你回来之后,一对比,家还是那个家,但是总感觉有些地方不对了,这就是cas的aba问题
aba问题的解决办法也很简单,加个版本号就行。就是你回来一看,这房子版本号99+了已经和你的不一样了,那你就知道有问题了。对比原本数据的时候同时对比版本号CAS 比较并交换的过程中如何保障线程安全的呢?
大家可以debug下atomic类,举个例子。atomicInteger
点开unsafe,可以看到这里有几个cas的方法,但是这个是native c++的本地方法。
这边直接给大家说结论,感兴趣的可以去整Hotspot源码debug一下 这里最后是到一个 Atomic::cmpxchg 方法中,当中会有if_mp的判断 ,mp是multi processor(多处理器)的意思。 在汇编语言中会执行 lock cmpxchg指令。cmpxchg(不是原子的) 所以lock这条指令给了个锁,锁定了一个信号啥的。(锁定了一个北桥信号)
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)