8.3笔记(多线程1)
并发编程的三要素
-
原子性
单个线程的内部操作,要么都完成;要么都不完成
-
可见性
是指多个线程操作一个变量时,其中一个线程对变量进行修改后,其他线程会立刻看到最新的结果
通常使用synchronized或者Lock来保证一个时刻只用一个线程获取锁,得到锁才能去获取资源。在锁释放之前会把新的值刷新到主内存,从而实现共享变量的可见性
-
有序性
按照程序的执行顺序要按照代码的顺序
synchronized,Lock,volatile
首先在java的内存模型中,每个线程内部都有一份本地内存,而这这些本地内存可以看作主内存的备份,本地内存也是线程的工作空间。当线程操作完成后,会把变量的值刷新到主内存。

-
volatile:类型修饰符。他的作用是用来修饰不同线程操作的变量,
- 保证了不同线程对变量操作的可见性
- 禁止指令重排
//线程1 boolean stop =false; while(!stop){ doSomething(); }//线程2 stop =true;比如在线程1执行时,想用线程2去中断线程1。因为每个线程都有自己的工作内存,虽然线程2修改了stop,但是仅仅是在线程2的工作内存修改了值,并没有刷新到主内存,所以对线程1并没有任何影响。
但是!如果stop被volatile修饰了:
- 首先volatile会强制把修改的值写入到主内存
- 被volatile后,线程2修改了stop的值;线程1的stop的值直接无效,要重新去主内存中获取stop的值
- 所以线程1就可以得到最新的值
加入volatile关键字后,生成的汇编代码会多出一个lock的前缀指令
这个前缀指令(又叫内存栅栏)的功能有:
- 它确保了指令重排不会把后面的指令排到内存屏障之前的位置;也不会拍到内存屏障之后的位置;在执行这条内存屏障时,它前面的指令均已执行完毕
- 它会强制把工作缓存的修改操作到主内存中
- 如果是写操作,会导致其他的CPU中对应的缓存无效
-
synchronized:通常放在范围操作符(public等)之后,和返回类型之前
使用synchronized的原因:保证了只用一个线程可以执行某个代码块和函数,也保证了可见性
使用方法
- 在方法上加锁
- 在对象上加锁
- 在一个代码块前加锁
wait() 与notify()/notifyAll():
- wait():释放当前的锁,线程进入等待池并释放CPU,其他的线程也可以竞争这把锁;而sleep()不同,它会先让线程进入休眠阶段,也会暂时释放CPU但是不会释放锁,也就是说在休眠时期,别的线程是无法竞争锁的。当休眠结束,线程会重新获取CPU,回归到运行态
- notify():会唤醒调用过wait()的线程,把该线程放进等待池中,从而获得竞争锁的权力;否则是不能去竞争锁的。notify的作用就是唤醒线程,就是把线程从等待池移动到锁池
- notifyAll():和notify作用相同,只不过它把全部线程都唤醒了,而notify只是会随机唤醒一个等待池里的线程
synchronized的优化:
synchronized的最大特征在于同一时刻只有一个线程进入临界区,即排他性。由于排他性的效率的低下,所以通过减少获取锁的是时间就可以对synchronized进行优化
通过CAS操作CAS操作:
- 概念:通常线程获取锁采取了悲观锁的策略(假设每一次进入临界区都会有冲突),而CAS是一种乐观锁策略(所有线程在访问共享资源都不会产生冲突),以这种策略下,即使出现了冲突,线程也不会出现阻塞停顿的情况
- 应用:CAS有3个操作数,当且仅当A和V相同时才会修改B的值
- V:内存值
- A:旧的预期值
- B:要修改的新值
- 缺点
- ABA问题:因为CAS要在操作值的时候检查值知否发生改变,如果改变才会更新。但是如果A变化到B,B有变回了A,那么CAS检查的结果就是没有变化,但是它是经过变化的,中间过程不能被忽略。解决ABA的思想就是就是使用版本号,在变量前面加上版本号,每次更新都加一
- 循环时间开销大:由于CAS是实现自旋锁的基础,就是无限循环,只有当操作成功CAS返回true后才会结束循环。那么如果CAS一直都不成功,就会给CPU带来巨大的开销。我们可以使用pause指令来提高效率
- 只能保证一个共享变量的原子操作:CAS只能保证一个共享变量的原子性,但是无法保证多个循环变量的原子性。我们可以通过把多个变量合并成一个变量(java的封装)
-
Lock:除了实现了synchronized类似的同步,也提供了比synchronized更灵活的API的实现

-
AQS:Lock实现线程安全的核心在于 AQS,AQS提供了一个FIFO队列,就是一个实现锁和同步功能的框架。
AQS分为独占和共享两种
- 在独占模式下,每次只能有一个线程持有锁
- 在共享模式下,允许有多个线程同时获取锁
AQS的内部实现:
AQS内部依赖一个同步队列(FIFO双向队列)来管理同步状态的管理,当线程获取同步状态失败后,同步器会把当前线程的所有信息构造成一个节点并加入同步队列,同时阻塞当前线程
-

浙公网安备 33010602011771号