快捷搜索:  汽车  科技

并发锁原理分析(并发进阶十五种锁)

并发锁原理分析(并发进阶十五种锁)final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0 acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) {

明天的你会感谢今天努力的你

举手之劳,加个关注

锁分类
  1. 公平锁/非公平锁
  2. 可重入锁/不可重入锁
  3. 独享锁/共享锁
  4. 互斥锁/读写锁
  5. 乐观锁/悲观锁
  6. 分段锁
  7. 偏向锁/轻量级锁/重量级锁
  8. 自旋锁
名词解释公平锁

公平锁是指多个线程按照申请的顺序获取锁

非公平锁

非公平锁是指多线程获取锁的顺序并不是按照申请的顺序,有可能后申请的线程比先申请的线程优先获取锁。有可能会造成优先级反转或者饥饿现象

对于Java ReentrantLock 来说,同构构造函数指定该锁是否是公平锁、默认是非公平锁,非公平锁的优点在于吞吐量比公平锁大。

对于Synchronized来说,也是一种非公平锁。由于其并不像ReentrantLock 是通过 AQS 来实现的线程调度,所以并没有任何办法使其变成公平锁。

可重入锁

广义上的可重入锁指的是可重复可递归调用的锁,在外层使用锁之后,在内层仍然可以使用,并且不发生死锁(前提是一个对象或者class) 这样的锁叫做可重入锁。ReentrantLock 和 synchronized 都是可重入锁。

ReentrantLock 可重入锁实现,AQS中维护了一个线程可见的 stat 来计算重入次数,避免频繁的持有和释放操作。

final boolean nonfairTryAcquire(int acquires) { final Thread current = Thread.currentThread(); int c = getState(); if (c == 0) { if (compareAndSetState(0 acquires)) { setExclusiveOwnerThread(current); return true; } } else if (current == getExclusiveOwnerThread()) { int nextc = c acquires; if (nextc < 0) // overflow throw new Error("Maximum lock count exceeded"); setState(nextc); return true; } return false; }不可重入锁

不可重入锁,与可重入锁相反, 不可递归调用, 递归调用就发生死锁,使用自旋锁来模拟一下不可重入锁

package com.test; import java.util.concurrent.atomic.AtomicReference; /** * @Descrintion: * @Date : Created in 10:48 2019/5/14 * @ */ public class UnreentrantLock { private AtomicReference<Thread> owner = new AtomicReference<Thread>(); public void lock(){ Thread current = Thread.currentThread(); for(;;){ System.out.println("start"); if(!owner.compareAndSet(null current)){ System.out.println("running"); return ; } } } public void unlock(){ Thread current = Thread.currentThread(); owner.compareAndSet(current null); } public static void main(String[] args) { UnreentrantLock unreentrantLock = new UnreentrantLock(); unreentrantLock.lock(); //unreentrantLock.unlock(); System.out.println("========="); unreentrantLock.lock(); } }

未调用unlock 输出

并发锁原理分析(并发进阶十五种锁)(1)

并发锁原理分析(并发进阶十五种锁)(2)

调用unlock() 输出

并发锁原理分析(并发进阶十五种锁)(3)

并发锁原理分析(并发进阶十五种锁)(4)

使用原子引用来存放线程, 同一线程两次调用lock() 方法,如果不执行unlock() 释放锁的话,第二次调用自旋的时候就会产生死锁

把他变成一个可重入锁

package com.test; import com.design.filter.Criteria; import Java.util.concurrent.atomic.AtomicReference; /** * @Descrintion: 可重入锁 * @Date : Created in 11:10 2019/5/14 * @ */ public class CreentrantLock { private AtomicReference<Thread> owner = new AtomicReference<>(); private int state = 0 ; public void lock(){ Thread current = Thread.currentThread(); if(current == owner.get()){ state ; System.out.println(" "); return ; } for(;;){ System.out.println("start"); if(!owner.compareAndSet(null current)){ System.out.println("running"); return ; } } } public void unlock(){ Thread current = Thread.currentThread(); if(current == owner.get()){ if(state != 0){ state-- ; }else{ owner.compareAndSet(current null); } } } public static void main(String[] args) { CreentrantLock creentrantLock = new CreentrantLock(); creentrantLock.lock(); //creentrantLock.unlock(); creentrantLock.lock(); } }

未调用 unlock

并发锁原理分析(并发进阶十五种锁)(5)

并发锁原理分析(并发进阶十五种锁)(6)

调用 unlock

并发锁原理分析(并发进阶十五种锁)(7)

并发锁原理分析(并发进阶十五种锁)(8)

每次执行操作之前,判断当前锁持有者是否是当前对象,采用state 计数,不用每次去释放锁

独享锁和共享锁在JUC的ReentrantLock 和 ReentrantReadwriteLock 它两一个是独享一个是共享

共享锁

该锁可以被多个线程共有,典型的就是ReentrantReadWriteLock 里的读写锁,他的读锁是可以被共享的,但是他的写锁确每次只能被独占,另外读锁的共享可保证并发读是非常高效的,但是读写和写写、写读都是互斥的, 独享锁与共享锁也是通过AQS来实现的,通过实现不同的方法,来实现独享或者共享。

互斥锁

在访问共享资源之前进行加锁,在访问完成之后进行解锁操作,加锁后,任何其他试图再次加锁的线程会被阻塞,直到当前进行解锁,如果解锁时有一个以上的线程阻塞,那么所有的该锁上的线程都被编成就绪状态,第一个变为就绪状态的线程有执行加锁操作,那么其他的线程又会进入等待,在这种方式下,只有一个线程能够访问被互斥锁保护的资源。

读写锁

读写锁既是互斥锁,又是共享锁, read 模式是共享, write 是互斥(排它锁)的

读写锁有三种状态:读加锁状态,写加锁状态和不加锁状态

读写锁在Java 中的具体实现就是 ReentrantLock

一次只有一个线程可以占有写模式的读写锁,但是多个线程可以同时占有读模式的读写锁。只有一个线程可以占有写状态的锁,但可以有多个线程同时占有写状态锁,这也是它可以实现高并发的原因,当其处于写状态锁下,任何想要尝试获得锁的线程都会被阻塞,直到写状态锁被释放; 如果处于读状态锁下,允许其他线程获得它的读状态锁,但是不允许获得它写状态锁,直到所有线程的读状态锁被释放;为了避免想要尝试写操作的线程一直得不到写状态锁,当读写锁感知到有线程想要获得写状态的锁时,便会阻塞其后所有想要获得读状态锁的线程。所以读写锁非常适合资源的读操作远大于写操作的情况

乐观锁

顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制。乐观锁适用于多读的应用类型,这样可以提高吞吐量,像数据库如果提供类似于write_condition机制的其实都是提供的乐观锁。JUC 包下面的原子变量类都是适用了乐观锁的一种实现方式 CAS 实现

悲观锁

顾名思义,就是很悲观,每次去拿数据的时候都认为别人会修改,所以每次在拿数据的时候都会上锁,这样别人想拿这个数据就会block直到它拿到锁。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。它指的是对数据被外界(包括本系统当前的其他事务,以及来自外部系统的事务处理)修改持保守态度,因此,在整个数据处理过程中,将数据处于锁定状态。悲观锁的实现,往往依靠数据库提供的锁机制(也只有数据库层提供的锁机制才能真正保证数据访问的排他性,否则,即使在本系统中实现了加锁机制,也无法保证外部系统不会修改数据)。

分段锁

就是通过分段锁的形式来实现高效的并发操作。

并发容器类的加锁机制是基于粒度更小的分段锁,分段锁是提升多并发程序性能的重要手段之一。

并发程序中,串行操作是会降低可伸缩性,并且上下文切换也会减低性能,在锁上发生竞争时将导致这两种情况问题,使用独占锁时保护受限资源的时候,基本上是采用串行方式每次只能有一个线程访问它。所以对于可伸缩性来说最大的威胁就是独占锁。

我们一般有三种方式降低锁的竞争程度

1.减少锁的持有时间

2.降低锁的请求频率

3.使用带有协调机制的独占锁

在某些情况下,我们可以将锁分解技术进一步扩展为一组独立对象上的锁进行分解,这称为分段锁。

偏向锁/轻量级锁/重量级锁

锁状态

1. 无锁状态

2.偏向锁状态

3.轻量级锁状态

4.重量级锁状态

锁状态是通过对象监视器在对象头中的字段来表明的。四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。 这四种状态都不是Java 语言中的锁,而是JVM为了提高锁的获取与释放效率而做的优化(synchronzied)

偏向锁

偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁。降级获取锁的代价。

轻量级锁

轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

重量级锁

重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能低下。

自旋锁

CAS 算法是乐观锁的一种实现方式,CAS 算法中有涉及到自旋锁。自旋锁(spinlock):它是为实现保护共享资源而提出一种锁机制。其实,自旋锁与互斥锁比较类似,它们都是为了解决对某项资源的互斥使用。无论是互斥锁,还是自旋锁,在任何时刻,最多只能有一个保持者,也就说,在任何时刻最多只能有一个执行单元获得锁。但是两者在调度机制上略有不同。对于互斥锁,如果资源已经被占用,资源申请者只能进入睡眠状态。但是自旋锁不会引起调用者睡眠,如果自旋锁已经被别的执行单元保持,调用者就一直循环在那里看是否该自旋锁的保持者已经释放了锁,"自旋"一词就是因此而得名

并发锁原理分析(并发进阶十五种锁)(9)

猜您喜欢: