并发编程----2.并发编程其他基础知识
2.1 什么是多线程并发编程
并发和并行的概念:并发指的是在单核cpu下,同一时间段内多线程运行。并行指的是多核cpu在同一时间点内多线程运行。
2.2 为什么是多线程并发编程
虽然多核cpu的到来,减少了cpu切换上下文的开销,但是处理海量数据和请求的要求,这些都对于高并发编程有着急切的需求
2.3 Java中的线程安全问题
多个线程访问同一共享变量却没有进行同步操作,导致脏数据的出现
2.4 java中共享变量的内存可见性问题
(1)cpu内存模型:线程拥有各自的私有内存,所有cpu共享的二级缓存,主内存
(2)线程操作共享变量:线程将查看私有内存是否存在相关变量,如果存在,刷新后复制到主存中,如果二级缓存中存在复制到
私有内存,刷新后复制到主存中。如果不存在,复制主存中变量到私有内存中,刷新复制到二级缓存,刷新到主内存。
2.5 java中synchronized关键字
(1)synchronized 关键字介绍
1. synchronized块是java提供的一种原子内置锁,也可以看做同步锁,这些java内置使用者看不到的内置锁成为内
部锁,也叫监视器锁。
2. 当线程进入synchronized块中便会获得内置锁,其他线程进入就会被挂起,在获得锁的线程顺利执行完,或者使用
wait放弃当前的锁他会释放该锁。
3. synchronized也是一种排他锁,当一个线程获得该锁时,其他线程要获取会被阻塞挂起。
4. synchronized阻塞其他锁是一个用户态切换到内核态的过程,而synchronized的使用就会导致上下文的切换。
(2)synchronized的内存定义
1. 进入synchronized内存语义:将线程的私有内存删除直接从主内存中获取变量
2. 退出synchronized内存语义:在synchronized中直接把变量的修改刷新到主内存中
(3)原理
1. 前提:每个对象都有一个监视器锁,当监视器锁被占用的时候就会处于锁定的状态
2. 方法同步:通过acc_synchronized是否被设置,如果设置了执行线程先获取对象监视器,获取成功了执行方法体,
方法执行完再释放对象监视器。再方法执行期间,其他任何线程都无法获得同一个监视器对象。
3. 代码块同步:monitorenter 和monitorexit 指令实现
4. 重入型:每个对象拥有一个计数器,当线程获取该对象锁的后,计数器加一,释放后减一
2.6 java中volatile关键字
synchronized 阻塞其他锁,会造成上下文切换的开销,所以java提供了弱形式的同步volatile。
内存语义为:删除私有内存中的变量获取主内存直接刷新主内存
(1) 使用volatile的情况
1. 读写变量值是没有加锁的
2. 写入变量的时候不依赖当前值,因为volatile不支持原子性。
2.7 java中的原子操作
原子操作:要么都执行要么都不执行
2.8 java中的cas操作
解决排他锁和独占锁中因上下文切换带来的消耗问题
(1)cas(Object object,long valueOffset,long expect,long update)方法:object对象的内存位置,valueOffset
偏移量,expect期望值,更新值。根据对象的内存位置和偏移量计算当前变量的值,如果和期待值相等就更新,否则不更新
(2)ABA问题:当线程A更新变量a从0到1又更新回0,这时候线程B的期待值是0,这就不是当时那个0了。
(3)解决ABA问题:AtomicStampedReference 类为每个变量配置了时间戳。
*2.9 Unsafe类
- 之后会和后面的详细讲一下,很总要的知识点
2.10 Java指令重排序
int a=1;(1)
int b=2;(2)
int c=a+b;(3)
指令重排序只会保证(1)(2)在(3)前面执行而不会保证(1)和(2)的先后顺序
2.11 伪共享
为了解决cpu和主内存之间的运行速度差之间的关系,在cpu和主内存中会存在一个一级缓存或者多级高速缓存存储器,也就是上文中的二级缓存,该存储器中存储着多个变量,内部按存储,缓存行和主内存进行数据交换的单位。
(1)什么是伪共享
1. 多个变量存储到同一个缓存行,但是同一个缓存行同一时间只能由一个线程访问,相比于把一个变量放入一个缓存行
性能大大消耗
(2)为什么会出现伪共享
1. 多个变量存储到同一个缓存行,多个线程去访问同一个缓存行。
(3)如何避免伪共享
1. 缓存行一共64字节,填充相应的字节
2.12 锁的概述
(1)乐观锁和悲观锁
1. 乐观锁:与悲观锁相反,在修改的过程中认为数据不会被其他线程修改
2. 悲观锁:对外界的修改保持悲观的态度,认为在线程修改变量的过程一定有其他线程进来修改
(2)公平锁和非公平锁
1. 公平锁:按请求锁的时间顺序执行的锁,会带来额外的开销
2. 非公平锁:按优先级来
(3)独占锁和共享锁
1. 独占锁:任何时候只能一个线程独占锁ReentrantLocak
2. 共享锁:ReadWriteLock读写锁
(4)可重入锁
1. 定义:一个线程再次获得自己已经独占的锁时候不会被阻塞
2. 原理:当一个线程获得一个锁的时候,会标示该线程,并且记录线程进入的次数。当次数为0时,标示便记录为null,
当进入是加一。
(5)自旋锁/自适应自旋锁
1. 自旋锁定义:一个线程占用锁,而另一个线程想要请求时会被阻塞而不马上阻塞自己,线程不放弃cpu的执行权,在指定的次数内
请求该锁,如果无法请求到便阻塞,这种方法以cpu的占用来换取上下文切换的消耗。
缺点:
1. 很多线程,很少处理器,自旋存在浪费
2. 密集型任务,自旋占用cpu得不偿失
3. 锁持有的时间长,白白浪费自旋占用的cpu时间
2. 自适应自旋锁:如果一个线程已经经常通过自旋的方式获取过某一个锁,现在该锁正在被其他线程持有,虚拟机便认为该锁有机会通
过自旋的方式获取,进而他将拥有更长的自旋时间,相反的,这个线程通过自旋方式获取该锁的次数比较少,成功率低的话,虚拟
机将会减少他自旋的时间,甚至取消自旋的过程。
(6)偏向锁->轻量级锁->重量锁
1. 偏向锁:无竞争情况下,且将来只有第一个申请锁的线程会使用锁
2. 轻量锁:无实际竞争,多个线程交替使用锁,允许短时间的锁的竞争
3. 重量锁:有实际的竞争,且锁竞争的时间长
膨胀式的锁分配策略
4. 定义:一个线程频繁访问一段同步代码,这个线程在访问的时候就可以立刻获得该锁,其他线程想要获得该锁的时候,该锁
就转变为轻量级锁,其他线程通过自旋的方式获取该锁,如果自旋到一定次数没办法获取该锁的时候锁就会转化为重量级锁