JAVA特别篇—锁

死锁四个必要条件互斥条件请求和保持条件不可剥夺条件环路等待条件


乐观锁和悲观锁

  • 悲观锁:先加锁后执行。
  • JAVA中synchronized和ReentrantLock等就是典型的悲观锁。还有HashTable等类。
    JAVA特别篇--锁 - 图1
  • 乐观锁:不加锁,执行的时候判断是否加锁。
  • 乐观锁可使用版本号机制CAS算法实现。Java中JUC下的atomic包下的原子类就是CAS乐观锁实现的。
  • 不用频繁上锁释放锁,节省开销,提升吞吐。写少读多场景
    JAVA特别篇--锁 - 图2

    独占锁和共享锁

  • 独占锁

  • 锁一次只能被一个线程持有。若一个线程对数据加上排他锁,其他线程不能再对数据加任何锁。期间能读能修改
  • Java中的synchronized和JUC下的Lock实现类就是独占锁。
    JAVA特别篇--锁 - 图3
  • 共享锁
  • 指锁可被多个线程持有。若一个线程对数据加上共享锁,其他线程只能对数据再加共享锁,不能加独占锁,期间只能读不能修改
  • JDK的ReentrantReadWriteLock是共享锁 。
    JAVA特别篇--锁 - 图4

    互斥锁和读写锁(独占共享锁的实现)

  • 互斥锁

  • 是独占锁的常规实现,指某资源同时只允许一个访问者对其进行访问,具有唯一和排他性。
  • 互斥锁一次只能一个线程拥有互斥锁,其他线程只能等待。
    JAVA特别篇--锁 - 图5
  • 读写锁
  • 读写锁是共享锁的一种具体实现。读写锁管理一组锁,一个只读,一个写。
  • 读锁可在没有写锁的时候被多个线程同时持有。
  • 写锁是独占的,优先级高于读锁,一个获得了读锁的线程必须能看到前一个释放的写锁更新的内容。
  • 读写锁相比互斥锁并发程度更高,每次只有一个写线程,但可同时有多个读线程。
  • JDK定义了一个读写锁的接口。ReentrantReadWriteLock 实现了这个接口。

    1. public interface ReadWriteLock {
    2. /**
    3. * 获取读锁
    4. */
    5. Lock readLock();
    6. /**
    7. * 获取写锁
    8. */
    9. Lock writeLock();
    10. }

    JAVA特别篇--锁 - 图6

    公平锁和非公平锁

  • 公平锁

  • 指多个线程按照申请锁的顺序获取。
  • 可重入锁默认非公平。 ``` /**
  • 创建一个可重入锁,true 表示公平锁,false 表示非公平锁。默认非公平锁 */ Lock lock = new ReentrantLock(true); ``` JAVA特别篇--锁 - 图7
  • 非公平锁
  • 非公平锁指多个线程获取锁的顺序不是按照申请的锁的顺序。高并发可能造成优先级反转,或饥饿状态(某线程一直得不到锁)。

JAVA特别篇--锁 - 图8

可重入锁

  • 又称递归锁,指同一个线程在外层方法获取了锁,在进入内层方法会自动获取锁
  • ReentrantLock和Synchronized都是可重入锁。
  • 一个好处是一定程度可避免死锁。 ``` public synchronized void mehtodA() throws Exception{ // Do some magic tings mehtodB(); }

public synchronized void mehtodB() throws Exception{ // Do some magic tings } //若线程调用A获取了锁,再调用B就不需要再次获取锁。 //若是不可重入锁,调用A再调用B可能不会被当前线程执行,造成死锁

![](https://cdn.nlark.com/yuque/0/2021/png/12844611/1624951287477-8a5a9504-3029-47bc-bcb9-b2d9e60cf296.png#height=719&id=uVEfG&originHeight=719&originWidth=1445&originalType=binary&ratio=1&status=done&style=none&width=1445)
<a name="Rt871"></a>
### 自旋锁

- 自旋锁指没有获得锁时不是被直接挂起,而是执行一个忙循环,这个忙循环就是自旋。
- 目的是减少线程被挂起的几率,因挂起唤醒耗资源。
- 若锁被另一个线程占用的时间比较长,仍会被挂起,忙自旋编程浪费资源的操作。因此自旋不适应所占用时间长的并发情况。
- AtomicInteger 类有类自旋的操作。
- CAS 操作如果失败就会一直循环获取当前 value 值然后重试。
- 在JDK1.6又引入了自适应自旋,这个就比较智能了,自旋时间不再固定,由前一次在同一个锁上的自旋时间以及锁的拥有者的状态来决定。如果虚拟机认为这次自旋也很有可能再次成功那就会次序较多的时间,如果自旋很少成功,那以后可能就直接省略掉自旋过程,避免浪费处理器资源。

public final int getAndAddInt(Object o, long offset, int delta) { int v; do { v = getIntVolatile(o, offset); } while (!compareAndSwapInt(o, offset, v, v + delta)); return v; }

![](https://cdn.nlark.com/yuque/0/2021/png/12844611/1624951287627-25053af1-f2c8-4da9-b626-a1017d99cb1e.png#height=788&id=ZQkRk&originHeight=788&originWidth=1106&originalType=binary&ratio=1&status=done&style=none&width=1106)
<a name="l7DrU"></a>
### 分段锁

- 是一种锁的设计,不是具体锁。
- 目的是将锁的粒度进一步细化,当操作不需要更新真个数组的时候,近仅仅针对数组中的一项进行加锁。
- CurrentHashMap 之前底层用了分段锁。<br />![](https://cdn.nlark.com/yuque/0/2021/png/12844611/1624951287758-e5e9efa2-c61e-43dc-9949-8938ba8456bd.png#height=843&id=rcAGo&originHeight=843&originWidth=1934&originalType=binary&ratio=1&status=done&style=none&width=1934)
<a name="tIsFo"></a>
### 锁升级(无所|偏向锁|轻量级锁|重量级锁|)

- 6为了提升性能减少锁获取释放带来的消耗,引入了四种锁的状态:无所|偏向锁|轻量级锁|重量级锁|,它随着多线程的竞争情况主键升价,但是不能降级。
- 无锁
   - 指的就是乐观锁
- **偏向锁**(cas比较信息;mark Word 中有线程信息 cas 比较)
   - 指偏向于第一个访问锁的线程,若运行过程中,只有一个线程访问加锁的资源,不存在多线程竞争的情况,那么线程是不需要重复获取锁的,这种情况下,就会给线程加一个偏向锁。
   - 偏向锁的实现是通过控制对象Mark Word的标志位来实现的,如果当前是可偏向状态,需要进一步判断对象头存储的线程 ID 是否与当前线程 ID 一致,如果一致直接进入。
- **轻量级锁**(cas尝试改变指针;复制了一份mark work 叫 Lock record 也是cas尝试改变指针)
   - 线程竞争比较激烈的时候,偏向锁会升级为轻量级锁,轻量级锁认为虽然竞争是存在的,但是理想情况下竞争程度低,通过自旋方式等待锁释放。
- **锁自选**(死循环)
- **重量级锁**()
   - 若并发进一步加剧,自旋超过一定次数,或一个持有一个自旋来了第三个,轻量级锁会膨胀为重量级锁,会使除了此时拥有锁的线程意外的线程都阻塞。
   - 升级到钟灵几所就是互斥锁了。一个拿到其他阻塞等待。
   - 6对synchronized进行优化,内部原理就是锁升级。
<a name="C9fvQ"></a>
### 锁优化(锁粗化|锁消除)

- 锁粗化
- 将多个同步快的数量减少,并将单个同步块的作用范围扩大,本质就是将多次上锁、解锁的请求合并为一次同步请求。

//循环中加锁,每次都会进行加锁解锁。 private static final Object LOCK = new Object();

for(int i = 0;i < 100; i++) { synchronized(LOCK){ // do some magic things } }

```
//优化后
 synchronized(LOCK){
     for(int i = 0;i < 100; i++) {
        // do some magic things
    }
}
  • 锁消除
  • 指虚拟机编译器在运行时检测到了共享数据没有竞争的锁,从而将这些所进行消除。
    public String test(String s1, String s2){
      //局部变量,在栈上,线程私有
      //StringBuffer是线程安全的,append是同步方法,但是test本身就是线程安全的
      StringBuffer stringBuffer = new StringBuffer();
      stringBuffer.append(s1);
      stringBuffer.append(s2);
      return stringBuffer.toString();
    }
    
    ``` //为提高效率虚拟机帮我们消除这些同步锁 StringBuffer.class

// append 是同步方法 public synchronized StringBuffer append(String str) { toStringCache = null; super.append(str); return this; }

<a name="OPzuZ"></a>
##### 总结
![](https://cdn.nlark.com/yuque/0/2021/png/12844611/1624951287849-4a86bcb4-d827-4d76-b117-dce95f6e9815.png#height=1657&id=kwxkY&originHeight=1657&originWidth=2315&originalType=binary&ratio=1&status=done&style=none&width=2315)

---

https://blog.csdn.net/guoguo527/article/details/118004077

---

<a name="69sGU"></a>
### JUC

- if多线程存在虚假唤醒问题,while可避免,if只判断一次,while一直循环判断。
- **Synchronized** 非公平锁;悲观的排他锁;可重入锁;无法中断;便于JVM栈跟踪。java内置关键字,JVM给的,加解锁过程由JVM自动控制;字节码层面的,无法判断获取锁的状态。
   - Java对象头和monitor是实现synchronized的基础。
   - 6之前时重量级锁,依赖于底层硬件;6之后可锁升级,四种状态(无锁、偏量级锁、轻量级锁、重量级锁)也改为用CAS实现,性能与lock相当。
   - 首先是无锁状态,如果只有A线程执行,则会变成偏向锁,单单偏向A线程。下次A线程再进来就能直接进来,不需要额外的cas操作。如果B线程进来,发现当前锁偏向A线程,就会通过cas竞争锁,竞争失败说明有多个线程竞争,就会再安全点的时候将锁升级为轻量级锁。如果轻量级锁还存在锁竞争失败的情况,就会升级为重量级锁,再竞争激烈的情况下,重量级锁会比轻量级锁更高的性能。因为轻量级锁不仅存在互斥的开销还存在cas的开销。
   - 在竞争锁的时候,也做了一些优化,比如自旋锁/适应性自旋。在代码层面上,会做的优化有,锁消除(隐形加锁 StringBuffer)和锁粗化(for循环内部上锁变成外部上锁)。
   - 若锁在静态方法,锁的就是这个类的Class,多个对象只有一个Class。
   - 局限性:不能设置超时。ReentrantLock可使用多个Condition,而synchronized只有一个,不能中断一个试图获得锁的线程。
- **Lock**(java类)在JUC包,尝试获取锁lock.trylock(),是用CAS实现的,有三个实现类
   - ReentrantLock 可重入锁,可选公平或非公平。可获得正在等待线程的个数,计数器等。

punlic ReentrantLock(){ sync = new NonfaireSync();//无参构造,非公平锁。 } punlic ReentrantLock(Boolear faire){ sync = faire ?new FaireSync() : new NonfaireSync();//公平锁。 }


- ReentrantLockReadWriteLock.ReadLock 读写锁之读锁(共享锁)
   - 可多线程同时读。
- ReentrantLockReadWriteLock.WriteLock 读写锁之写锁(独占锁)
   - 同一时间只能一个线程写。
- lock()-->try(){}catch(){}finally{unlock()};

![](https://cdn.nlark.com/yuque/0/2021/png/12844611/1624951288037-bbfbbe2b-9841-4fd0-be94-97a31f720e3b.png#height=1807&id=Apw4t&originHeight=1807&originWidth=3867&originalType=binary&ratio=1&status=done&style=none&width=3867)
<a name="YyZCJ"></a>
##### 线程安全的List

- List<String> list = Collections.synchronizedList(new ArrayList<>());
- Vector(粗暴的synchronized)
- CopyOnWriteArrayList<>();
   - 底层也是数组,数组加了volatile。添加的时候加了可重入锁,先复制元素,添加然后写入回主体setArray();后面的版本又用回了synchronized。synchronized CAS优化后效率与lock相当。
   - CopyOnWrite写入时复制简称COW计算机程序设计领域的一种优化策略。当多个线程调用的时候,读取list时固定的,写入时(并发可能覆盖)避免覆盖造成的数据问题。读写分离的思想。写入的时候复制一份,然后插入。
<a name="YhUaX"></a>
##### 常用的辅助类

- CountDownLatch减法计数器
   - countDown();数量-1
   - await();等待计数器归零,然后向下执行。
- CycilcBarrier加法计数器
   - await();等待计数器达到要求,然后向下执行。
- Semaphore信号量,限流;底层是AQS.
   - acquire()得到
   - release()释放
<a name="bCwKD"></a>
##### 线程安全的Set

- 粗暴加锁
- Set<String> set = Collections.SynchronizedSet(new HashSet<>())
- CopyOnWriteSet.
<a name="WHuLE"></a>
##### 线程安全的Map

- Collections.SynchronizedMap(new HashMap<>())
- ConcurrentHashMap
   - KV都不能接收null,会抛出空指针异常。
<a name="N9XSP"></a>
##### blockingQueue

- add、remove、element异常
- offer、poll、peek返回布尔/null

![](https://cdn.nlark.com/yuque/0/2021/png/12844611/1624951288261-50bd8d87-46fb-4b2f-a98c-200c9f6bbf42.png#height=283&id=yOn77&originHeight=283&originWidth=1258&originalType=binary&ratio=1&status=done&style=none&width=1258)
<a name="IhbfH"></a>
##### SynchronousQueue同步队列

- 不存元素,放一个必须取出后才能放下一个。
<a name="YDCGM"></a>
##### 线程池

- 三大方法、七大参数、四种拒绝策略。
- 好处:
   - 降低资源消耗
   - 提高响应速度
   - 方便管理
- 工具类Executors
   - 三大方法
   - Executors.newSingleThreadExecutor()单个线程
   - Executors.newFixedThreadPool(3)固定线程池的大小
   - Executors.newCachedThreadPool()可伸缩的,遇强则强。
<a name="5WB3f"></a>
##### 函数式接口

- Consumer
- Function
- Predicate
- Supplier

---

- ForkJoin 分而治之,大话小。
- 特点:工作窃取;能者多劳。
- submit()异步,execute()同步。
- Long::sum 就是 (o1,o2)->{return Long.sum(o1,o2);} Long下面的sum方法
<a name="vWArY"></a>
##### JMM

- Volatile:内存可见性、不保证原子性、禁止指令重排。轻量级同步机制。
- JMM的一些同步的约定:
   - 线程解锁前,必须把共享变量立刻刷回主存。
   - 线程加锁千,必须读取主存中的最新值到工作内存。
   - 加解锁是同一个。
   - ps:println()中有synchronized同步代码块,synchronized也能保证修改可见性。
- Volatile不保证原子性,synchronized和lock可保证;
   - a++不是原则操作,1获取这个值,2加操作,3,写回这个值。
   - 原子类可解决,原子类底层CAS实现。

private volatile static int num = 0;//X private volatile static AtomicInteger num = new AtomicInteger();//yes unsafe类 ``` JAVA特别篇--锁 - 图9

  • 指令重排:计算机并不是按照代码顺序执行的。cpu会根据值依赖关系。
  • 源代码—>编译器优化的重排—>指令并行也可能会重排—>内存系统也会重排—>执行。
    • volatile可避免指令重排内存屏障,CPU指令,作用:
    • 保证指定的操作的执行顺序
    • 可保证某些变量的内存可见性(利用这些特性volatile实现了可见性)
    • 内存屏障单例使用的最多!。