博主主页: 码农派大星.
数据结构专栏:Java数据结构
数据库专栏:MySQL数据库
JavaEE专栏:JavaEE
关注博主带你了解更多数据结构知识
1.锁策略
1.1乐观锁vs悲观锁
悲观和乐观是对后续的锁冲突是否激烈做出的预测
悲观锁: 假设最坏的情况,每次去拿数据的时候都认为别⼈会修改,所以每次在拿数据的时候都会上锁, 这样别⼈想拿这个数据就会阻塞直到它拿到锁。
乐观锁: 假设数据⼀般情况下不会产⽣并发冲突,所以在数据进⾏提交更新的时候,才会正式对数据是否产⽣ 并发冲突进⾏检测,如果发现并发冲突了,则让返回⽤⼾错误的信息,让⽤⼾决定如何去做。
1.2轻量级锁vs重量级锁(优先考虑时间开销)
轻量级锁:锁的开销很小
重量级锁:锁的开销很大
1.3自旋锁vs挂起等待锁
挂起等待锁: 是悲观锁/重量级锁的一种体现
等待过程中,让出了cpu资源,cpu就可以做别的事,过一段时间,再伺机而动
自旋锁:是乐观锁/轻量级锁的一种体现
等待的过程中,不会释放cpu资源,不停地检测锁是否被释放,若被释放,就立即有机会获取锁了.
1.4公平锁vs非公平锁
操作系统提供的加锁api默认情况下就是非公平锁(synchronized),如何要实现公平锁,需要引入额外的队列来进行维护。
公平锁:
非公平锁:
1.5可重入锁vs不可重入锁
在一个线程中如果对一把锁连续加锁多次但不会出现死锁的情况就称为可重入锁,否则为不可重入锁。
1.6读写锁(synchronized并非读写锁)
读加锁:读的时候能读但不能写
写加锁:写的时候不能读也不能写
1.7synchronized"自适应"
轻量锁就是基于"自旋"方式实现:效率高,就是耗cpu资源
(JVM内部,用户态代码实现)
重量锁就是基于"等待"的方式实现: 效率稍低,cpu资源耗费少
(调用操作系统api,系统内核实现)
synchronized属于“悲观乐观”自适应的。
synchronized属于“重量轻量”自适应的。
synchronized属于“自旋挂起”自适应的。
synchronized属于“可重入”。
synchronized属于“非公平”。
synchronized不属于“读写锁”。
1.7.1.锁升级
刚开始使用synchonizaed加锁,首先锁会处于"偏向锁"状态,遇到线程之间的锁竞争,升级到"轻量级锁"进一步的统计竞争出价的频次,达到一定程度之后,升级到"重量级锁"
sychronized加锁的时候,会经历无锁=> 偏向锁 => 轻量级锁 => 重量级锁
偏向锁 -> 轻量级锁: 出现竞争
轻量级锁 -> 重量级锁: 竞争激烈
偏向锁并不是真的加锁,而是给对象头中做一个偏向锁的标记,记录这个锁属于哪一个线程。
上述升级的过程中, 主要是为了让sychronized这个数很好的适应不同的场景,减轻程序员的负担.
锁升级不可逆.
1.7.2.锁消除
编译器会自动针对当前写的代码进行优化,做出判断是否需要加锁,如果不需要会把所写的所优化掉。但只会在非常有把握的时候才会进行此操作.
1.7.3.锁粗化
锁的粒度:synchronied里,代码越多就认为锁越粗,代码越少就认为锁越细。
粒度细,并发执行的逻辑更多,更利于利用cpu资源,但如果粒度细被反复加锁,编译器就会自动进行粗化
2.CAS
CAS:Compare and swap,比较和交换的是内存和寄存器
CAS(M,A,B):比较A与M是否相等,如果相等就将B写入M,M=B,返回true,否则返回false。
CAS是一个cpu指令,被系统封装提供api,被JVM封装也提供api供我们使用。CAS是原子的,那么就可以使用CAS来替代加锁操作。
实现原子类
AtomicInteger atomicInteger=new AtomicInteger(0);
//相当于i++;
atomicInteger.getAndIncrement();