一:对volatile理解
是虚拟机提供的轻量级的同步机制
可见性,禁止指令重排,不保证原子性
如何解决原子性?
*加sync
*使用juc下的AtomicInteger
指令重排
源代码—编译器优化重排—指令并行重排—内存系统重排—最终执行的指令
禁止指令重排:避免多线程下程序出现乱序执行现象
内存屏障(Memory Barrier):内存栅栏,是一个CPU指令,两个作用::
一:保证特定的执行顺序,
在指令间插入一条内存屏障后编译器和处理器就不会对指令执行重排优化.
二:保证变量的内存可见性(这个特性实现了volatile的内存可见性)
,强制刷新个各种CPU的缓存数据,所以在CPU上的线程都读取到数据的最新版本
解决工作内存与主内存同步延迟导致的可见性问题?
*synchronized或volatile解决,他们都使一个线程改变变量后对其他线程可见
解决指令重排导致的可见性问题和有序性问题
volatile,
synchronized是加锁,加锁就是单线程,单线程怎样指令重排都可以
volatile与synchronized
1.volatile 仅能使用在变量级别; synchronized 则可以使用在变量、方法、和类级别的
2.volatile 仅能实现变量的修改可见性,并不能保证原子性; synchronized 则可以保证变量的修改可见性和原子性
3.volatile 不会造成线程的阻塞;
synchronized 可能会造成线程的阻塞。
4.volatile 标记的变量不会被编译器优化; synchronized 标记的变量可以被编译器优化
Lock与synchronized
1.原始结构
synchronized是关键字是JVM层面的,字节码上是monitorenter和monitorexit,底层是通过monitor对象完成,wait和notify方法也是依赖monitor对象在
Lock 是api层面
2.方法
synchronized不需要手动释放锁,当synchronized代码执行完后系统会自动让线程释放锁的占用
ReentrantLock需要手动释放锁,没有释放会出现死锁
3等待是否可中断
synchronized不可中断,除非抛出异常
ReentrantLock可中断,设置超时方法
4加锁是否公平
synchronized 非公平锁
ReentrantLock 默认非公平锁,构造方法中可以传入 boolean 值,true 为公平锁,false 为非公平锁。
5.精确唤醒
synchronized 没有 Condition。
ReentrantLock 用来实现分组唤醒需要唤醒的线程们,可以精确唤醒,而不是像 synchronized 要么随机唤醒一个线程要么唤醒全部线程。
public static int num = 1;
public static Lock lock = new ReentrantLock();
public static Condition a = lock.newCondition();
public static Condition b = lock.newCondition();
public static Condition c = lock.newCondition();
public static void main(String[] args) {
newConditiondemo demo = new newConditiondemo();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
demo.a3();
}
},"A").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
demo.b3();
}
},"B").start();
new Thread(() -> {
for (int i = 0; i < 5; i++) {
demo.c3();
}
},"C").start();
}
public void a3() {
lock.lock();
try {
if (num==1) {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() +i);
}
num = 2;
b.signal();
}else {
a.await();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
public void b3() {
lock.lock();
try {
if (num==2) {
for (int i = 0; i < 3; i++) {
System.out.println(Thread.currentThread().getName() + i);
}
num = 3;
c.signal();
}else {
b.await();
}
} catch (Exception e) {
e.printStackTrace();
}finally {
lock.unlock();
}
}
````
## 双端检索机制与volatile的单例模式
```javascript
private static volatile Singleton02 instance = null;
private Singleton02() {
System.out.println(Thread.currentThread().getName() + " construction...");
}
public static Singleton02 getInstance() {
if (instance == null) {
synchronized (Singleton01.class) {
if (instance == null) {
instance = new Singleton02();
}
}
}
return instance;
}
public static void main(String[] args) {
ExecutorService executorService = Executors.newFixedThreadPool(10);
for (int i = 0; i < 10; i++) {
executorService.execute(()-> Singleton02.getInstance());
}
executorService.shutdown();
}
}
二 JMM(java内存模型)
jmm对同步的规定
1 线程解锁前,必须把共享变量的值刷新回主内存
2 线程加锁前,必须读取主内存的最新值到自己的工作内存
3 加锁解锁是用一把锁
java内存模型中规定所有变量都存储在主内存,主内存是共享区域,
线程对变量的操作必须在工作内存中,
首先将变量从主内存拷贝到自己的工作内存空间,
然后对变量操作,操作后写回主内存
四CAS
CAS 的全称 Compare-And-Swap,它是一条 CPU 并发。
它的功能是判断内存某一个位置的值是否为预期,如果是则更改这个值,这个过程就是原子的.
原理:
CAS并发原语
体现在sun.misc.Unsafe类中的各个方法.调用Unsafe类中的CAS方法,JVM会帮我们实现CAS汇编指令.
原语是属于操作系统用语范畴,由指令构成完成某个功能,原语执行必须是连续的,执行过程不许被打断,CAS是一条CPU原子指令,不会造成数据不一致问题
4.1CAS===>compareAndSet比较并交换
第一个参数 expect:期望值
第二个参数 update:跟新值
一开始期望值一致(true)就更新,后来期望值不同(false)就不更新
比较并更新就是cas的功能
4.2 UnSafe类
是CAS的核心类,存在于sun.misc包中,内部方法操作可以像C的指针一样操作内存,
其类中所有方法都是native修饰的,都可以直接调用操作系统底层资源执行相应任务.
比如我们对5使用getAndIncrement()会发生什么呢?
我们一路跟进getAndIncrement()的源码看看:
会发现它底层就是CAS
this就是当前值,
变量value,表示该变量值在内存中的偏移量,
比如会议室的三排四列我们可以锁定一个人,Unsafe就是根据内存偏移地址获取数据的.
对象值的引用地址
1:固定加1嘛
发现是一个do{}while结构,不管while是否成立,do必定被执行一次,
这里我们先进do的getIntVolatile里面,发现是一个native
然后进入do{}while的while里
发现是一个native
好了回到do{}while来分析
首先
定义局部变量v
getIntVolatile(对象本身,偏移量),获取内存中真实值,赋值给v
compareAndSwapInt(对象本身,偏移量,v,v+1);
比较当前对象的值与v,
如果相同,执行v+1,返回true,!取反,返回false跳出循环.
如果不同,返回flase,!取反,返回true,继续循环,重新getIntVolatile获取真实值v
在理解一下:
1.假设线程A和线程B同时在线getAndInt操作
value原始值为3,主内存value值为3,线程A与线程B的工作内存分布存储3
2.线程A拿到value后被挂起,
3.线程B执行compareAndwapInt方法,比较主内存值为3,为true,成功修改内存值为4,线程B执行完毕
4.线程A执行,compareAndSwaInt方法比较,发现自己工作内存值3与主内存值4不一样,只能重新读取主内存拷贝到自己的工作内存.(比较并交换)
5.线程A重新获取value值,value被volatile修饰,其他线程对其修改线程A总能看到,线程A执行compareAndSwaInt比较并替换,直到成功!
五 java.util.concurrent.CopyOnWriteArrayList写时复制
Vector线程安全,Collections.synchronizedList(List)线程安全
写时复制:CopyOnWriteArrayList也是线程安全的
CopyOnWriteArrayList底层也是一个数组,但是是一个加了volatile的数组
CopyOnWrite容器即写时复制的容器,
往容器添加元素,
先
将当前容器复制一个新容器,
然后
向新容器里添加元素,
最后
将原容器的引用指向新的容器.
好处
可以对CopyOnWrite容器进行并发读,不需要加锁,因为当前 容器不会添加任何元素
这就是读写分离思想,读与写不同容器
CopyOnWriteArraySet底层还是CopyOnWriteArrayList