首页 > 代码库 > 锁机制及锁优化
锁机制及锁优化
锁
在Java中目前有两种锁机制防止代码块受到并发访问的干扰:。java语言提供了一个synchronized (内部锁)或Lock/Condition(显示锁) 关键达到这一目的,在java SE 5.0引入了Lock/ReentranLock(重入锁)类。
锁具有以下作用:
(1)锁用来保护代码片段,任何时刻只能有一个线程执行被保护的代码。
(2)锁可以哦管理视图进入被保护代码段的线程
(3)锁可以拥有一个或多个相关条件对象
(4)每个条件对象管理哪些已经进入被保护的代码片段但还不能运行的线程。
1.synchronized
从1.0开始,java中的每一个对象都有一个内部锁,如果一个方法使用synchronized 关键字声明,那么对象所讲保护整个方法,当某一个线程运行到该方法时,需要检查有没有其他线程正在使用这个方法,有的话需要等待那个线程运行完这个方法后在运行,没有的话,需要锁定这个方法,然后运行。
将静态方法声明为synchronized 也是合法的,如果调用这种方法,该方法获得相关的类对象的内部锁,因此,没有其他线程可以调用同一个类的同步静态方法。
内部锁条件的局限性:
(1)不能中断一个正在试图获得锁的线程
(2)试图获得锁时不能设定超时
(3)每个锁仅有单一的条件,可能是不够的。
2.Lock 和 Condition
当使用synchronied进行同步时,可以在同步代码块中只用常用的wait和notify等方法,在使用显示锁的时候,将通过Condition对象与任意Lock实现组合使用,为每个对象提供多个等待方法,其中Lock代替了synchronized方法和语句的使用,Condition代替了Object监视器方法的使用,条件Condition为线程提供了一个含义,以便在某个状态条出现可能为true,另一个线程通知它之前,一直挂起该线程,即让其等待,因为访问该共享状态信息发生在不同的线程中,所以它必须受到保护。
3.Lock 和 ReentrantLock
Lock 接口定义了一组抽象的锁定操作。与内部锁定(intrinsic locking)不同,Lock 提供了无条件的、可轮询的、定时的、可中断的锁获取操作,所有加锁和解锁的方法都是显式的。这提供了更加灵活的加锁机制,弥补了内部锁在功能上的一些局限——不能中断那些正在等待获取锁的线程,并且在请求锁失败的情况下,必须无限等待。
Lock 接口主要定义了下面的一些方法:
1)void lock():获取锁。如果锁不可用,出于线程调度目的,将禁用当前线程,并且在获得锁之前,该线程将一直处于休眠状态。
2)void lockInterruptibly() throws InterruptedException:如果当前线程未被中断,则获取锁。如果锁可用,则获取锁,并立即返回。如果当前线程在获取锁时被 中断,并且支持对锁获取的中断,则将抛出InterruptedException,并清除当前线程的已中断状态。
3)boolean tryLock():如果锁可用,则获取锁,并立即返回值 true。如果锁不可用,则此方法将立即返回值 false。
4)boolean tryLock(long time, TimeUnit unit) throws InterruptedException:如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁。
5)void unlock():释放锁。
6)Condition newCondition():返回绑定到此 Lock 实例的新 Condition 实
除以上方法外,ReentrantLock 还增加了一些高级功能,主要有以下3项:
(1)等待可中断:当持有锁的线程长期不释放锁时,正在等待的线程可以选择放弃等待,改为处理其他事情,可中断特性对处理执行时间非常长的同步块很有帮助。
(2)公平锁:多个线程在等待同一个锁时必须按照申请锁的时间顺序来依次获得锁,而非公平锁不能保证这一点,在锁被释放时,任何一个等待锁的线程都有机会获得锁,synchronized中的锁时非公平的,ReentranLock默认情况下也是非公平的,但可以通过带布尔值的构造函数要求使用公平锁。
(3)锁绑定多个条件:指一个ReentrantLock对象可以同时绑定多个Condition对象,er在synchronized中,锁对象的wait()和notify()或notifyAll()方法可以实现一个隐含的条件,如果和多余一个的条件关联的时候,就不得不额外添加一个锁,而ReentrantLock则无需这样做,只需要多次调用newCondition()方法即可。
例。调用 Condition.await() 将在等待前以原子方式释放锁,并在等待返回前重新获取锁。
ReentrantLock 实现了Lock 接口。获得ReentrantLock 的锁与进入synchronized块具有相同的语义,释放 ReentrantLock 锁与退出 synchronized 块有相同的语义。相比于 synchronized,ReentrantLock 提供了更多的灵活性来处理不可用的锁。
4.编程时锁的选择
1.最好既不是用 Lock/Condition 也不使用 synchronized关键字,在许多情况下,可以使用java.util.concurrent包中的一种机制,它会处理所有的加锁。
2.如果synchronized 关键字适合编写的程序,那就尽量使用它,这样可以减少编写的代码数量,减少出错的几率,如果特别需要Lock/Condition 结构提供的独有的特性时,才是用Lock/Condition 。
锁优化
高效 并发是从JDK1.5到JDK1.6的一个重要改进,目前的优化技术有 适应性自旋、锁消除、锁粗化、轻量级锁和偏向锁 等 这些技术是为了在线程之间更高效地共享数据,以解决竞争问题,从而提高程序的执行效率。
自旋锁与自适应锁:
如果物理机器上有一个以上的处理器,能让两个或两个 以上的线程同时并行执行,就可以让后面请求锁的那个线程稍微等待一下,但不放弃处理器的执行时间,看看持有线程的锁 是否很快就会释放锁,为了让线程等待,只需要让线程执行一个忙循环即自旋,这就是自旋锁。
如果所被占用的时间很短,自旋等待的效果就会非常的好,反之,自旋的线程只会白白得消耗处理器资源,而不会做任何有用的工作,反而带来性能上的浪费,因此,自选锁等待 的时间必须要有一定的限制,如果自旋锁超过了限定的次数仍然没有成功获得锁,就应当使用传统的方式挂起锁了,默认想、次数是10。
锁消除:
锁消除是Java虚拟机在JIT编译时,通过对运行上下文的扫描,去除不可能存在共享资源竞争的锁,通过锁消除,可以节省毫无意义的请求锁时间。
锁粗化:
如果一系列的连续操作都是对同一对象反复加锁和解锁,甚至加锁操作时出现在循环体中,那即是没有线程竞争,频繁地进行互斥同步操作也会导致不必要的性能损耗,如果虚拟机 探测到有这样一串零碎的操作都对同一个对象加锁,将会把加锁同步范围扩展到整个操作序列的外部,就 扩展到第一个append()操作之前直至最后一个append()操作之后,这样只 需要加锁一次就好。
轻量级锁:
轻量级锁是JDK1,6中加入的新型锁机制,它是在没有多线程竞争的前提下,减少传统的重量级锁使用操作系统互斥量产生的性能消耗。
偏向锁:
在JVM1.6中引入了偏向锁,偏向锁主要解决无竞争下的锁性能问题,首先我们看下无竞争下锁存在什么问题:
现在几乎所有的锁都是可重入的,也即已经获得锁的线程可以多次锁住/解锁监视对象,按照之前的HotSpot设计,每次加锁/解锁都会涉及到一些CAS操作(比如对等待队列的CAS 操作), CAS操作会延迟本地调用,因此偏向锁的想法是一旦线程第一次获得了监视对象,之后让监视对象“偏向”这个线程,之后的多次调用则可以避免CAS操作,说白了就是置个 变量,如果发现为true则无需再走各种加锁/解锁流程。
锁机制及锁优化