1、官方文档
public interface Lock
Lock
实现提供比使用synchronized
方法和语句可以获得的更广泛的锁定操作。 它们允许更灵活的结构化,可能具有完全不同的属性,并且可以支持多个相关联的对象Condition
锁是用于通过多个线程控制对共享资源的访问的工具。 通常,锁提供对共享资源的独占访问:一次只能有一个线程可以获取锁,并且对共享资源的所有访问都要求首先获取锁。 但是,某些锁是可以允许对于一个共享资源的并发访问,如ReadWriteLock
的读锁。
(这里稍加说明一下,对于实际中可能用这么一个场景:大部分都是读操作,只有小部分是写操作,而如果几个线程都是对一个资源进行读取,那很明显是不需要上锁的,而有些情况又需要写则肯定得上锁,此时读写锁ReadWriteLock就有它的适用场景了,也是并发包中的,瞅一眼:)
使用synchronized方法或块的使用提供了与每个对象关联的隐式监视器的访问,但是它强制所有锁的获取和释放必须以一个块结构的方式发生:当多个锁被获取时,他们必须以一个相反的顺序得到释放,而且所有锁必须在他们所获取相同的范围当做进行释放
虽然synchronized方法与语句的范围机制使得带监视器锁的程序变得更加简单,并且有助于避免很多与锁相关的编码上的错误。但是!!!有一种场景你需要以一种更加灵活的方式来使用锁。
比如:一些用于并发遍历访问数据结构的算法需要使用hand-over-hand(双手交互使用)或者链式锁:你需要获取A结点的锁,然后再获取B结点的锁,然后再释放A结点的锁,并且再获取C结点的锁,再释放B结点的锁,再获取D结点的锁等等(注意:这跟通常的不太一样,一般使用锁可能是先获取A,再获取B,接着释放B,释放A),Lock接口的具体实现使用这种技术(允许获取锁和释放锁在不同的范围以及允许多个锁以任意一种顺序来获取和回释放),清楚描述了synchronized关键字和Lock的区别
借助于这种增加的灵活性则产生了一些额外的职责。此时就会缺少发生在synchronized方法和块结构当做的块结构会自动的释放锁的机制了(也就是使用Lock则需要我们手动来释放锁),在大多情况下,下面的语句会被使用
Lock l = ...;
l.lock();
try{
//access the resource protected by this lock
}finally {
l.unlock();
}
当上锁和解锁发生在不同范围中时,就得非常小心地确保当锁被持有执行的所有代码的通过try - finally 或 try - catch 所保护来确保锁在必要的时候得到释放
Lock
实现提供了使用synchronized
方法和语句的附加功能,通过提供非阻塞尝试来获取锁(tryLock()
),尝试获取可被中断的锁( lockInterruptibly()
),以及尝试获取可以超时( tryLock(long, TimeUnit)
)。
Lock
类还可以提供与隐式监视锁完全不同的行为和语义,比如保证顺序,非可重入的使用或者死锁检测。如果实现提供特殊的语句,那么该实现就必须以文档的形式呈现出来
注意:Lock实例仅仅就是一个普通的对象,所以它自身也可以用作synchronized语句的目标。获取一个Lock的监视器锁的实例跟调用Lock实例的lock()方法是没有任何关系的。避免这种混淆的推荐做法是你永远也不要使用以这种方式创建Lock实例。(将Lock实例作为synchronized锁的目标对象的话,在jvm底层获取到的是对应Lock实例的monitor lock监视器锁,这个监视器锁与Lock实例的lock方法之间没有任何关系)
所有Lock实施必须执行与内置监视器锁相同的内存同步语义
- 成功的
lock
操作具有与成功锁定动作相同的内存同步效果。 - 成功的
unlock
操作具有与成功解锁动作相同的内存同步效果。
不成功的锁定和解锁操作以及重入锁定/解锁操作,不需要任何内存同步效果。
2、Lock接口的方法
1、void lock()
获取锁,如果lock
获取不到,当前线程则无法进行线程调度,并且一直等待(处于休眠状态)直到lock
能被获取
Lock l = new ReentrantLock();
l.lock();
try{
//access the resource protected by this lock
}finally {
l.unlock();
}
2、void lockInterruptibly()
当通过这个方法获取锁时,如果当前线程获取不到锁,则会进入休眠的状态,直到两件事情发生就会唤醒
①:这个锁被当前线程获取到了
②:其他线程中断当前线程的等待状态(当前线程能够响应中断)
注意:当一个线程获取了锁之后,是不会被interrupt()
方法中断的。因为单独调用interrupt()
方法不能中断正在运行过程中的线程,只能中断阻塞过程中的线程。因此当通过lockInterruptibly()
方法获取某个锁时,如果不能获取到,只能进行等待的情况下,是可以响应中断的。而用synchronized
修饰的话,当一个线程处于等待某个锁的状态,是无法被中断的,只有一直等待下去。
public void method() throws InterruptedException {
Lock l = new ReentrantLock();
l.lockInterruptibly();
try{
//access the resource protected by this lock
}finally {
l.unlock();
}
}
3、boolean tryLock()
在调用的时候该锁空闲则可以获取锁。如果可以获取到锁,则获取锁并立马返回true;如果不可以获取锁,则立马返回false。这种用法就确保了获取到了锁才去解锁,而如果没有获取到锁那就没必要进行解锁了
Lock l = new ReentrantLock();
if(l.tryLock()) {
try{
//access the resource protected by this lock
}finally {
l.unlock();
}
}else {
// perform alternative actions
}
4、boolean tryLock(long time, TimeUnit unit)
如果可以获取到锁,则获取锁并立马返回true;
如果不可以获取到锁,则当前线程会进行阻塞直到下面3件事情发生
①:正常获取到锁
②:当前线程被其他线程打断了,并且锁获取的中断是被支持的
③:指定等待的时间到了
5、void unlock()
释放锁
6、Condition newCondition()
返回一个绑定了这个Lock实例上的一个新的Condition实例,在等待条件之前该锁必须要被当前线程所持有,在等待或重新获取之前Condition.await()方法再它返回之前会自动释放锁
3、Lock的例子
Lock与synchronized关健字在锁的处理上的重要差别进行梳理一下:
1、锁的获取方法:前者是通过程序代码的方式由开发者手工获取,后者是通过JVM来获取(无需开发者干预)。
2、具体实现方法:前者是通过Java代码的方式来实现,后者是通过JVM底层来实现(无需开发者关注)。
3、锁的释放方法:前者务必通过unlock()方法在finally块中手工释放,后者是通过JVM来释放(无需开发者关注)。
4、锁的具体类型:前者提供了多种,如公平锁、非公平锁,后者与前者均提供了可重入锁。其中公平锁和非公平锁在ReentrantLock源码中看到身影:
例子1
package com.concurrency2;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyTest1 {
private Lock lock = new ReentrantLock();
public void myMethod1() {
try {
lock.lock();
System.out.println("myMethod1 invoke");
} finally {
//lock.unlock();
}
}
public void myMethod2() {
try {
lock.lock();
System.out.println("myMethod2 invoke");
} finally {
lock.unlock();
}
}
public static void main(String[] args) {
MyTest1 myTest1 = new MyTest1();
Thread t1 = new Thread(() -> {
for(int i = 0;i < 10;i ++) {
myTest1.myMethod1();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
for(int i = 0;i < 10;i ++) {
myTest1.myMethod2();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}
条件:注释掉了myMethod1的lock.unlock();
结果:那当线程执行了myMethod1之后抢到锁就永远不会释放了,那myMethod2就永远获取不到了,则输出就可以预想了
输出,且不会停止
myMethod1 invoke
myMethod1 invoke
myMethod1 invoke
myMethod1 invoke
myMethod1 invoke
myMethod1 invoke
myMethod1 invoke
myMethod1 invoke
myMethod1 invoke
myMethod1 invoke
例子2
package com.concurrency2;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class MyTest1 {
private Lock lock = new ReentrantLock();
public void myMethod1() {
try {
lock.lock();
System.out.println("myMethod1 invoke");
} finally {
//lock.unlock();
}
}
public void myMethod2() {
/* try {
lock.lock();
System.out.println("myMethod2 invoke");
} finally {
lock.unlock();
}*/
boolean result = false;
try {
result = lock.tryLock(800, TimeUnit.MILLISECONDS);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(result) {
System.out.println("get the lock");
} else {
System.out.println("can't get the lock");
}
}
public static void main(String[] args) {
MyTest1 myTest1 = new MyTest1();
Thread t1 = new Thread(() -> {
for(int i = 0;i < 10;i ++) {
myTest1.myMethod1();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
Thread t2 = new Thread(() -> {
for(int i = 0;i < 10;i ++) {
myTest1.myMethod2();
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
t1.start();
t2.start();
}
}
条件:将method2改成了tryLock的形式
结果:当method1一直拿着lock锁时,method2会因为拿不到锁而输出can't get the lock
,继续执行下去
输出
myMethod1 invoke
can't get the lock
myMethod1 invoke
can't get the lock
myMethod1 invoke
myMethod1 invoke
can't get the lock
myMethod1 invoke
can't get the lock
myMethod1 invoke
myMethod1 invoke
can't get the lock
myMethod1 invoke
can't get the lock
myMethod1 invoke
can't get the lock
myMethod1 invoke
can't get the lock
can't get the lock
can't get the loc