Java中的锁

Synchronized同步锁

synchronized它可以把任意一个非NULL的对象当做锁。它属于独占式悲观锁,同时属于可重入锁。

Synchronized作用范围

  1. 作用于方法时,锁住是对象的实例(this);
  2. 当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PerGen(jdk1.8则是metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有该调用改方法的线程。
  3. synchronized作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。他有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。

synchronized核心组件

  • Wait Set:那些调用wait方法被阻塞的线程被放置到这里
  • Contention List :竞争队列,所有请求锁的线程首先被放置到这个竞争队列中;
  • Entry List :Contention List 中那些有资格成为候选资格的线程被移动到Entry List中
  • OnDeck : 任意时刻,最多只能有一个线程正在竞争锁资源,该资源成为OnDeck;
  • Owner : 目前已经获取到所有资源的线程被称为Owner
  • !Owner : 当前释放的线程

Synchronized实现

 

  1. JVM每次从队列的尾部取出一个数据用于竞技候选者(OnDeck),但是并发情况下,ContentionList会被大量的并发线程进行CAS访问,为了降低对尾部元素的竞争,JVM会将一部分线程移动到EntryList中作为候选竞争线程。
  2. Owner线程会在unlock时,将ContentionList中的部分迁移到EntryList中,并制定EntryList中的某一个线程为OnDeck线程(一般是最先进去的那个线程)。
  3. Owner线程并不会直接把锁传递给OnDeck线程,二十把锁竞争的权利交给OnDeck,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大地提升系统的吞吐量,在JVM中,也把这种选择行为称之为“竞争切换”。
  4. OnDeck线程获取到锁资源后会变为Owner线程,而没有得到资源得到任然停留在EntryList中,如果Owner线程被wait方法阻塞,则转移到WaitSet队列中,知道某个时刻通过notify或者notifyAll唤醒,会重新进去EntryList中。
  5. 出于ContentionList、EntyList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的(Linux内核下采用pthread_mutex_lock内核函数实现的)。
  6. Synchronized是非公平锁。synchronized在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,着没显示对于进入的队列的线程是不公平的,还有一个不公平的事情就是自旋获取的线程还可能直接抢占OnDeck的线程的锁资源。
  7. 每个对象都有个monitor对象,加锁就是在竞争monitor对象,代码块加锁是在前后分别加上monitorenter和monitorexit质量来实现的,方法加锁是通过一个标记位来判断的。
  8. synchronized是一个重量级操作,需要调用操作系统相关接口,性能抵消的,有可能给线程加锁消耗时间比有操作消耗的时间更多。
  9. Java1.6,synchronized进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。在之后推出的Java1.7和1.8中,均对改关键字的实现机理做了优化。引入了偏向锁和轻量级锁。都是在对象投中有标记位,不需要经过操作系统加锁。
  10. 锁可以从偏向锁神升级到轻量级锁,在升级到重量级锁。这种升级叫做膨胀锁。
  11. JDK1.6中默认是开启偏向锁和轻量级锁,可以通过-xx:-UseBiasdLocking来禁用偏向锁。

ReentrantLock

ReentrantLock继承接口Lock并实现接口中定义的方法,他是一种可重入锁,除了能完成synchronized锁能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。

Lock接口的主要方法

  1. void lock():执行此方法时,如果锁处于空闲状态,当前线程将获取到锁,相反,如果锁已经被其他线程持有,将禁用当前线程,直到当前线程获取到锁。
  2. boolean trylock : 如果锁可用,则获取锁,并立即返回true,否则返回false,改方法和lock。的区别在于,trylock()只是“试图”获取锁,如果锁不可以用,不会导致当前线程被禁用,当前线程任然在继续往下执行代码,而lock()方法则是一定要获取到锁,如果锁不可用,就一直等待,在未获得锁之前,当前锁并不会继续往下执行。
  3. void unlock : 执行此方法时,当前线程将释放持有的锁,锁只能由持有者释放,如果线程并不持有锁,却执行该锁方法,可能导致异常发生。
  4. condition newCondition:条件对象,获取等待通知组件。该组件和当前的锁绑定, 当前线程只有获取了锁,才能调用该组件的await。方法,而调用后,当前线程将缩放锁。
  5. getHoldCount。:査询当前线程保持此锁的次数,也就是执行此线程执行lock方法的次 数。
  6. getQueueLength ():返回正等待获取此锁的线程估计数,比如启动10个线程,1个 线程获得锁,此时返回的是9
  7. getWaitQueueLength:     (Condition condition)返回等待与此锁相关的给定条件的线
  8. 程估计数。比如10个线程,用同一个condition对象,并且此时这10个线程都执行了 condition对象的await方法,那么此时执行此方法返回10

  9. hasQueuedThread(Thread thread):査询给定线程是否等待获取此锁
  10. hasQueuedThreads。:是否有线程等待此锁
  11. isFairO:该锁是否公平锁
  12. isHeldByCurrentThread。:当前线程是否保持锁锁定,线程的执行lock方法的前后分 别是false和true
  13. isLock():此锁是否有任意线程占用
  14. lockInterruptibly ():如果当前线程未被中断,获取锁
  15. tryLock ():尝试获得锁,仅在调用时锁未被线程占用,获得锁
  16. tryLock(long timeout TimeUnit unit):如果锁在给定等待时间内没有被另一个线程保持, 则获取该锁
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值