JUC并发包 CAS、Synchronized、AQS、ReentrantLock、阻塞队列、原子类

CAS、Synchronized、阻塞队列、原子类、AQS、ReentrantLock

锁的分类优化

偏向锁:没有线程与自己争抢资源的,加个标记就可以了。

轻量锁:竞争时间比较小,比如使用CAS就可以解决的。

重量锁:竞争时间开销很大,需要利用操作系统的同步机制


乐观锁:对目标资源不加任何锁,失败了,可以接着尝试

悲观锁:当要做操作时,必须将资源锁起来,避免别人来干扰


可重入锁:自己是当前资源锁的拥有者,当自己再次获取的时候,不需要释放再获取。例如ReentrantLock

不可重入锁:即使自己是当前资源锁的拥有者,但是再次获取这把锁,需要释放再获取


自旋锁:线程在当前资源获取锁失败后,并不直接阻塞或者等待,利用循环不断尝试获取锁

非自旋锁:和自旋锁相反的呗~拿不到锁直接放弃,进入等待或者阻塞状态


公平锁:先来先得的意思。拿不到锁的线程会进入等待状态,开始排队,一般等待时间最久的先获得锁。

非公平锁:插队了


不可中断锁:一旦开始进入竞争锁的状态,就不可以中途退出,不能说我不要锁了,不想等待了,比如synchronized

可中断锁:可以随时退出竞争锁的状态,例如Reentrant Lock

jvm做的相关优化

自适应的自旋锁:jvm已经都优化好了,不用自己实现。自旋锁一直重复获取锁,其实对cpu消耗还是很大的。所以后来优化成了自适应的自旋锁,可以根据获取的成功率失败率,当前锁拥有者的状态等等,来决定自旋的周期时间等等。

还有锁消除、粗化之类的一系列优化~

CAS

乐观锁的一种,它不会对目标资源加锁。它的过程看图吧

在这里插入图片描述

1.线程A获取到当前的内存值V为1,与自己的预期值1相等,将资源修改为2。

2.因为CAS对目标资源并不加任何锁,所以会发生A在修改前,B先完成了修 改,这样A获取到的当前内存值V就

​ 为3,与自己的期望值A 1不相等,不修改。

使用场合

concurrentHashMap在没有产生hash碰撞时,直接插入数组,会使用CAS

原子类AtomicInteger

synchronize底层实现

底层是通过monitor锁来实现的。synchronized在jvm中的锁优先级 偏向锁---->轻量锁—>重量锁

1.同步代码块的实现

代码如下:

public class Atest {
    public  void test(){
        synchronized(this){
            System.out.println("meng");
        }
    }
}

反编译结果

在这里插入图片描述

这里呢~我们可以看到一个monitorenter,两个monitorexit指令。

monitorenter指令获得锁,monitorexit释放锁。为啥俩monitorexit这个呢~因为jvm要保证线程正确结束时执行释放锁,遇到异常结束的话也要执行,所以jvm会在这两个位置分别插入一个monitorexit指令。

monitor锁存在于对象的对象头中,所以这也就是为啥说,一个Java对象可以作为锁。同时,每个对象维护着一个计数器,初始值0。monitorenter命令会使计数器+1。monitorexit释放锁,使计数器-1。

线程A来获取monitor锁,首先判断计数器是否为0,为0则获取锁,计数器+1。此时,线程B来获取锁发现计数器为1,则直接进入阻塞等待状态。

线程A在已拥有锁的情况下,再次申请获取锁,计数器会再次累加+1。只有执行monitorexit将计数器-1,直至为0时,其他阻塞等待的线程就可以来获取这把锁了。

2.同步方法

代码如下:

public class BTest {
    public synchronized void test(){
        System.out.println("mengmeng");
    }
}

反汇编结果

在这里插入图片描述

同步方法与同步代码块不同,同步方法会增加一个ACC_SYNCHRONIZED 标识。jvm通过该标识来判断该方法是否为一个同步方法。然后来执行同步调用,获取monitor锁,执行方法,释放monitor锁。之后其余等待阻塞线程可再次获取锁。

阻塞队列

也就是BlockingQueue,这是一个接口。他的子类有很多 linkedBlockingQueue、ArrayBlockingQueue、SynchronousQueue、PriorityBlockingQueue。
线程安全的队列。适用于工作中很多场景,比如生产者/消费者。生产者只管向其中写入,而不需要考虑队列内的因并发导致的问题。消费者只管从队列中取出消费。

阻塞队列的特点

我不想写了

几种阻塞队列的实现类

ArrayBlockingQueue 有界(这里的有界是指队列的大小有界),内部使用数据存储,利用ReentrantLock保证线程安全。
LinkedBlockingQueue 链表结构,如果我们不指定的话,默认容量是Integer.MAX_VALUE最大值,这个值很大,所以可以认定是无界队列。
SynchronousQueue 初始容量为0,所以他的消费者取元素开始是阻塞的,直到有元素写入,容量变为1,读取线程才可以工作。
PriorityBlockingQueue ArrayBlockingQueue、LinkedBlockingQueue都是先进先出,如果我们要指定优先级出队列的顺序,那么就可以使用这个,使用CompareTo方法就可以了

DelayQueue 一种提供延时执行功能的阻塞队列。

原子类

Atomic+xxxx这样子的类,比如正常的i++是一个线程不安全的操作,但是使用原子类进行自增,就可以保证线程安全。他是用的是CAS来保证线程安全。

AtomicInteger

AtomicInteger 类的一些常用的方法:

public final int get() //获取当前的值
public final int getAndSet(int newValue) //获取当前值,并且设置新的值
public final int getAndIncrement() //获取当前的值,并自增
public final int getAndDecrement() //获取当前的值,并自减
public final int getAndAdd(int delta) //获取当前的值,并加上预期的值

getAndAdd(int delta) 中delta这个参数是让当前这个原子类改变多少值,可以是正数也可以是负数,如果是正数就是增加,如果是负数就是减少。 getAndIncrement 和 getAndDecrement 是做 +1 或 -1的操作。

boolean compareAndSet(int expect, int update) 

这个方法的作用是:如果输入的数值等于预期值,则以原子方式将该值更新为输入值,可以结合CAS考虑这个方法。这个方法是 CAS 的一个重要体现。

AQS

他是帮助线程协作的一个工具类。比如在各类锁中,当一个线程获取到锁后,其他线程不允许获取到锁,该线程释放锁后,其他线程可以来获取,上述这类操作是很多锁都需要完成的,所以AQS就是一个帮助锁类实现线程协作的工具类。他有三个重要的点:状态、队列、获取锁/释放锁的方法。

ReentrantLock

ReentrantLock是可重入锁。它的内部是使用AQS来实现的。
首先使用AQS内部的state状态字段,这个成员变量可以通过set方法或者compareAndSetState方法来设置值,但是他为什么是线程安全的呢?compareAndSetState()使用CAS来设置的。set()方法也线程安全是因为state成员变量是使用volatile修饰的,保证了线程间的可见性。

一个线程获取到锁后,其他线程应该都处于阻塞状态,那么他们就需要一个队列来存储。AQS给ReentrantLock提供了一个队列,并且是双向的。一个线程获取到锁后,其他线程会进入到队列中,在该线程释放锁后,再从队列中根据自定义优先级获取一个线程来获取锁。

ReentrantLock关于线程的获取锁,lock()方法,他通过判断state值,如果是大于0,并且这个线程并不是这把锁的当前持有者,那么这个线程当然是进入阻塞状态。
ReentrantLock的释放锁,也是通过维护state值,将state值-1,当state为0后,才允许其他线程来获取锁。

剩下的我不想写了,以后有时再写吧!!!

  • 3
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值