在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.isHeldByCurrentThreadSystem.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 {@Overridepublic 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();}}
