Lock锁
从JDK5.0开始,java提供了更为强大的线程同步机制——通过显式定义同步锁对象来实现同步
1.Lock接口
public interface Lock
Lock
实现提供更广泛的锁定操作可以比使用 synchronized
获得方法和报表。他们允许更灵活的结构,可以有完全不同的特性,可以支持多个相关的 Condition对象。
锁是一种通过多个线程控制对共享资源的访问的工具。通常,一个锁提供对共享资源的独占访问:在一个时间只有一个线程可以获得锁和所有访问共享资源,需要先获得锁。然而,有些锁可以允许并发访问共享资源,如一个ReadWriteLock读锁。
对synchronized
方法或报表提供了访问每个对象相关的隐式监视器锁的使用,但部队的所有锁的获取和释放在一个结构化的方式发生:当多个锁的获得必须以相反的顺序释放,和所有的锁必须释放相同的词法范围,他们获得。
而作用机制synchronized
方法和语句使它与监控锁程序更容易,并有助于避免许多常见的编程错误的锁,有时需要使用锁以更灵活的方式。例如,一些遍历并发访问的数据结构的算法需要使用“手在手”或“链锁”:你获得的锁的节点A,然后节点B,然后释放A和获取C,然后释放B和获取D等。该Lock
接口实现使通过允许一个锁被获取和在不同范围内发布这样的技术的使用,并允许多个锁被获取和发布任何命令。
随着这种增加的灵活性来增加额外的责任。块没有锁定解除锁结构与synchronized
方法和报表时自动释放。在大多数情况下,应该使用以下的成语:
Lock l = ...;
l.lock();
try {
// access the resource protected by this lock
} finally {
l.unlock();
}
当锁定和解锁在不同的范围时,必须注意确保所有的代码,而持有锁的执行受终于尝试或尝试抓住确保锁被释放时。
Lock
实现提供额外的功能来提供一个非阻塞的尝试获取锁的synchronized
方法和语句的使用(tryLock()),企图获取锁,可以中断(lockInterruptibly(),并试图获取锁,超时(tryLock(long, TimeUnit))。
一个Lock
类还可以提供行为和语义,从隐含的监视器锁完全不同,例如保证订货,不可重入的用法,或死锁检测。如果一个实现提供了这样的专门的语义,那么这些语义的实现必须记录这些语义。
注意,Lock
实例仅仅是普通的对象,也可以被用来作为目标在synchronized
声明。获取一个Lock
实例监控锁没有关系的任何指定调用该实例的lock()方法。这是建议,为了避免混淆,你这样没用Lock
实例,除了在自己的实现。
除特别注明外,传递任何参数null
值将导致NullPointerException抛出。
内存同步
所有的Lock
实现必须执行相同的内存同步语义的内置监控锁提供,如 The Java Language Specification (17.4 Memory Model):
- 一个成功的
lock
操作具有相同的内存同步的影响作为一个成功的锁定动作。 - 一个成功的
unlock
操作具有相同的内存同步的影响作为一个成功的解锁行动。
成功锁定和解锁操作,并可锁定/解锁操作,不需要任何内存同步的影响。
2.问题引入
线程间的不安全问题:
买票为例:
public class TestLock {
public static void main(String[] args) {
TestLock2 lock1 = new TestLock2();
// TestLock2 lock2 = new TestLock2();
// TestLock2 lock3 = new TestLock2();
//多个线程需要操作同一个对象
new Thread(lock1).start();
new Thread(lock1).start();
new Thread(lock1).start();
}
}
class TestLock2 implements Runnable{
int ticketNumber = 10;
public void run() {
while (true){
if (ticketNumber>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("获得票:"+(ticketNumber--));
}else {
break;
}
}
}
}
不安全(资源访问冲突)
之前的解决方法通过synchronized (Object)处理:
public class TestLock {
public static void main(String[] args) {
TestLock2 lock1 = new TestLock2();
// TestLock2 lock2 = new TestLock2();
// TestLock2 lock3 = new TestLock2();
//多个线程需要操作同一个对象
new Thread(lock1).start();
new Thread(lock1).start();
new Thread(lock1).start();
}
}
class TestLock2 implements Runnable{
int ticketNumber = 10;
public synchronized void run() {
while (true){
if (ticketNumber>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("获得票:"+(ticketNumber--));
}else {
break;
}
}
}
}
结果正确:
3.Lock的解决——ReentrantLock类
public class ReentrantLock
extends Object
implements Lock, Serializable
一个可重入的互斥 Lock具有相同的基本行为和语义为隐式监控锁使用 synchronized
方法和报表访问,但扩展功能。
一个ReentrantLock
是由线程最后成功锁定,但尚未解锁它。一个线程调用lock
将返回,成功获取锁,当锁不是由另一个线程拥有。如果当前线程已经拥有锁,该方法将立即返回。这可以使用方法isHeldByCurrentThread()检查,并getHoldCount()。
此类的构造函数接受一个可选的公平性参数。当设置true
,争,锁青睐授予访问最长等待线程。否则,此锁不保证任何特定的访问顺序。使用许多线程访问的公平锁的程序可能会显示较低的整体吞吐量(即,比那些使用默认设置慢,往往要慢得多),但有较小的差异,在时间获得锁和保证缺乏饥饿。请注意,锁的公平性并不能保证线程调度的公平性。因此,使用一个公平锁的许多线程之一可能会获得它的连续多次,而其他活动线程不进展,而不是目前持有的锁。还要注意,不定时的tryLock()方法不尊重公平设置。如果锁是可用的,即使其他线程正在等待,它也会成功的。
这是推荐的做法总是紧跟一个电话lock
与try
块,最典型的是在前/后建设等:
class X {
private final ReentrantLock lock = new ReentrantLock();
// ...
public void m() {
lock.lock(); // block until condition holds
try {
// ... method body
} finally {
lock.unlock()
}
}
}
除了实现Lock接口,这个类定义了用于检查锁的状态的一些public
和protected
方法。这些方法中的一些只是有用的仪器和监测。
这类的序列化的行为以同样的方式作为一个反序列化内置锁:锁处于解锁状态,无论其状态序列化时。
此锁支持同一线程的2147483647个递归锁的最大值。试图在Error超过这个限制的结果将从锁定的方法。
可重入锁的概念
可重入锁是指,线程可对同一把锁进行重复加锁,而不会被阻塞住,这样可避免死锁的产生。
ReentrantLock就是可重入锁
实现——关键是将解锁放到finally语句中,防止发生错误时,产生死锁。
public class TestLock {
public static void main(String[] args) {
TestLock2 lock1 = new TestLock2();
// TestLock2 lock2 = new TestLock2();
// TestLock2 lock3 = new TestLock2();
//多个线程需要操作同一个对象
new Thread(lock1).start();
new Thread(lock1).start();
new Thread(lock1).start();
}
}
class TestLock2 implements Runnable{
int ticketNumber = 10;
//定义lock锁
private final ReentrantLock lock = new ReentrantLock();
public void run() {
while (true){
try {
lock.lock();//加锁
if (ticketNumber>0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("获得票:"+(ticketNumber--));
}else {
break;
}
}catch (Exception e){
e.printStackTrace();
}finally {
lock.unlock();//释放锁
}
}
}
}
4.synchronized与lock的区别
- lock是显式的锁(手动释放),synchronized是隐式的锁(出了作用域自动释放)
- lock只能锁代码块,synchronized可以锁定方法
- 使用Lock锁,jvm将花费更少的时间,性能更好,并且具有更好的拓展性(ReentrantLock可重入锁)
- 使用顺序Lock>同步代码块>同步方法