多线程相关(3)

乐观锁,CAS思想

java乐观锁机制

乐观锁体现的是悲观锁的反面。它是一种积极的思想,它总是认为数据是不会被修改的,所以是不会对数据上锁的
但是乐观锁在更新的时候会去判断数据是否被更新过
乐观锁的实现方案一般有两种:版本号机制和CAS
乐观锁适用于读多写少的场景,这样可以提高系统的并发量。
在Java中 java.util.concurrent.atomic下的原子变量类就是使用了乐观锁的一种实现方式CAS实现的

乐观锁,大多是基于数据版本 (Version) 记录机制实现。
即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个 “version” 字段来实现。
读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。

此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。

CAS思想

CAS就是compare and swap(比较交换),是一种很出名的无锁的算法,就是可以不使用锁机制实现线程间的同步。使用CAS线程是不会被阻塞的,所以又称为非阻塞同步。
CAS算法涉及到三个操作:
1.需要读写内存值V;2.进行比较的值A;3.准备写入的值B
当且仅当V的值等于A的值的时候,才用B的值去更新V的值,否则不会执行任何操作(比较和替换是一个原子操作:A和V比较,V和B替换),一般情况下是一个自旋操作,即不断重试。

这其中还包括 ABA 的问题。
即:将A修改为B,随后再修改成A,初始值和终值相同,无法判断中间是否发生过变化。

原子性
功能限制CAS是能保证单个变量的操作是原子性的,在Java中要配合使用volatile关键字来保证线程的安全;当涉及到多个变量的时候CAS无能为力;除此之外CAS实现需要硬件层面的支持,在Java的普通用户中无法直接使用,只能借助atomic包下的原子类实现,灵活性受到了限制。

synchronized底层实现

主要的三种使用方法

  • 修饰实例方法:给当前对象实例加锁,进⼊同步代码前要获得当前对象实例的锁。
  • 修饰静态方法:也就是给当前类加锁,会作⽤于类的所有对象实例,因为静态成员不属于任何⼀个实例对象,是类成员。
  • 修饰代码块:指定加锁对象,对给定对象加锁,进⼊同步代码库前要获得给定对象的锁。

总结:synchronized锁住的资源只有两类:一个是对象,一个是类。

底层实现

对象头是我们需要关注的重点,它是synchronized实现锁的基础,因为synchronized申请锁、上锁、释放锁都与对象头有关。
对象头主要结构是由Mark Word组成,其中Mark Word存储对象的hashCode、锁信息或分代年龄或GC标志等信息
锁也分不同状态,JDK6之前只有两个状态:无锁、有锁(重量级锁);而在JDK6之后对synchronized 进行了优化,新增了两种状态,总共就是四个状态:无锁状态、偏向锁、轻量级锁、重量级锁,其中无锁就是一种状态了。锁的类型和状态在对象头Mark Word中都有记录,在申请锁、锁升级等过程中JVM都需要读取对象的Mark Word数据

同步代码块是利用 monitorenter 和 monitorexit 指令实现的,而同步方法则是利用 flags 实现的

ReenTrantLock底层实现

由于 ReentrantLock 是 java.util.concurrent 包下提供的一套互斥锁,相比 Synchronized,ReentrantLock 类提供了一些高级功能。

使用方法

基于API层面的互斥锁,需要 lock() 和 unlock() 方法配合 try-finally 语句块来完成。

底层实现

ReenTrantLock 的实现是一种自旋锁,通过循环调用 CAS 操作来实现加锁。
它的性能比较好也是因为避免了使线程进入内核态的阻塞状态
想尽办法避免线程进入内核的阻塞状态是我们去分析和理解锁设计的关键

与synchronized区别

  1. 底层实现:synchronized 是JVM层面的锁,是Java关键字,通过monitor对象来完成(monitorenter 与 monitorexit);ReentrantLock 是从jdk1.5以来( java.util.concurrent.locks.Lock )提供的 API 层面的锁。
  2. 实现原理:synchronized 的实现涉及到锁的升级,具体为无锁、偏向锁、自旋锁、向 OS 申请重量级锁;ReentrantLock 实现则是通过利用CAS(Compare And Swap)自旋机制保证线程操作的原子性和 volatile保证数据可见性以实现锁的功能。
  3. 释放方式:synchronized 不需要用户去手动释放锁,synchronized 代码执行完后系统会自动让线程释放对锁的占用;ReentrantLock则需要用户去手动释放锁,如果没有手动释放锁,就可能导致死锁现象。
  4. 是否可中断synchronized 是不可中断类型的锁,除非加锁的代码中出现异常或正常执行完成;ReentrantLock则可以中断,可通过trylock( long timeout, TimeUnit unit) 设置超时方法或者将 lockInterruptibly() 放到代码块中,调用 interrupt 方法进行中断。
  5. 是否公平锁synchronized为非公平锁;ReentrantLock则既可以选公平锁也可以选非公平锁,通过构造方法 new ReentrantLock() 时传入boolean值进行选择,为空默认false-非公平锁,true-公平锁,公平锁性能非常低

公平锁和非公平锁区别

公平锁

公平锁自然是遵循 FIFO(先进先出)原则的,先到的线程会优先获取资源,后到的会进行排队等待。
优点:所有的线程都能得到资源,不会饿死在队列中。适合大任务。
缺点:吞吐量会下降,队列里面除了第一个线程,其他的线程都会阻塞,cpu唤醒阻塞线程的开销大。

非公平锁

多个线程去获取锁的时候,会直接去尝试获取,获取不到,再去进入等待队列,如果能获取到,就直接获取到锁。
优点:可以减少CPU唤醒线程的开销,整体的吞吐效率会高点,CPU也不必取唤醒所有线程,会减少唤起线程的数量。
缺点:这样可能导致队列中间的线程一直获取不到锁或者长时间获取不到锁
获取锁机制

公平锁效率低原因

公平锁要维护一个队列,后来的线程要加锁,即使锁空闲,也要先检查有没有其他线程在 wait,如果有自己要挂起,加到队列后面,然后唤醒队列最前面线程。这种情况下相比较非公平锁多了一次挂起和唤醒
线程切换的开销,其实就是非公平锁效率高于公平锁的原因,因为非公平锁减少了线程挂起的几率,后来的线程有一定几率逃离被挂起的开销。

原文:https://mp.weixin.qq.com/s/IVgGXQKU1QiT1ToN2wXHJg

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值