【Java 基础 6】锁-sychronized和Lock

3 篇文章 0 订阅

sychronized

Java内置锁,基于Montier实现,通过moniterenter进入和moniterexit退出实现方法和代码块的同步,Montier依赖于操作系统底层的MutexLock(互斥锁)实现

作用方式

普通同步方法(实例方法),锁是当前实例对象 ,进入同步代码前要获得当前实例的锁
静态同步方法,锁是当前类的class对象 ,进入同步代码前要获得当前类class对象的锁
同步方法块,锁是括号里面的对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁
 

Lock

实现Lock接口的锁有ReentrantReadWriterLock中的读锁和写锁、StampedLock中的读锁和写锁、ReentrantLock还有ConcurrentHashMap的Segment也是继承了ReentrantLock实现的分段锁(给每一个Segment加一个ReentrantLock锁)。

ReentrantReadWriterLock,ReentrantLock,信息量等内部维护一个继承AbstractQueuedSynchronizer(AQS, 抽象队列同步器)的同步锁Sycn

AQS

AQS内部主要维护了

1. Node head 2. Node tail 用于存储未获取到锁资源的线程,进入队列中的线程被LockSupport.park()阻塞,head为一个指向队列中第一个节点的头节点(本身不存储节点值)

3. int state记录被上锁的次数

4. Thread exclusiveOwnerThread 记录当前持有锁的线程

前3个属性被volatile修饰,基于CAS进行修改,当state被修改为0,exclusiveOwnerThread置空,同时LockSupport.unpark()唤醒队列中线程。

exclusiveOwnerThread属性保证锁的可重入,锁被lock了几次,必须unlock几次才能完全释放。

ReentrantLock

抽象类Sync继承了AQS,它的两个具体实现类FairSync和NonfairSync实现了公平锁和非公平锁。

ReentrantLock默认的无参构造方法通过NonfairSync创建一个非公平锁Sync, 可以通过带一个Boolean形参的构造方法指定锁类型。

加锁过程

ReentrantLock实例调用一次lock()方法,

1. 通过成员Sync锁调用AQS的acqurire(1)去尝试获取资源tryAccquire(1),若获取成功,则acqurire()方法中的if为false不再往下执行,方法正常结束,返回到主线程中表示已获取锁资源;

2. 若获取资源失败则尝试添加到队列中排队acquireQueued(addWaiter(Node.EXCLUSIVE),1),通过parkAndCheckInterrupt()方法调用LockSupport的park方法阻塞线程;

3. 若都失败,则通过selfInterrupt()调用线程的interrupt()方法中断该线程。

解锁过程

ReentrantLock实例调用一次unlock()方法,

1. 通过成员Sync锁调用AQS的release(1)去尝试释放资源tryRelease(1),若释放资源成功,则

2. 通过unparkSuccessor(head)方法调用LockSupport.unpark(head.thread)唤醒阻塞队列中的头节点的线程。

线程不安全是因为写操作造成,多个线程同时读并不会造成线程不安全,便有了ReadWriterLock

ReentrantReadWriteLock

读锁加锁调用的是acqurieShared()方法,解锁调用的是relaseShared()方法(共享方法)

写锁加锁调用的是acqurie()方法,解锁调用的是relase()方法(独占方法)

读锁可重入次数也是有限的,当同时占用读锁的线程数达到上线,新的线程进入阻塞队列等待

写锁的可重入最大数为MAX_COUNT,默认为2的16次方-1。

【Java并发编程】ReentrantReadWriteLock源码及实现原理分析 这篇文章写的很清楚,建议学习。

StampedLock

当读锁被占用时,试图获取写锁的线程阻塞等待,这是一种悲观锁的表现,为了提高性能,jdk1.8开始引入StampedLock,在读的过程中允许写锁获取,但是这样对于读操作可能出现读取数据不一致的问题,因此引入类似Mysql的ABA问题解决方案-版本号,返回一个邮戳(Stamp),通知读线程数据被修改了。

写锁

排它锁或者叫独占锁,同时只有一个线程可以获取该锁,并且不可重入,请求该锁成功后会返回一个stamp票据变量用来表示该锁的版本,当释放该锁时候需要unlockWrite并传递参数stamp;

读锁,支持悲观锁和乐观锁两种模式

悲观锁模式下写锁和读锁不可同时获取,自然能够保证数据的一致性;

乐观锁模式下,不涉及CAS操作,获取读锁只是使用【与或】操作进行检验

如果当前没有线程持有写锁,则简单的返回一个非0的stamp版本信息,

调用乐观读锁判断【tryOptimisticRead()返回stamp后 到 操作时刻】时间段内是否有其他线程持有了写锁,

如果是validate(stamp)返回0,则获取读锁失败,

否则就可以使用该stamp版本的锁对数据进行操作。

 


死锁的产生条件:

1. 互斥

线程T1占有A时,其他线程只能等待T1释放A

2. 不可剥夺

线程T1占有A时,线程T1不释放,其他线程不能得到A

3. 请求和等待

线程T1在等待被其他线程锁住的B时不释放自己锁住的A

4. 循环等待

线程循环等待资源

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值