在JDK5中增加了Lock锁接口,有ReentrantLock实现类,ReentrantLock锁称为可重入锁,它功能比synchronized多。
5.1 锁的可重入性
当一个线程获得一个对象锁后,再次请求该对象锁时可以获得该对象的锁。
5.2 ReentrantLock
5.2.1 基本使用
public class Test01 {
/**
* 定义显示锁
*/
static Lock lock = new ReentrantLock();
/**
* 定义方法
*/
public static void sm(){
//先获得锁
lock.lock();
try{
int num = 100;
for (int i = 0; i < num; i++){
System.out.println(Thread.currentThread().getName() + " -- " + i);
}
} finally {
// 释放锁
lock.unlock();
}
}
public static void main(String[] args) {
Runnable runnable = () -> sm();
// 启动三个线程
new Thread(runnable).start();
new Thread(runnable).start();
new Thread(runnable).start();
}
}
5.2.2 lockInterruptibly()
如果当前线程未被中断则获得锁,如果当前线程被中断则出现异常。
public class Test02 {
static class Service{
private Lock lock = new ReentrantLock();
public void serviceMethod() {
// 被锁定后,即使调用线程的interrupt()方法也没有真正中断
// lock.lock();
try {
lock.lockInterruptibly();
try {
System.out.println(Thread.currentThread().getName() + " -- begin lock");
for (int i = 0; i < Integer.MAX_VALUE; i++){
new StringBuilder();
}
System.out.println(Thread.currentThread().getName() + " -- end lock");
}finally {
lock.unlock(); // if lock.isHeldByCurrentThread
System.out.println(Thread.currentThread().getName() + " ** 释放锁");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Service s = new Service();
Runnable r = s::serviceMethod;
Thread t1 = new Thread(r);
t1.start();
Thread.sleep(50);
Thread t2 = new Thread(r);
t2.start();
Thread.sleep(50);
t2.interrupt();
}
}
对于synchronized内部锁来说,如果一个线程在等待锁,只有两个结果,要么该线程获得锁继续执行,要么就保持等待。
对于ReentrantLock可重入锁来说,提供另外一种可能,在等待锁的过程中,程序可以根据需要取消对锁的请求。
5.2.3 tryLock()方法
tryLock(long time, TimeUnit unit)的作用在给定等待时长内锁没有被另外的线程持有,并且当前线程也没有被中断,则获得该锁,通过该方法可以实现锁对象的限时等待。
tryLock()仅在调用时锁定未被其他线程持有的锁。
5.2.4 newCondition()方法
关键字synchronized与wait()/notify()这两个方法一起使用可以实现等待/通知模式。Lock锁的newCondition()方法返回Condition对象,Condition类也可以实现等待/通知模式。
使用nofify()通知时,JVM会随机唤醒某个等待的线程,使用Condition类可以进行选择性通知。Condition比较常用的两个方法:
- await()会使当前线程等待,同时会释放锁,当其他线程调用signal()时,线程会重新获得锁并继续执行。
- signal()用于唤醒一个等待的线程。
注意:在调用Condition的await()/signal()方法前,也需要线程持有相关的Lock锁,调用await()后线程会释放这个锁,在singal()调用后会从当前Condition对象的等待队列中唤醒一个线程,唤醒的线程尝试获得锁,一旦获得锁成功就继续执行。
public class Test01 {
static Lock lock = new ReentrantLock();
static Condition condition = lock.newCondition();
static class SubThread extends Thread {
@Override
public void run() {
lock.lock();
try {
System.out.println("method lock");
condition.await();
System.out.println("method await");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
System.out.println("method unlock");
}
}
}
public static void main(String[] args) {
SubThread t = new SubThread();
t.start();
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
// 主线程想要唤醒子线程,需要先持有锁,否则会出现异常
lock.lock();
try {
condition.signal();
} finally {
lock.unlock();
}
}
}
5.2.5 公平锁与非公平锁
大多数情况下,锁的申请都是非公平的,如果线程1与线程2都在请求锁A,当锁A可用时,系统只是会从阻塞队列中随机地选择一个线程,不能保证其公平性。
公平的锁会按照时间先后顺序,保证先到先得,公平锁这一特点不会出现线程饥饿现象。
synchronized内部锁就是非公平的,ReentrantLock重入锁提供了一个构造方法:ReentrantLock(boolean fair),当在创建锁对象时,实参传递true可以把该锁设置为公平锁。
线程接连获得/释放锁, 如果是非公平锁,系统倾向于让一个线程再次获得已经持有的锁,这种分配策略是高效的、非公平的; 如果是公平锁,多个线程不会发生同一个线程连续多次获得锁的可能,保证了公平性。
要实现公平锁,必须要求系统维护一个有序队列,公平锁的实现成本较高,性能低,因此默认情况下锁是非公平的。
5.2.6 其他常用方法
- int getHoldCount():返回当前线程调用lock()方法的次数;
- int getQueueLenghth():返回等待获得锁的线程预估数;
- int getWaitQueueLength(Condition condition):返回与Condition条件相关的等待线程预估数;
- boolean hasQueuedThread(Thread thread):查询参数指定的线程是否在等待获得锁;
- boolean hasQueuedThread():查询是否还有线程在等待获得该锁;
- boolean hasWaiters(Condition condition):查询是否有线程正在等待指定的Condition条件;
- boolean isFair():判断是否为公平锁;
- boolean isHeldByCurrentThread():判断当前线程是否持有该锁;
- boolean isLocked():查询当前锁是否被线程持有。
5.3 ReentrantReadWriteLock读写锁
synchronized内部锁与ReentrantLock锁都是独占锁(排他锁),同一时间只允许一个线程执行同步代码块,可以保证线程的安全性,但是执行效率低。
ReentrantReadWriteLock读写锁是一种改进的排他锁,也可以称为共享/排他锁,允许多个线程同时读取共享数据,但是一次只允许一个线程对共享数据进行更新。
读写锁通过读锁与写锁来完成读写操作,线程在读取共享数据前,必须先持有读锁,该读锁可以同时被多个线程持有,即它是共享的;线程在修改共享数据前必须持有写锁,写锁是排他的。一个线程持有写锁时,其他线程无法获得相应的锁。
在java.util.concurrent.locks包中定义了ReadWriteLock接口,该接口中定义了readLock()返回读锁,定义writeLock()方法返回写锁,该接口的实现类是ReentrantReadWriteLock。readLock()与writeLock()方法返回的锁对象是同一个锁的两个不同的角色,不是分别获得两个不同的锁。
5.3.1 读读共享
public class Test01 {
static class Service{
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* 输出:
* Thread-0获得读锁,开始读取数据的时间--1614927014969
* Thread-3获得读锁,开始读取数据的时间--1614927014970
* Thread-4获得读锁,开始读取数据的时间--1614927014970
* Thread-2获得读锁,开始读取数据的时间--1614927014970
* Thread-1获得读锁,开始读取数据的时间--1614927014970
*/
public void read(){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()
+ "获得读锁,开始读取数据的时间--" + System.currentTimeMillis());
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
readWriteLock.readLock().unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
int threadNum = 5;
for (int i = 0; i < threadNum; i++){
new Thread(service::read).start();
}
}
}
5.3.2 写写互斥
public class Test02 {
static class Service{
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
/**
* Thread-0获得写锁,开始修改数据的时间--1614927264280
* Thread-0修改数据结束的时间--1614927266283
* Thread-2获得写锁,开始修改数据的时间--1614927266283
* Thread-2修改数据结束的时间--1614927268284
* Thread-1获得写锁,开始修改数据的时间--1614927268284
* Thread-1修改数据结束的时间--1614927270284
* Thread-4获得写锁,开始修改数据的时间--1614927270284
* Thread-4修改数据结束的时间--1614927272285
* Thread-3获得写锁,开始修改数据的时间--1614927272285
* Thread-3修改数据结束的时间--1614927274285
*/
public void write(){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()
+ "获得写锁,开始修改数据的时间--" + System.currentTimeMillis());
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName()
+ "修改数据结束的时间--" + System.currentTimeMillis());
readWriteLock.writeLock().unlock();
}
}
}
public static void main(String[] args) {
Service service = new Service();
int threadNum = 5;
for (int i = 0; i < threadNum; i++){
new Thread(service::write).start();
}
}
}
5.3.3 读写互斥
public class Test03 {
static class Service{
ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public void write(){
readWriteLock.writeLock().lock();
try {
System.out.println(Thread.currentThread().getName()
+ "获得写锁,开始修改数据的时间--" + System.currentTimeMillis());
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName()
+ "修改数据结束的时间--" + System.currentTimeMillis());
readWriteLock.writeLock().unlock();
}
}
public void read(){
readWriteLock.readLock().lock();
try {
System.out.println(Thread.currentThread().getName()
+ "获得读锁,开始读取数据的时间--" + System.currentTimeMillis());
TimeUnit.SECONDS.sleep(2);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName()
+ "读取数据结果的时间--" + System.currentTimeMillis());
readWriteLock.readLock().unlock();
}
}
}
/**
* Thread-0获得读锁,开始读取数据的时间--1614927579907
* Thread-0读取数据结果的时间--1614927581910
* Thread-1获得写锁,开始修改数据的时间--1614927581910
* Thread-1修改数据结束的时间--1614927583910
*/
public static void main(String[] args) {
Service service = new Service();
new Thread(service::read).start();
new Thread(service::write).start();
}
}