java锁相关介绍与分析

本文主要介绍两种锁:synchronized和lock,以及锁相关比较重要的知识:volatile,atomic,锁的升级

1.synchronized

三种同步形式

同步静态方法时,锁住的是类实例,因为类信息存放在方法区,是全局共享的,所以会将所有调用该方法的线程全部锁住.

同步普通方法,锁住的是对象的实例(this)

同步一个对象实例(同步代码块)时,以该对象为锁的代码块.

synchronized的实现

每个实例的实例头的重量级锁指针都会指向一个ObjectMonitor对象,下面是该对象的部分变量:

线程执行到同步代码模块时,线程会执行monitorentry指令(C++实现)来获取monitor的所有权,这时有三种情况:

  • _count为0时线程获取到monitor,_owner指向该线程,_count+1
  • 如果_owner就是自己的线程,属于重新进入则_count+1
  • 如果_owner是其他线程,线程为阻塞状态,当_count==0的时候重新尝试获取monitor

当线程释放锁的时候会将阻塞状态的线程放入_entryList中,_entryList中的线程竞争锁,如果调用wait()方法,会将线程放入_WaitSet中,等待被唤醒

在线程释放线程的时候会执行monitorexit方法,先将_count-1,如果_count==0则退出monitor;monitorexit和monitorentry必须成对出现.

针对于synchronized()中放什么的问题:

如果放实例对象如this,这时可以防止多个线程同时访问这个以这个对象为锁的synchronized代码块或者这个对象的synchronized方法.

如果放某个类如Object.class,那么可以防止多个线程同时访问以这个类或这个类的对象为锁的synchronized代码块或者这个类建立的实例对象中的synchronized方法(即对此类的所有实例对象起作用)

2.lock

ReentrantLock(重入锁)

ReentrantLock是一种用java实现的可以取代synchronized的锁,操作更灵活,可以通过tryLock方法尝试获取锁,而不阻塞;也可以设置公平锁和非公平锁,而java1.5版本以后,synchronized锁进行了锁升级优化,性能方面差别不大.

ReentrantLock的实现原理

ReentrantLock实现的前提就是AbstractQueuedSynchronizer,简称AQS,是java.util.concurrent的核心,ReentrantLock的基础实现类:sync是继承了AQS,而公平锁和非公平锁实现类则继承了sync。而AQS的基础是CAS,这里解释一下CAS:

cas即compareAndSwap,原理就是先对比,获取内存地址上的数据和期望值做对比(这个期望值可能是上一次获取到的),对比成功则用新值替换旧值.

AQS是基于FIFO队列实现的,提供了等待线程队列的模型以及队列的操作方法,如acquire获取锁方法,release释放锁方法等,在reentrantlock的lock()方法和unlock()方法中实现了大部分代码,如果想知道AQS操作的大致流程,可以看下lock()方法和unlock()方法的实现源码,本文只介绍大体步骤

lock()方法:

首先用一个ACS操作尝试获取线程,在获取失败的情况下调用来自子类的方法tryAcquire,这里可以实现ReentrantLook自己的判断方法和逻辑,然后调用AQS的新建或添加队列的方法,将线程放入队列,然后向前遍历到一个状态不为CANCELLED的节点,将这个节点状态设置为通知状态(即释放锁后通知下一个线程)然后将自己挂起。

unlock()方法

流程大致为先尝试释放锁,若释放成功,那么查看头结点的状态是否为SIGNAL,如果是则唤醒头结点的下个节点关联的线程,如果释放失败那么返回false表示解锁失败。

ReentrantReadWriteLock(读写锁)

读写锁的访问约束:

  • 读-读不互斥:读读之间不阻塞
  • 读-写互斥:读堵塞写,写也阻塞读
  • 写-写互斥:写写阻塞

如果在一个系统中读的操作次数远远大于写操作,那么读写锁就可以发挥明显的作用,提升系统性能。

通过reentrantReadWriteLock.readLock();reentrantReadWriteLock.writeLock();获取到读写锁,在lock()的时候就会遵守上述的方位约束了。

3.volatile

关键字volatile的作用是强制从公共堆栈中取得变量的值,而不是从线程私有数据栈中取得变量的值,不具有原子性,通常用来标记boolean变量作为标记位.

4.atomic

atomic是JUC提供的一个为线程安全设计的java包,通过名字可以看出,改包主要实现的是数据的原子性,包中的类有:

  • 标量类:AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference
  • 数组类:AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
  • 更新器类:AtomicLongFieldUpdater,AtomicIntegerFieldUpdater,AtomicReferenceFieldUpdater
  • 复合变量类:AtomicMarkableReference,AtomicStampedReference
  • 在Java 8中引入了4个新的计数器类型,LongAdder、LongAccumulator、DoubleAdder、DoubleAccumulator。

atomic可以保证线程安全的对一些基础数据做一些简单高效的原子操作,如AtomicInteger的自增1功能,实现原理就很简单,为自旋+CAS:如上边介绍CAS所述,先从内存地址中获取该数值,然后进行加一操作得到新数值,再次获取内存地址中的数值和上一次得到的旧数值(预期值)做对比,如果相同,则新数组替换旧数值,如果不同重新循环执行上边的步骤(阻塞),直到成功

java8后通过降低粒度(多段锁)增加并发性能.

解决ABA问题:

ABA问题即在CAS对比的时候发生了数据从A替换到B又替换到A,也能CAS成功,这种情况大多数时候是无所谓的,因为我们只关心数据的值,但是在某些情况下也需要防止,可以通过下边的原子类解决ABA问题:

  • AtomicStampedReference原子类是一个带有时间戳的对象引用,在每次修改后,AtomicStampedReference不仅会设置新值而且还会记录更改的时间。当AtomicStampedReference设置对象值时,对象值以及时间戳都必须满足期望值才能写入成功,这也就解决了反复读写时,无法预知值是否已被修改的窘境
     

5.锁的升级

锁的升级是1.5版本以后,java针对synchronized性能较差做的改进,锁的升级过程为视竞争的严重性,从无竞争到强烈竞争经历:偏向锁->轻量级锁->重量级锁

  • (偏向锁)线程想要获取锁,先检查对象头中的锁线程id是否是自己,如果是自己就直接获取,如果不是自己,就看指向的线程是否存活,如果不存活,则通过CAS将自己的线程id设置进去,获取锁
  • (偏向锁升级轻量级锁)如果存活,锁升级为轻量级锁,暂停正在执行的线程将对象头的Mark Word置换到当前线程的栈帧当中,想要获取锁的线程采用CAS的方式循环等待
  • (轻量级锁升级为重量级锁)如果轻量级锁自旋的次数达到了一定的数值,如10次或100次,这是又有一个线程来竞争锁,这时会升级为重量级锁,将除了正在执行的线程都阻塞.
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值