- 两个线程都只是读一个数据, 此时并没有线程安全问题. 直接并发的读取即可.
- 两个线程都要写一个数据, 有线程安全问题.
- 一个线程读另外一个线程写, 也有线程安全问题.
ReadLock readLock = readWriteLock.readLock();
readLock.lock();//读锁加锁
readLock.unock();//读锁释放锁
写锁
WriteLock writeLock = readWriteLock.writeLock();
writeLock.lock();//写锁加锁
writeLock.unock();//写锁释放锁
其中
,
- 读加锁和读加锁之间, 不互斥.
- 写加锁和写加锁之间, 互斥.
- 读加锁和写加锁之间, 互斥.
一般会搭配乐观锁一起来实现
do {
获取变量的值
}while(!compareAndSwap(变量的值));
尝试修改变量的值:
- 修改成功,就退出循环,接着往下执行
- 修改失败,就再次循环
以申请锁的时间先后顺序,来获取到锁——类似排队买票的方式。
缺点:效率稍微差一些。
非公平锁 定义:synchronized申请对象锁,是竞争的方式。
优点:效率更高(不考虑执行顺序)。
缺点:可能出现线程饥饿(某些线程长期得不到执行)的现象。
5.CAS 定义:CAS是乐观锁的一种实现,CAS全称Compare and swap,字面意思:”比较并交换“,CAS的操作主要是由以下几个步骤组成:
- 先查询原始值
- 操作时比较原始值是否修改
- 如果修改,则操作失败,禁止更新操作,如果没有发生修改,则更新为新值。
上述三个步骤是一个原子性操作,不可以被拆分执行。
CAS的实现 针对不同的操作系统, JVM 用到了不同的 CAS 实现原理,简单来讲:- java 的 CAS 利用的的是 unsafe 这个类提供的 CAS 操作;
- unsafe 的 CAS 依赖了的是 jvm 针对不同的操作系统实现的 Atomic::cmpxchg;
- Atomic::cmpxchg 的实现使用了汇编的 CAS 操作,并使用 cpu 硬件提供的 lock 机制保证其原子性。
CAS是一种无锁操作,不需要加锁,避免了线程切换的开销。
缺点CAS虽然在低并发量的情况下可以减少系统的开销,但是CAS也有一些问题:
- CPU开销过大问题
- ABA问题
- 只能针对一个共享变量
所谓ABA问题,就是比较并交换的循环,存在⼀个时间差,而这个时间差可能带来意想不到的问题。比如线程1和线程2同时也从内存取出A,线程T1将值从A改为B,然后⼜从B改为A。线程T2看到的最终值还是A,经过与预估值的比较,二者相等,可以更新,此时尽管线程T2的CAS操作成功,但不代表就没有问题。
本质:ABA问题的根本在于cas在修改变量的时候,无法记录变量的状态,比如修改的次数,否修改过这个变量。这样就很容易在⼀个线程将A修改成B时,另一个线程又会把B修改成A,造成cas多次执行的问题。
解决方案:AtomicStampedReference类来引入版本号,每次操作之后让版本号 +1,执行的时候判断版本号的值,就可以解决ABA问题。
6.synchronized优化 基本特点 结合上面的锁策略 , 我们就可以总结出 , Synchronized 具有以下特性 ( 只考虑 JDK 1.8):- 开始时是乐观锁, 如果锁冲突频繁, 就转换为悲观锁.
- 开始是轻量级锁实现, 如果锁被持有的时间较长, 就转换成重量级锁.
- 实现轻量级锁的时候大概率用到的自旋锁策略
- 是一种不公平锁
- 是一种可重入锁
- 不是读写锁
- 无锁
- 偏向锁:只有一个线程申请某个对象的锁,就可以使用偏向锁(代价小),同一个线程可重入的方式多次申请同一个对象锁,也是使用偏向锁
- 轻量级锁:自旋+CAS,线程冲突不严重(没有冲突,或少量冲突):相对重量级锁代价要低
- 重量级锁:基于系统级别的mutex lock锁,代价就非常大(系统加锁),线程冲突严重的时候使用
编译器+JVM 会根据代码运行的情况智能判定当前的锁是否必要,如果不必要,就直接把锁的代码干掉
//局部变量只有当前方法执行的线程持有(不可能有其他线程持有)
//也就不存再线程安全问题:jvm给append中synchronized加锁释放锁
// 优化方案,就是“锁消除”=>不加锁
StringBuffer sb = new StringBuffer();
sb.append("a");
sb.append("b");
sb.append("c");
锁粗化
如果一段逻辑中,需要多次解锁解锁,并且解锁时没有其他线程来竞争,此时就会把锁粗化
private static StringBuffer sb = new StringBuffer();
public static void main(String[] args) {
sb.append("a");
sb.append("b");
sb.append("c");
System.out.println(sb.toString());
}
7.独占锁/共享锁
独占锁
指的是这一把锁只能被一个线程拥有。(synchronized、Lock)
共享锁指的是一把锁可以被多个线程同时拥有。(ReadWriteLock读写锁,读锁就是共享)
ReadWriteLock读写锁将锁的粒度更加细化,从而提高锁的性能,可以设置为公平锁。
ReentrantReadWriteLock 读写锁读锁是共享锁,写锁是独占锁(只有在写的时候才会发生线程不安全问题),读锁和写锁之间是互斥的。



