Java -各种锁

悲观锁/乐观锁

这是一种策略。

悲观锁的意思就是对一个数据的并发操作前都会有其他线程修改,然后我每次都需要加锁。在Java中的应用就是各种锁。

乐观锁的意思就是认为不会有其他线程干扰,在尝试更新的时候先会读取现在的值,然后在更新前判断这个值是否被更新过,更新过那么就不断循环判断。
思路代码:

sychronized

1.6之前 synchronized 都是重量级锁

1.6 之后进行了 锁的升级 优化,产生了 偏向锁,轻量级锁(自旋锁),重量级锁

  1. 开始的时候synchronized 锁住一个对象,这时候属于偏向锁,这样当自己再次进入这个循环的时候,不需要再加锁,因为会默认给拥有锁的对象,然后假如又来一个线程申请这个锁,持有这个锁的还是不释放,那么就会进行锁的升级到轻量级锁,然后等待锁的线程自旋了10次,超过了忙等的时间,就会把自己挂起,然后把锁升级为重量级锁,等待唤醒。

可重入锁

ReentrantLock 是一个独占/排他锁 可以显示加锁,必须手动释放

在这里插入图片描述

公平锁和非公平锁

多个线程等待一把公平锁,那么先申请的先得到,后申请得后得到,这就是公平锁,反之就是非公平锁

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

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

对于Java ReentrantLock而言,通过构造函数指定该锁是否是公平锁,默认是非公平锁。非公平锁的优点在于吞吐量比公平锁大。

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

独享锁/共享锁

都是基于AQS设计实现
独享锁 是只能被一个线程持有
共享锁是可以被多个线程持有

RnentrantWriteLock ReentrantLock就是独享锁
ReadWriteLock 读锁是共享锁 写锁是独享锁

互斥锁/读写锁

上面讲的独享锁/共享锁就是一种广义的说法,互斥锁/读写锁就是具体的实现。

字面意思是线程之间互相排斥的锁,也就是表明锁只能被一个线程拥有。

互斥锁在Java中的具体实现就是ReentrantLock synchronized

读写锁其实是一对锁,一个读锁(共享锁)和一个写锁(互斥锁、排他锁)。读写锁在Java中的具体实现就是ReadWriteLock

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

这三种锁是指锁的状态,并且是针对Synchronized。在Java 5通过引入锁升级的机制来实现高效Synchronized。这三种锁的状态是通过对象监视器在对象头中的字段来表明的。

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

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

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

自旋锁

在Java中,自旋锁是指尝试获取锁的线程不会立即阻塞,而是采用循环的方式去尝试获取锁,这样的好处是减少线程上下文切换的消耗,缺点是循环会消耗CPU。

自旋锁可以使线程在没有取得锁的时候,不被挂起,而转去执行一个空循环,(即所谓的自旋,就是自己执行空循环),若在若干个空循环后,线程如果可以获得锁,则继续执行。若线程依然不能获得锁,才会被挂起。
使用自旋锁后,线程被挂起的几率相对减少,线程执行的连贯性相对加强。因此,对于那些锁竞争不是很激烈,锁占用时间很短的并发线程,具有一定的积极意义,但对于锁竞争激烈,单线程锁占用很长时间的并发程序,自旋锁在自旋等待后,往往毅然无法获得对应的锁,不仅仅白白浪费了CPU时间,最终还是免不了被挂起的操作 ,反而浪费了系统的资源。

在JDK1.6中,Java虚拟机提供-XX:+UseSpinning参数来开启自旋锁,使用-XX:PreBlockSpin参数来设置自旋锁等待的次数。

在JDK1.7开始,自旋锁的参数被取消,虚拟机不再支持由用户配置自旋锁,自旋锁总是会执行,自旋锁次数也由虚拟机自动调整。

CAS

乐观锁的基础
本质使用Unsafe 类的CompareAndSwap 实现。底层是用汇编的函数,实现了 有一个旧的地址V 一个估计值 A 想要设置的值B
V == A ? B:null

  • 比较:读取到一个值 A,在将其更新为 B 之前,检查原值是否为 A(未被其它线程修改过,这里忽略 ABA 问题)。

  • 替换:如果是,更新 A 为 B,结束。如果不是,则不会更新。

  • ABA 问题
    就是线程1操作数据a,线程2 也操作数据 a,但是 2删除了又添加a ,这样线程1读到的还是a ,但是如果是链表之类的呢?虽然还是a 的引用,但是a.next 为null 那么就不对了,所以方法就是可以加一个version 版本号

public int data = 123;
    boolean flag = true;
    public void setData(int newData){
        while (flag){
            int oldDate = data;
            newData = newData + 1;

            //下面的就是CAS操作
            if(data == oldDate){
                data = newData;
                flag = false;
            }else{
                //啥也不干,再次循环  直到满足
            }
        }
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值