1.volatile关键字与内存可见性
本质是写的东西没有同步到读缓存
主线程没有更新缓存数据的时间,所以内存里的改动同步不过来
一直读的的缓存里的数据,内存的数据被写了还是读不到
同步锁可以解决,但是效率低
volatile:多个线程共享内存数据,保证内存中的数据是可见的。使用内存障碍,保证从主存拿数据
volaile修饰以后,jvm不能重排序了,相较于synchronize轻量级。不需要挂起线程可以一直执行,没有挂起、恢复消耗
但是
- 没有互斥性
- 不能保证变量的原子性(不可分割)
2.原子变量与CAS算法
本质是写的过程被分割
i++ 读改写三步,可能保证内存可见性,但是写内存同步数据,无法互斥,会有重复操作污染数据。i++操作被分割开来,没有原子性了
同时写内存,两条线一样的值同时发生改变成一样的,后写的覆盖了先写的
juc的atomic包
多个线程同时先从内存读一个数值,当做A,传进去,进行操作的从内存读一下V,一样就设置为更新值B 。读取内存和比较更新是原子操作不可分割,硬件支持
这就保证了,同时操作,只有一个会成功
效率比锁高,失败了就重试,不会阻塞流程
3.模拟CAS算法
3.ConcurrentHashMap锁分段机制
HashTable整个方法执行的收都带锁,具有隐患:复合操作可能被切割,执行一半暂停,回来后状态变了,所以内部加锁没有用,要整个操作加锁。此外并行变串行,效率更低
复合操作本质上是一句话的事情分为两个方法执行,被切割非原子
ConcurrentHashMap给数据分段加锁,16个段16把锁
1.8用CAS替代分段锁
全体方法加锁可以使用Collections.synchronizedList
这样在使用迭代器访问并修改集合会有并发修改异常。
因为迭代next和add操作被切割,写入的时候别人可能在读东西,一写之后再读取就出错(因为可能加在头部,再读迭代器指针就对不上,还读了上一个)。在迭代的过程中,添加和删除元素都会报错,通过modCount这个变量来校验
使用CopyOnWriteArrayList解决,每次写入时复制新的列表再添加,读的时候读取旧的列表,两不相干。适合读操作多的时候
4.CountDownLatch,闭锁
5.创建线程的第三种方式,实现Callable接口
Callable接口给FutureTask,FutureTask给Thread。FutureTask的get可以实现闭锁效果,等待Callable的call方法执行完再获取结果
Future实现了Runnable接口
7.同步锁
有风险,要在finally里调用,不是百分百被调用
8.生产者消费者案例:虚假唤醒
生产者不停地发数据,消费者不停地接数据,没有等待,可能过度生产与消费
如果用if esle而不是if结构,会有以下问题
B缺
A
B
B缺
A
A卡住等待唤醒
本质是B跑的比较快,消费完了马上等待生产但又退出
解决办法是等待、通知后直接进行操作,不要分开,AB或者BA有序
wait和notifyAll
所以要加入等待唤醒机制,生产满了等待,生产了通知,消费完了等待,消费了通知
虚假唤醒:多个等待,一次唤醒后多个源做同一操作,超出了限制。wait要在循环中使用,不要用if用while,这样多一次判断,A、B线程同时等待,A线程执行了B线程可以在条件不满足的时候限制不要顺序执行重新等待。
注意:当线程在某个条件变量下等待时,即使其他线程没有broadcast or signaled 这个条件变量,该线程仍然可能被唤醒,在多核处理器系统下,使条件变量完全可以预测会降低系统的性能,而导致虚假唤醒的几率又很小
在操作系统底层"唤醒"的实现机制就注定虚假唤醒的存在,设计者们不解决这个问题的原因是
- 修复这个问题会导致系统性能下降,性价比太低
- 即使修复了这个问题,由于同步问题的存在,仍然要将wait()放进循环里.
8.Condition控制线程通信
10.线程按需交替
三个线程按序执行,每个线程执行前while判断条件,唤醒了满足条件就往下走,唤醒其他条件。这个过程加锁,await会临时解锁
11.ReadWriteLock读写锁
读写分离
乐观锁
同时读不加锁
12.线程八锁
13.线程池
Executor就是线程池
Executors是工厂工具类,用这个来创建线程池,不要直接创建
14.线程池调度
15.ForkJoinPool分支/合并框架 工作窃取
1.读不到写的东西
2.多个写错乱
3.读写并发读错乱