Synchronized同步锁
synchronized它可以把任意一个非NULL的对象当做锁。它属于独占式悲观锁,同时属于可重入锁。
Synchronized作用范围
- 作用于方法时,锁住是对象的实例(this);
- 当作用于静态方法时,锁住的是Class实例,又因为Class的相关数据存储在永久带PerGen(jdk1.8则是metaspace),永久带是全局共享的,因此静态方法锁相当于类的一个全局锁,会锁所有该调用改方法的线程。
- synchronized作用于一个对象实例时,锁住的是所有以该对象为锁的代码块。他有多个队列,当多个线程一起访问某个对象监视器的时候,对象监视器会将这些线程存储在不同的容器中。
synchronized核心组件
- Wait Set:那些调用wait方法被阻塞的线程被放置到这里
- Contention List :竞争队列,所有请求锁的线程首先被放置到这个竞争队列中;
- Entry List :Contention List 中那些有资格成为候选资格的线程被移动到Entry List中
- OnDeck : 任意时刻,最多只能有一个线程正在竞争锁资源,该资源成为OnDeck;
- Owner : 目前已经获取到所有资源的线程被称为Owner
- !Owner : 当前释放的线程
Synchronized实现
- JVM每次从队列的尾部取出一个数据用于竞技候选者(OnDeck),但是并发情况下,ContentionList会被大量的并发线程进行CAS访问,为了降低对尾部元素的竞争,JVM会将一部分线程移动到EntryList中作为候选竞争线程。
- Owner线程会在unlock时,将ContentionList中的部分迁移到EntryList中,并制定EntryList中的某一个线程为OnDeck线程(一般是最先进去的那个线程)。
- Owner线程并不会直接把锁传递给OnDeck线程,二十把锁竞争的权利交给OnDeck,OnDeck需要重新竞争锁。这样虽然牺牲了一些公平性,但是能极大地提升系统的吞吐量,在JVM中,也把这种选择行为称之为“竞争切换”。
- OnDeck线程获取到锁资源后会变为Owner线程,而没有得到资源得到任然停留在EntryList中,如果Owner线程被wait方法阻塞,则转移到WaitSet队列中,知道某个时刻通过notify或者notifyAll唤醒,会重新进去EntryList中。
- 出于ContentionList、EntyList、WaitSet中的线程都处于阻塞状态,该阻塞是由操作系统来完成的(Linux内核下采用pthread_mutex_lock内核函数实现的)。
- Synchronized是非公平锁。synchronized在线程进入ContentionList时,等待的线程会先尝试自旋获取锁,如果获取不到就进入ContentionList,着没显示对于进入的队列的线程是不公平的,还有一个不公平的事情就是自旋获取的线程还可能直接抢占OnDeck的线程的锁资源。
- 每个对象都有个monitor对象,加锁就是在竞争monitor对象,代码块加锁是在前后分别加上monitorenter和monitorexit质量来实现的,方法加锁是通过一个标记位来判断的。
- synchronized是一个重量级操作,需要调用操作系统相关接口,性能抵消的,有可能给线程加锁消耗时间比有操作消耗的时间更多。
- Java1.6,synchronized进行了很多的优化,有适应自旋、锁消除、锁粗化、轻量级锁及偏向锁等,效率有了本质上的提高。在之后推出的Java1.7和1.8中,均对改关键字的实现机理做了优化。引入了偏向锁和轻量级锁。都是在对象投中有标记位,不需要经过操作系统加锁。
- 锁可以从偏向锁神升级到轻量级锁,在升级到重量级锁。这种升级叫做膨胀锁。
- JDK1.6中默认是开启偏向锁和轻量级锁,可以通过-xx:-UseBiasdLocking来禁用偏向锁。
ReentrantLock
ReentrantLock继承接口Lock并实现接口中定义的方法,他是一种可重入锁,除了能完成synchronized锁能完成的所有工作外,还提供了诸如可响应中断锁、可轮询锁请求、定时锁等避免多线程死锁的方法。
Lock接口的主要方法
- void lock():执行此方法时,如果锁处于空闲状态,当前线程将获取到锁,相反,如果锁已经被其他线程持有,将禁用当前线程,直到当前线程获取到锁。
- boolean trylock : 如果锁可用,则获取锁,并立即返回true,否则返回false,改方法和lock。的区别在于,trylock()只是“试图”获取锁,如果锁不可以用,不会导致当前线程被禁用,当前线程任然在继续往下执行代码,而lock()方法则是一定要获取到锁,如果锁不可用,就一直等待,在未获得锁之前,当前锁并不会继续往下执行。
- void unlock : 执行此方法时,当前线程将释放持有的锁,锁只能由持有者释放,如果线程并不持有锁,却执行该锁方法,可能导致异常发生。
- condition newCondition:条件对象,获取等待通知组件。该组件和当前的锁绑定, 当前线程只有获取了锁,才能调用该组件的await。方法,而调用后,当前线程将缩放锁。
- getHoldCount。:査询当前线程保持此锁的次数,也就是执行此线程执行lock方法的次 数。
- getQueueLength ():返回正等待获取此锁的线程估计数,比如启动10个线程,1个 线程获得锁,此时返回的是9
- getWaitQueueLength: (Condition condition)返回等待与此锁相关的给定条件的线
-
程估计数。比如10个线程,用同一个condition对象,并且此时这10个线程都执行了 condition对象的await方法,那么此时执行此方法返回10
- hasQueuedThread(Thread thread):査询给定线程是否等待获取此锁
- hasQueuedThreads。:是否有线程等待此锁
- isFairO:该锁是否公平锁
- isHeldByCurrentThread。:当前线程是否保持锁锁定,线程的执行lock方法的前后分 别是false和true
- isLock():此锁是否有任意线程占用
- lockInterruptibly ():如果当前线程未被中断,获取锁
- tryLock ():尝试获得锁,仅在调用时锁未被线程占用,获得锁
- tryLock(long timeout TimeUnit unit):如果锁在给定等待时间内没有被另一个线程保持, 则获取该锁