sychronized (monitor监视器) -- 自旋获取锁形式
把代码块声明为 synchronized,有两个重要后果,通常是指该代码具有 原子性(atomicity)和 可见性(visibility)。原子性意味着一个线程一次只能执行由一个指定监控对象(lock)保护的代码,从而防止多个线程在更新共享状态时相互冲突。可见性则更为微妙;它要对付内存缓存和编译器优化的各种反常行为。
monitorenter & monitorexit
ReentranLock -- 阻塞获取锁形式
Lock
框架是锁定的一个抽象,它允许把锁定的实现作为 Java 类,而不是作为语言的特性来实现。这就为 Lock
的多种实现留下了空间,各种实现可能有不同的调度算法、性能特性或者锁定语义。 ReentrantLock
类实现了 Lock
,它拥有与 synchronized
相同的并发性和内存语义,但是添加了类似锁投票、定时锁等候和可中断锁等候的一些特性。此外,它还提供了在激烈争用情况下更佳的性能。(换句话说,当许多线程都想访问共享资源时,JVM 可以花更少的时候来调度线程,把更多时间用在执行线程上。)
reentrant 锁意味着什么呢?简单来说,它有一个与锁相关的获取计数器,如果拥有锁的某个线程再次得到锁,那么获取计数器就加1,然后锁需要被释放两次才能获得真正释放。这模仿了 synchronized
的语义;如果线程进入由线程已经拥有的监控器保护的 synchronized 块,就允许线程继续进行,当线程退出第二个(或者后续) synchronized
块的时候,不释放锁,只有线程退出它进入的监控器保护的第一个 synchronized
块时,才释放锁。
ReentrantLock
构造器的一个参数是 boolean 值,它允许您选择想要一个 公平(fair)锁,还是一个 不公平(unfair)锁。公平锁使线程按照请求锁的顺序依次获得锁;而不公平锁则允许讨价还价,在这种情况下,线程有时可以比先请求锁的其他线程先得到锁。在现实中,公平保证了锁是非常健壮的锁,有很大的性能成本。要确保公平所需要的记帐(bookkeeping)和同步,就意味着被争夺的公平锁要比不公平锁的吞吐率更低。作为默认设置,应当把公平设置为 false
,除非公平对您的算法至关重要,需要严格按照线程排队的顺序对其进行服务。
在确实需要一些 synchronized 所没有的特性的时候,比如时间锁等候、可中断锁等候、无块结构锁、多个条件变量或者锁投票。 ReentrantLock
还具有可伸缩性的好处,应当在高度争用的情况下使用它,但是请记住,大多数 synchronized 块几乎从来没有出现过争用,所以可以把高度争用放在一边。我建议用 synchronized 开发,直到确实证明 synchronized 不合适,而不要仅仅是假设如果使用 ReentrantLock
“性能会更好”。请记住,这些是供高级用户使用的高级工具。(而且,真正的高级用户喜欢选择能够找到的最简单工具,直到他们认为简单的工具不适用为止。)。一如既往,首先要把事情做好,然后再考虑是不是有必要做得更快。
ReentrantLock之lock形式 -- for (;;){} 循环获取锁 -- 自旋
1、根据构造器创建lock,设定为公平锁or不公平锁
2、lock,获取锁
添加至Node队列,包含上Node地址,下Node地址,以及线程信息
获取锁队列,从head位开始,尝试去获取锁
获取方式:获取当前线程信息,得到锁当前状态,对处于state为0的,通过CAS操作,成功后将当前线程设置至可执行线程ExclusiveOwnerThread 。
如果失败,调用Thread的interrupted(),中断线程
3、unlock
更改状态,将ExclusiveOwnerThread 置为空
ReentrantLock扩展的功能
在内部锁中,死锁是致命的——唯一的恢复方法是重新启动程序,唯一的预防方法是在构建程序时不要出错。而可轮询的锁获取模式具有更完善的错误恢复机制,可以规避死锁的发生。
如果你不能获得所有需要的锁,那么使用可轮询的获取方式使你能够重新拿到控制权,它会释放你已经获得的这些锁,然后再重新尝试。可轮询的锁获取模式,由tryLock()方法实现。此方法仅在调用时锁为空闲状态才获取该锁。如果锁可用,则获取锁,并立即返回值true。如果锁不可用,则此方法将立即返回值false。此方法的典型使用语句如下:
Lock lock = ...; if (lock.tryLock()) { try { // manipulate protected state } finally { lock.unlock(); } } else { // perform alternative actions }
实现可定时的锁请求
动调用了阻塞方法,定时锁能够在时间预算内设定相应的超时。如果活动在期待的时间内没能获得结果,定时锁能使程序提前返回。可定时的锁获取模式,由tryLock(long, TimeUnit)方法实现。
只在时间范围内去获取锁,超出时间则认为无法获取锁。
实现可中断的锁获取请求
可中断的锁获取操作允许在可取消的活动中使用。lockInterruptibly()方法能够使你获得锁的时候响应中断。
ReentrantLock不好与需要注意的地方
Object.wait
做的那样。完整的await()操作是安装如下步骤进行的:
- 将当前线程加入Condition锁队列。特别说明的是,这里不同于AQS的队列,这里进入的是Condition的FIFO队列。进行2。
- 释放锁。这里可以看到将锁释放了,否则别的线程就无法拿到锁而发生死锁。进行3。
- 自旋(while)挂起,直到被唤醒或者超时或者CACELLED等。进行4。
- 获取锁(acquireQueued)。并将自己从Condition的FIFO队列中释放,表明自己不再需要锁(我已经拿到锁了)
FIFO(固定长度队列)
AQS的全称为(AbstractQueuedSynchronizer)
AQS是链表,有一个head引用来指向链表的头节点,AQS在初始化的时候head、tail都是null,在运行时来回移动。此时,我们最少至少知道AQS是一个基于状态(state)的链表管理方式。
列的结构就变成了以下这种情况了,通过这样的方式,就可以让执行完的节点释放掉内存区域,而不是无限制增长队列,也就真正形成FIFO了。
AQS获取锁是通过tryAcquire去获取的,本身并没有获取方式,释放锁是release,通过tryRelease。
原理:
ReentraneLock 是Lock的一种实现,也是java层次锁的体现,而非synchronized语言特性的形式。ReentraneLock最基本的核心在于AQS,AbstractQuenedSynchronizer提供了一系列的模板方法,
通过将lock和unlock操作分别交给子类去实现,AQS提供了公平锁和非公平锁2种模式。而ReentraneLock是使用的Sync,是AQS的一个子类,使用的是非公平锁。
AQS会把请求线程放入一个CLH队列中,由线程去尝试获取锁,如果成功,则将当前运行线程设置为该线程,并将state状态从0设置为1,再重入则会继续加1。如果线程尝试去获取锁的时候,发现已经有
线程将锁独占,这时AQS会将线程包装为一个Node放入CLH队列的tail处,然后对线程进行阻塞。在阻塞前会再次的请求获取锁,如果还是没有获取到锁,则将线程阻塞。
CLH队列的含义:CLH为队列锁,队列中的Node对标注为是否需要获取锁。