以下是摘自JAVA API文档关于lock和synchronized的一段话:
Lock
实现提供了比使用synchronized
方法和语句可获得的更广泛的锁定操作。此实现允许更灵活的结构,可以具有差别很大的属性,可以支持多个相关Condition对象。synchronized
方法或语句的使用提供了对与每个对象相关的隐式监视器锁的访问,但却强制所有锁获取和释放均要出现在一个块结构中:当获取了多个锁时,它们必须以相反的顺序释放,且必须在与所有锁被获取时相同的词法范围内释放所有锁。- Lock 实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 (tryLock())、一个获取可中断锁的尝试 (lockInterruptibly()) 和一个获取超时失效锁的尝试 (tryLock(long, TimeUnit))。Lock 类还可以提供与隐式监视器锁完全不同的行为和语义,如保证排序、非重入用法或死锁检测。如果某个实现提供了这样特殊的语义,则该实现必须对这些语义加以记录。
先看一下Lock接口的定义的方法:
- void lock() 获取锁。
- void lockInterruptibly() 如果当前线程未被中断,则获取锁
- Condition newCondition() 返回绑定到此 Lock 实例的新 Condition 实例
- boolean tryLock() 仅在调用时锁为空闲状态才获取该锁
- boolean tryLock(long time, TimeUnit unit) 如果锁在给定的等待时间内空闲,并且当前线程未被中断,则获取锁
- void unlock() 释放锁
注意:synchronized是在JVM层面上实现的,不但可以通过一些监控工具监控synchronized的锁定,而且在代码执行时出现异常,JVM会自动释放锁定,但是使用Lock则不行,lock是通过代码实现的,要保证锁定一定会被释放,就必须将unLock()放到finally{}中
Lock接口的实现类:ReentrantLock, ReentrantReadWriteLock.ReadLock, ReentrantReadWriteLock.WriteLock 。其中ReadLock和WriteLock是ReentrantReadWriteLock的内部类,ReentrantReadWriteLock是ReadWriteLock的实现类。关于ReadWriteLock后续再做详解。
ReentrantLock
一个可重入的互斥锁,它拥有与 synchronized 相同的并发性和内存语义,但是添加了类似轮询锁、定时锁等候和可中断锁等候的一些特性。构造方法:
- ReentrantLock() 创建一个 ReentrantLock 的实例
- ReentrantLock(boolean fair) 创建一个具有给定公平策略的 ReentrantLock----公平锁
ReentrantLock特有的方法:
- int getHoldCount() 查询当前线程保持此锁的次数。
- int getQueueLength() 返回正等待获取此锁的线程估计数
- int getWaitQueueLength(Condition condition) 返回等待与此锁相关的给定条件的线程估计数
- boolean hasQueuedThread(Thread thread) 查询给定线程是否正在等待获取此锁
- boolean hasQueuedThreads() 查询是否有些线程正在等待获取此锁
- boolean hasWaiters(Condition condition) 查询是否有些线程正在等待与此锁有关的给定条件
- boolean isFair() 如果此锁的公平设置为 true,则返回 true
- boolean isHeldByCurrentThread() 查询当前线程是否保持此锁
- boolean isLocked() 查询此锁是否由任意线程保持
如下实现两个线程轮询处理公共数据:
public class ReenLockTest {
public static void main(String[] args) {
new Thread(new Runnable(){
@Override
public void run() {
Thread1.m1();
}
},"thread1").start();
new Thread(new Runnable(){
@Override
public void run() {
Thread1.m2();
}
},"thread2").start();
}
static ReentrantLock lock = new ReentrantLock();
static ReentrantLock lock2 = new ReentrantLock();
static int i = 100;
static class Thread1{
public static void m1(){
while(i>0){
lock.lock();
try {
System.out.println("method m1()" + i--);
} catch (Exception e) {
e.printStackTrace();
}finally{
if(!lock2.isLocked())
lock2.unlock();
}
}
}
public static void m2(){
while(i>0){
lock2.lock();
try {
System.out.println("method m2()" + i--);
} catch (Exception e) {
e.printStackTrace();
}finally{
if(!lock.isLocked())
lock.unlock();
}
}
}
}
}
重入锁:
ReentrantLock锁,可以被单个线程多次获取:
ReentrantLock部分方法介绍:
1、tryLock():
- boolean tryLock() 尝试获取锁,仅在调用时锁为空闲状态才获取该锁。
- boolean tryLock(long timeout, TimeUnit unit) 如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
如果该锁没有被另一个线程保持,并且立即返回 true
值,则将锁的保持计数设置为 1。即使已将此锁设置为使用公平排序策略,但是调用 tryLock()
仍将 立即获取锁(如果有可用的),而不管其他线程当前是否正在等待该锁。
- 如果当前线程已经保持此锁,则将保持计数加 1,该方法将返回
true
。 - 如果锁被另一个线程保持,则此方法将立即返回
false
值。
tryLock()的用法与lock()的使用方式基本相同,只是在释放锁时,tryLock()方式获取的锁需要先验证当前线程是否获取到锁isLocked()
static class Thread1{
public static void m1(){
while(i>0){
boolean tryLock = lock.tryLock();
if(tryLock){
try {
Thread.sleep(500);
System.out.println("method m1()" + i--);
} catch (Exception e) {
e.printStackTrace();
}finally{
if(lock.isLocked())
lock.unlock();
}
}
}
}
2、lockInterruptibly()
- void lockInterruptibly() 如果当前线程未被中断,则获取锁
- 如果该锁没有被另一个线程保持,则获取该锁并立即返回,将锁的保持计数设置为 1。
- 如果当前线程已经保持此锁,则将保持计数加 1,并且该方法立即返回。
- 如果锁被另一个线程保持,则出于线程调度目的,禁用当前线程,并且在发生以下两种情况之一以前,该线程将一直处于休眠状态:
锁由当前线程获得;
其他某个线程中断当前线程。 - 如果当前线程获得该锁,则将锁保持计数设置为 1。
- 如果当前线程在进入此方法时已经设置了该线程的中断状态;或者 在等待获取锁的同时被中断。 则抛出 InterruptedException,并且清除当前线程的已中断状态。
lockInterruptibly() 与lock() 的区别:
- lock 优先考虑获取锁,待获取锁成功后,才响应中断。使用lock()方法获取锁标记,如果需要阻塞等待锁标记,不能被打断
- lockInterruptibly 优先考虑响应中断,而不是响应锁的普通获取或重入获取。使用lockInterribly()方法获取锁标记,如果需要阻塞等待锁标记,可以被打断。
public class TryLockTest {
static Lock lock = new ReentrantLock();
static Lock lock2 = new ReentrantLock();
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable(){
@Override
public void run() {
m1();
}
});
Thread t2 = new Thread(new Runnable(){
@Override
public void run() {
m2();
}
});
t1.start();
t2.start();
t1.interrupt();
t2.interrupt();
}
public static void m1(){
lock.lock();
for(int i=0 ;i<20 ; i++){
try {
Thread.sleep(500);
} catch (InterruptedException e) {
System.out.println("m1() java.lang.InterruptedException");
}
System.out.println("m1 *running");
}
lock.unlock();
}
public static void m2(){
try {
lock2.lockInterruptibly();
for(int i=0 ;i<20 ; i++){
Thread.sleep(500);
System.out.println("m2 **running");
}
} catch (InterruptedException e) {
System.out.println("m2() java.lang.InterruptedException");
}finally{
if(((ReentrantLock) lock2).isLocked())
lock2.unlock();
}
}
}
输出结果:
从输出结果中可以看到,m1()执行,而m2()被打断。
注意:
- interrupt()方法打断线程休眠状态,会抛出异常 InterruptedException。
- m1()方法的被打断一次,是因为在调用sleep()方法进入休眠状态时被打断一次。
关于线程中断请看这篇博客:Java并发之线程中断