Lock和synchronized这两个最多见的锁均可以达到线程安全的目的,可是功能上有很大不一样。java
Lock并非用来代替synchronized的而是当使用synchronized不知足状况或者不合适的时候来提供高级功能的程序员
为何synchronized不够用算法
灵活性的提升带来了额外的责任。 缺乏块结构锁定须要手动的去释放锁。 在大多数状况下,应使用如下惯用法:编程
Lock lock = new ReentrantLock(); lock.lock(); try{ }finally { lock.unlock(); }
当锁定和解锁发生在不一样的范围内时,必须当心以确保经过try-finally或try-catch保护持有锁定时执行的全部代码,以确保在必要时释放锁定。
Lock实现经过使用非阻塞尝试获取锁( tryLock() ),尝试获取可被中断的锁( lockInterruptibly以及尝试获取锁),提供了比使用synchronized方法和语句更多的功能。可能会超时( tryLock(long, TimeUnit) )。安全
Lock类还能够提供与隐式监视器锁定彻底不一样的行为和语义,例如保证顺序,不可重用或死锁检测。 若是实现提供了这种特殊的语义,则实现必须记录这些语义。服务器
请注意, Lock实例只是普通对象,它们自己能够用做synchronized语句中的目标。 获取Lock实例的监视器锁与调用该实例的任何lock方法没有指定的关系。 建议避免混淆,除非在本身的实现中使用,不然不要以这种方式使用Lock实例。数据结构
全部Lock实现必须强制执行与内置监视器锁所提供的相同的内存同步语义,如Java语言规范中所述 :并发
不成功的锁定和解锁操做以及可重入的锁定/解锁操做不须要任何内存同步效果。ide
实施注意事项工具
锁获取的三种形式(可中断,不可中断和定时)在其性能特征可能有所不一样。 此外,在给定的Lock类中,可能没法提供中断正在进行的锁定的功能。 所以,不须要为全部三种形式的锁获取定义彻底相同的保证或语义的实现,也不须要支持正在进行的锁获取的中断。 须要一个实现来清楚地记录每一个锁定方法提供的语义和保证。 在支持锁获取中断的范围内,它还必须服今后接口中定义的中断语义:所有或仅在方法输入时才这样作。
void lock(); // 获取锁。
注意:lock()方法不能被中断,这会带来很大的隐患:一旦陷入死锁、lock()就会陷入永久等待状态
void lockInterruptibly() throws InterruptedException;
除非当前线程被中断,不然获取锁。
获取锁(若是有)并当即返回。
若是该锁不可用,则出于线程调度目的,当前线程将被挂起,并在发生如下两种状况之一以前处于休眠状态:
若是当前线程:在进入此方法时已设置其中断状态;要么获取锁时被中断,而且支持锁获取的中断,而后抛出InterruptedException并清除当前线程的中断状态。
注意事项
在某些实现中,中断锁获取的能力多是不可能的,而且若是可能的话多是昂贵的操做。 程序员应意识到多是这种状况。 在这种状况下,实现应记录在案。与正常方法返回相比,实现可能更喜欢对中断作出响应。Lock实现可能可以检测到锁的错误使用,例如可能致使死锁的调用,而且在这种状况下可能引起(未经检查的)异常。
注意 synchronized 在获取锁时是不可中断的
boolean tryLock();
非阻塞获取锁(若是有)并当即返回true值。 若是锁不可用,则此方法将当即返回false值。相比于Lock这样的方法显然功能更增强大,咱们能够根据是否能获取到锁来决定后续程序的行为
注意:该方法会当即返回,即使在拿不到锁的时候也不会在一只在那里等待
该方法的典型用法是:
Lock lock = new ReentrantLock(); if(lock.tryLock()){ try{ // TODO }finally { lock.unlock(); } }else{ // TODO }
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
若是线程在给定的等待时间内获取到锁,而且当前线程还没有中断,则获取该锁。
若是锁可用,则此方法当即返回true值。 若是该锁不可用,则出于线程调度目的,当前线程将被挂起,并处于休眠状态,直到发生如下三种状况之一:
若是通过了指定的等待时间,则返回值false 。 若是时间小于或等于零,则该方法将根本不等待。
注意事项
在某些实现中,中断锁获取的能力多是不可能的,而且若是可能的话多是昂贵的操做。 程序员应意识到多是这种状况。 在这种状况下,实现应记录在案。与正常方法返回或报告超时相比,实现可能更喜欢对中断作出响应。Lock实现可能可以检测到锁的错误使用,例如可能致使死锁的调用,而且在这种状况下可能引起(未经检查的)异常。
void unlock(); //释放锁。
注意事项
Lock实现一般会限制哪些线程能够释放锁(一般只有锁的持有者才能释放锁),而且若是违反该限制,则可能引起(未经检查的)异常。
Condition newCondition(); //返回绑定到此Lock实例的新Condition实例。
该组件与当前锁绑定,当前线程只有得到了锁。 才能调用该组件的wait()方法,而调用后,当前线程将释放锁。
注意事项
Condition实例的确切操做取决于Lock实现。
Lock对象锁还提供了synchronized所不具有的其余同步特性,如可中断锁的获取(synchronized在等待获取锁时是不可中断的),超时中断锁的获取,等待唤醒机制的多条件变量Condition等,这也使得Lock锁具备更大的灵活性。Lock的加锁和释放锁和synchronized有一样的内存语义,也就是说下一个线程加锁后能够看到前一个线程解锁前发生的全部操做。
根据一下6种状况能够区分多种不一样的锁,下面详细介绍
是否锁住 | 锁名称 | 实现方式 | 例子 |
---|---|---|---|
锁柱 | 悲观锁 | synchronized、lock | synchronized、lock |
不锁住 | 乐观锁 | CAS算法 | 原子类、并发容器 |
悲观锁又称互斥同步锁,互斥同步锁的劣势:
悲观锁:
当一个线程拿到锁了以后其余线程都不能获得这把锁,只有持有锁的线程释放锁以后才能获取锁。
乐观锁:
本身才进行操做的时候并不会有其余的线程进行干扰,因此并不会锁住对象。在更新的时候,去对比我在修改期间的数据有没有人对他进行改过,若是没有改变则进行修改,若是改变了那就是别人改的那我就不改了放弃了,或者从新来。
开销对比:
使用场景:
是否共享 | 锁名称 |
---|---|
能够 | 共享锁(读锁) |
不能够 | 排他锁(独占锁) |
共享锁:
获取共享锁以后,能够查看可是没法修改和删除数据,其余线程此时也能够获取到共享锁也能够查看但没法修改和删除数据
案例:ReentrantReadWriteLock的读锁(具体实现后续系列文章会讲解)
排他锁:
获取排他锁的以后,别的线程是没法获取当前锁的,好比写锁。
案例:ReentrantReadWriteLock的写锁(具体实现后续系列文章会讲解)
是否排队 | 锁名称 |
---|---|
排队 | 公平锁 |
不排队 | 非公平锁 |
非公平锁:
先尝试插队,插队失败再排队,非公平是指不彻底的按照请求的顺序,在必定的状况下能够进行插队
存在的意义:
案例:
例如:当有线程执行tryLock的时候一旦有线程释放了锁,那么这个正在执行tryLock的线程立马就能获取到锁即便在它以前已经有其余线程在等待队列中
公平锁:
排队,公平是指的是按照线程请求的顺序来进行分配锁
案例:以ReentrantLock为例,建立对象的时候参数为true(具体实现后续系列文章会讲解)
注意:
非公平也一样不提倡插队行为,这里指的非公平是指在合适的时机插队,而不是盲目的插队
优缺点:
非公平锁:
公平锁:
是否能够重入 | 锁名称 |
---|---|
能够 | 可重入锁 |
不能够 | 不可重入锁 |
案例:以ReentrantLock为例(具体实现后续系列文章会讲解)
是否能够中断 | 锁名称 | 案例 |
---|---|---|
能够 | 可中断锁 | Lock是可中断锁(由于tryLock和lockInterruptibly都能响应中断) |
不能够 | 不可中断锁 | Synchronized就是不可中断锁 |
是否自旋 | 锁名称 |
---|---|
是 | 自旋锁 |
否 | 阻塞锁 |
使用场景:
这三种锁优化的方式在前一篇Synchronized文章种全部讲解