Java锁

Java锁
  1. 乐观锁
    乐观锁是一种乐观思想,即认为读多写少,遇到并发写的可能性低,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,采取在写时先读出当前版本号,然后加锁操作(比较跟上一次的版本号,如果一样则更新),如果失败则要重复读-比较-写的操作。
    java 中的乐观锁基本都是通过CAS 操作实现的,CAS 是一种更新的原子操作,比较当前值跟传入值是否一样,一样则更新,否则失败。
    CAS:在这里插入图片描述
    解决CAS中的ABA问题是通过给那个变量增加 一个版本号
  2. 悲观锁
    悲观锁是就是悲观思想,即认为写多,遇到并发写的可能性高,每次去拿数据的时候都认为别人会修改,所以每次在读写数据的时候都会上锁,这样别人想读写这个数据就会block 直到拿到锁。
    java 中的悲观锁就是Synchronized,AQS 框架下的锁则是先尝试cas 乐观锁去获取锁,获取不到,才会转换为悲观锁,如RetreenLock。
  3. 自旋锁
    自旋锁原理非常简单,如果持有锁的线程能在很短时间内释放锁资源,那么那些等待竞争锁的线程就不需要做内核态和用户态之间的切换进入阻塞挂起状态,它们只需要等一等(自旋),等持有锁的线程释放锁后即可立即获取锁,这样就避免用户线程和内核的切换的消耗。
    线程自旋是需要消耗cup 的,说白了就是让cup 在做无用功,如果一直获取不到锁,那线程也不能一直占用cup 自旋做无用功,所以需要设定一个自旋等待的最大时间。
    如果持有锁的线程执行的时间超过自旋等待的最大时间扔没有释放锁,就会导致其它争用锁的线程在最大等待时间内还是获取不到锁,这时争用线程会停止自旋进入阻塞状态。
    自旋锁的优缺点
    自旋锁尽可能的减少线程的阻塞,这对于锁的竞争不激烈,且占用锁时间非常短的代码块来说性能能大幅度的提升,因为自旋的消耗会小于线程阻塞挂起再唤醒的操作的消耗,这些操作会导致线程发生两次上下文切换!
    但是如果锁的竞争激烈,或者持有锁的线程需要长时间占用锁执行同步块,这时候就不适合使用自旋锁了,因为自旋锁在获取锁前一直都是占用cpu 做无用功,占着XX 不XX,同时有大量
    线程在竞争一个锁,会导致获取锁的时间很长,线程自旋的消耗大于线程阻塞挂起操作的消耗,其它需要cup 的线程又不能获取到cpu,造成cpu 的浪费。所以这种情况下我们要关闭自旋锁;
  4. Synchronized 同步锁
    synchronized 它可以把任意一个非NULL 的对象当作锁。他属于独占式的悲观锁,同时属于可重入锁。
  5. ReentrantLock
    ReentantLock 继承接口Lock 并实现了接口中定义的方法,他是一种可重入锁,除了能完成synchronized 所能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。
  6. 公平锁和非公平锁
    公平锁:公平锁指的是锁的分配机制是公平的,通常先对锁提出获取请求的线程会先被分配到锁,ReentrantLock 在构造函数中提供了是否公平锁的初始化方式来定义公平锁。
    非公平锁:加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待,JVM 按随机、就近原则分配锁的机制则称为不公平锁,ReentrantLock 在构造函数中提供了是否公平锁的初始化方式,默认为非公平锁。非公平锁实际执行的效率要远远超出公平锁,除非程序有特殊需要,否则最常用非公平锁的分配机制。
    1. 非公平锁性能比公平锁高5~10 倍,因为公平锁需要在多核的情况下维护一个队列
    2. Java 中的synchronized 是非公平锁,ReentrantLock 默认的lock()方法采用的是非公平锁。
  7. ReadWriteLock 读写锁
    为了提高性能,Java 提供了读写锁,在读的地方使用读锁,在写的地方使用写锁,灵活控制,如
    果没有写锁的情况下,读是无阻塞的,在一定程度上提高了程序的执行效率。读写锁分为读锁和写
    锁,多个读锁不互斥,读锁与写锁互斥,这是由jvm 自己控制的,你只要上好相应的锁即可。
    读锁
    如果你的代码只读数据,可以很多人同时读,但不能同时写,那就上读锁
    写锁
    如果你的代码修改数据,只能有一个人在写,且不能同时读取,那就上写锁。总之,读的时候上
    读锁,写的时候上写锁!
  8. 共享锁和独占锁
    独占锁
    独占锁模式下,每次只能有一个线程能持有锁,ReentrantLock 就是以独占方式实现的互斥锁。
    独占锁是一种悲观保守的加锁策略,它避免了读/读冲突,如果某个只读线程获取锁,则其他读线程都只能等待,这种情况下就限制了不必要的并发性,因为读操作并不会影响数据的一致性。
    共享锁
    共享锁则允许多个线程同时获取锁,并发访问 共享资源,如:ReadWriteLock。共享锁则是一种
    乐观锁,它放宽了加锁策略,允许多个执行读操作的线程同时访问共享资源。
  9. 重量级锁
    Synchronized 是通过对象内部的一个叫做监视器锁(monitor)来实现的。但是监视器锁本质又是依赖于底层的操作系统的Mutex Lock 来实现的。而操作系统实现线程之间的切换这就需要从用户态转换到核心态,这个成本非常高,状态之间的转换需要相对比较长的时间,这就是为什么
    Synchronized 效率低的原因。因此,这种依赖于操作系统Mutex Lock 所实现的锁我们称之为“重量级锁”。JDK 中对Synchronized 做的种种优化,其核心都是为了减少这种重量级锁的使用。JDK1.6 以后,为了减少获得锁和释放锁所带来的性能消耗,提高性能,引入了“轻量级锁”和“偏向锁”。
  10. 轻量级锁
    锁的状态总共有四种:无锁状态、偏向锁、轻量级锁和重量级锁。
    锁升级
    随着锁的竞争,锁可以从偏向锁升级到轻量级锁,再升级的重量级锁(但是锁的升级是单向的,也就是说只能从低到高升级,不会出现锁的降级)。
    “轻量级”是相对于使用操作系统互斥量来实现的传统锁而言的。但是,首先需要强调一点的是,轻量级锁并不是用来代替重量级锁的,它的本意是在没有多线程竞争的前提下,减少传统的重量级锁使用产生的性能消耗。在解释轻量级锁的执行过程之前,先明白一点,轻量级锁所适应的场景是线程交替执行同步块的情况,如果存在同一时间访问同一锁的情况,就会导致轻量级锁膨胀为重量级锁。
  11. 偏向锁
    偏向锁的目的是在某个线程获得锁之后,消除这个线程锁重入(CAS)的开销,看起来让这个线程得到了偏护。引入偏向锁是为了在无多线程竞争的情况下尽量减少不必要的轻量级锁执行路径,因为轻量级锁的获取及释放依赖多次CAS 原子指令,而偏向锁只需要在置换ThreadID 的时候依赖一次CAS 原子指令(由于一旦出现多线程竞争的情况就必须撤销偏向锁,所以偏向锁的撤销操作的性能损耗必须小于节省下来的CAS 原子指令的性能消耗)。上面说过,轻量级锁是为了在线程交替执行同步块时提高性能,而偏向锁则是在只有一个线程执行同步块时进一步提高性能
  12. AtomicInteger
    首先说明, 此处AtomicInteger , 一个提供原子操作的Integer 的类, 常见的还有AtomicBoolean、AtomicInteger、AtomicLong、AtomicReference 等,他们的实现原理相同,区别在与运算对象类型的不同。令人兴奋地,还可通过AtomicReference将一个对象的所有操作转化成原子操作。我们知道,在多线程程序中,诸如++i 或 i++等运算不具有原子性,是不安全的线程操之一。通常我们会使用synchronized 将该操作变成一个原子操作,但JVM 为此类操作特意提供了一些同步类,使得使用更方便,且使程序运行效率变得更高。通过相关资料显示,通常AtomicInteger的性能是ReentantLock 的好几倍。
  13. 可重入锁(递归锁)
    本文里面讲的是广义上的可重入锁,而不是单指JAVA 下的ReentrantLock。可重入锁,也叫做递归锁,指的是同一线程 外层函数获得锁之后 ,内层递归函数仍然有获取该锁的代码,但不受影响。在JAVA 环境下 ReentrantLock 和synchronized 都是 可重入锁。
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值