java多线程-ReentrantLock重入锁的使用及常用API

2,403 阅读10分钟

1 ReentrantLock重入锁

和关键字synchronization相比,重入锁有着显示的操作过程。需要手动指定何时加锁,何时释放锁。重入锁对逻辑控制的灵活性要远远优于关键字synchronization。

在退出时,必须记得释放锁,否则其他线程就没有机会访问。

重入锁之所以叫重入锁,是因为这种锁能反复进入。但是只限于一个线程。

//锁定几个就要释放几个
try{
	lock.lock();	
	lock.lock()
}finally{
    lock.unlock();
    lock.unlock();
}

使用ReentrantLock对象的lock()方法获取锁,使用unlock()方法释放锁。

public class MyLock extends Thread{

    private Lock lock = new ReentrantLock();

    @Override
    public void run(){
        lock.lock();
        for (int i = 0;i<5;i++){
            System.out.println("ThreadName = "+Thread.currentThread().getName() + " " + i);
        }
        lock.unlock();
    }

    public static void main(String[] args) {
        MyLock myLock = new MyLock();
        Thread r = new Thread(myLock);
        Thread r1 = new Thread(myLock);
        Thread r2 = new Thread(myLock);
        r.start();r1.start();r2.start();
    }

}

从结果来看,当前线程执行完毕后对锁进行释放,其他线程才可以继续打印。当调用lock.lock()方法后,该线程就持有了“对象监视器”,其他线程只有等待锁被释放时再次争抢。

ReentrantLock的几个重要方法:

  • lock():获得锁,如果锁被占用则等待。
  • lockInterruptibly():获得锁,但优先响应中断。
  • tryLock():尝试获得锁,如果成功,则返回true;失败返回false。此方法不等待,立即返回。
  • unlock():释放锁。

1.1 公平锁和非公平锁

锁Lock分为公平锁和非公平锁,公平锁表示线程获取锁的顺序是按照线程加锁的顺序来分配的,即先来先得先进先出的顺序。而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,和公平锁不一样的就是先来的不一定先得到锁,这会造成某些线程一直拿不到锁,出现饥饿现象。

公平锁虽好,但是实现公平锁需要系统维护一个有序队列,因此公平锁的实现成本比较高,性能低下。

synchronization关键字产生得锁是非公平锁。

通过ReentrantLock的构造函数可以指定公平锁还是非公平锁。

默认情况下ReentrantLock使用的是非公平锁。

public class FairLock extends Thread{

    private ReentrantLock lock;

    public FairLock(boolean isFair){
        lock = new ReentrantLock(isFair);
    }

    @Override
    public void run(){
        try{
            lock.lock();
            System.out.println("⭐线程 "+ Thread.currentThread().getName() + "运行");
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        FairLock fairLock = new FairLock(true);
        Thread[] threads = new Thread[10];
        for (int i = 0;i< 10;i++){
            threads[i] = new Thread(fairLock);
        }
        Arrays.stream(threads).forEach(thread -> thread.start());
    }

}

运行结果

⭐线程 Thread-1运行
⭐线程 Thread-4运行
⭐线程 Thread-3运行
⭐线程 Thread-2运行
⭐线程 Thread-5运行
⭐线程 Thread-6运行
⭐线程 Thread-7运行
⭐线程 Thread-8运行
⭐线程 Thread-9运行
⭐线程 Thread-10运行

打印的结果基本上是有序的,这就是公平锁。如果将参数改为false,那么打印结果基本上是乱序的。

⭐线程 Thread-1运行
⭐线程 Thread-4运行
⭐线程 Thread-2运行
⭐线程 Thread-5运行
⭐线程 Thread-3运行
⭐线程 Thread-10运行
⭐线程 Thread-8运行
⭐线程 Thread-7运行
⭐线程 Thread-9运行
⭐线程 Thread-6运行

1.2 getHoldCount()方法

getHoldCount()方法的作用是查询当前线程保持此锁定的个数,也就是调用lock()方法的次数。

public class MyGetHoldCount {

    private ReentrantLock lock = new ReentrantLock();
    public void serviceLock1(){
        try {
            lock.lock();
            System.out.println("serviceLock1 :" + lock.getHoldCount());
            this.serviceLock2();
        }finally {
            lock.unlock();
        }
    }

    public void serviceLock2(){
        try {
            lock.lock();
            System.out.println("serviceLock2 :" + lock.getHoldCount());
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        new MyGetHoldCount().serviceLock1();
    }
}

打印输出

serviceLock1 :1
serviceLock2 :2

1.3 getQueueLength()方法

getQueueLength()方法的作用是返回正等待获取此锁定的线程数。

public class MyQueueLen {
    public ReentrantLock lock = new ReentrantLock();

    public void serviceMe(){
        try {
            lock.lock();
            System.out.println("ThreadName = "+ Thread.currentThread().getName() + "获取锁");
            Thread.sleep(Integer.MAX_VALUE);
        }catch (InterruptedException e){
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyQueueLen myQueueLen = new MyQueueLen();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                myQueueLen.serviceMe();
            }
        };
        Thread[] threads = new Thread[10];
        for (int i = 0;i<10;i++){
            threads[i] = new Thread(runnable);
        }
        Arrays.stream(threads).forEach(thread -> thread.start());
        Thread.sleep(1000);
        System.out.println("有" + myQueueLen.lock.getQueueLength() + "个线程在等待锁");
    }
}

打印输出

ThreadName = Thread-0获取锁
有9在等待锁

1.4 hasQueueThread()方法

boolean hasQueueThread(Thread thread)的作用是查询指定线程是否正在等待获取此锁定。

boolean hasQueueThreads()的作用是查询是否有线程正在等待获取此锁定。

public class MyHasQTh {

    public ReentrantLock lock = new ReentrantLock();

    public void serviceMeth(){
        try{
            lock.lock();
            Thread.sleep(Integer.MAX_VALUE);
        } catch (InterruptedException e) {
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyHasQTh myHasQTh = new MyHasQTh();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                myHasQTh.serviceMeth();
            }
        };
        Thread threadA = new Thread(runnable);
        threadA.start();
        Thread.sleep(1000);
        Thread threadB = new Thread(runnable);
        threadB.start();
        Thread.sleep(500);
        System.out.println(myHasQTh.lock.hasQueuedThread(threadA));
        System.out.println(myHasQTh.lock.hasQueuedThread(threadB));
        System.out.println(myHasQTh.lock.hasQueuedThreads());

    }
}

打印结果

false
true
true

1.5 isFai()方法

isFair()方法的作用是判断是不是公平锁

public class IsFair {
    private ReentrantLock lock;
    public IsFair(boolean fair){
        lock = new ReentrantLock(fair);
    }
    public void serviceMeth(){
        try {
            lock.lock();
            System.out.println("是否是公平锁:" + lock.isFair());
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        IsFair isFair = new IsFair(true);
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                isFair.serviceMeth();
            }
        };
        new Thread(runnable).start();
    }
}

打印结果

是否是公平锁:true

1.6 isHeldByCurrentThread()方法

boolean isHeldByCurrentThread()方法的作用是查询当前线程是否保持此锁定。

public class MyHeldThread {

    private ReentrantLock lock;

    public MyHeldThread(boolean fair){
        lock = new ReentrantLock(fair);
    }

    public void serviceMethod(){
        try{
            System.out.println(lock.isHeldByCurrentThread());
            lock.lock();
            System.out.println(lock.isHeldByCurrentThread());
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        MyHeldThread myHeldThread = new MyHeldThread(true);
        new Thread(new Runnable() {
            @Override
            public void run() {
                myHeldThread.serviceMethod();
            }
        }).start();
    }
}

打印结果

false
true

1.7 isLocked()方法

Boolean isLocked()方法的作用是查询此锁定是否由任意线程保持。

public class MyIsLocked {

    public ReentrantLock lock = new ReentrantLock();

    public void serviceMethod(){
        try{
            System.out.println(lock.isLocked());
            lock.lock();
            System.out.println(lock.isLocked());
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        MyIsLocked myIsLocked = new MyIsLocked();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myIsLocked.serviceMethod();
            }
        }).start();
    }
}

打印结果

false
true

1.8 tryLock()方法

boolean tryLock()方法的作用是仅在调用时锁定未被另一个线程保持的情况下返回true,若锁定以被保持则返回false。并且是立即执行,不会进行等待。

tryLock 是防止自锁的一个重要方式。

public class MyTryLock {

    public ReentrantLock lock = new ReentrantLock();

    public void serviceMethod(){
        try {
            if (lock.tryLock()) {
                System.out.println(Thread.currentThread().getName() + "获取锁定 "+System.currentTimeMillis());
                Thread.sleep(4000);
            } else {
                System.out.println(Thread.currentThread().getName() + "未获取锁定");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        MyTryLock myTryLock = new MyTryLock();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                myTryLock.serviceMethod();
            }
        };
        Thread aa = new Thread(runnable, "aa");
        aa.start();
        Thread bb = new Thread(runnable, "bb");
        bb.start();
    }
}

打印结果:

aa未获取锁定
bb获取锁定 1560237476296

boolean tryLock(long timeout,TimeUnit unit)的作用是如果锁定在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁定。

会在指定时间内等待获取锁。

public class MyTryLock {

    public ReentrantLock lock = new ReentrantLock();

    public void serviceMethod(){
        try {
            if (lock.tryLock(3,TimeUnit.SECONDS)) {
                System.out.println(Thread.currentThread().getName() + "获取锁定 "+System.currentTimeMillis());
                Thread.sleep(2000);
            } else {
                System.out.println(Thread.currentThread().getName() + "未获取锁定");
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) {
        MyTryLock myTryLock = new MyTryLock();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                System.out.println(Thread.currentThread().getName() + " " + System.currentTimeMillis());
                myTryLock.serviceMethod();
            }
        };
        Thread aa = new Thread(runnable, "aa");
        aa.start();
        Thread bb = new Thread(runnable, "bb");
        bb.start();
    }
}

打印结果:

aa 1560237873563
bb 1560237873563
bb获取锁定 1560237873563
aa获取锁定 1560237875579

若将代码中Thread.sleep()的时间改为超过3秒,则会打印:

aa 1560237942950
bb 1560237942950
bb获取锁定 1560237942950
aa未获取锁定

1.9 getWaitQueueLength()方法

int getWaitQueueLength(Condition con)方法的作用是返回等待与此锁定相关的给定Condition的线程数。就是有多少个指定的Condition实例在等待此锁定。

public class MyWaitQuere {

    public ReentrantLock lock = new ReentrantLock();
    public Condition con = lock.newCondition();

    public void waitMethod(){
        try{
            lock.lock();
            con.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signalMethod(){
        try{
            lock.lock();
            System.out.println("有 " + lock.getWaitQueueLength(con) + "个线程正在等待con");
            con.signal();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyWaitQuere myWaitQuere = new MyWaitQuere();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                myWaitQuere.waitMethod();
            }
        };
        Thread[] threads = new Thread[10];
        for(int i = 0;i< 10;i++){
            threads[i] = new Thread(runnable);
        }
        Arrays.stream(threads).forEach(thread -> thread.start());
        Thread.sleep(2000);
        myWaitQuere.signalMethod();
    }
}

打印结果

有 10个线程正在等待con

1.10 int hasWaiters(Condition con)

int hasWaiters(Condition con)的作用是查询是否有线程正在等待与此锁定有关的Condition条件。

public class MyHasWaiters {

    public ReentrantLock lock = new ReentrantLock();
    public Condition con = lock.newCondition();
    public void myWait(){
        try{
            lock.lock();
            con.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }
    public void hasWaits(){
        try{
            lock.lock();
            System.out.println("是否有线程在等待con : " + lock.hasWaiters(con));
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyHasWaiters myHasWaiters = new MyHasWaiters();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myHasWaiters.myWait();
            }
        }).start();
        Thread.sleep(2000);
        myHasWaiters.hasWaits();
    }
}

打印结果

是否有线程在等待con : true

1.11 lockInterruptibly()方法

void lockInterruptibly()方法的作用是,如果当前线程未被中断,则获取锁定,已被中断则抛异常。

public class MyLockInterr {

    public ReentrantLock lock = new ReentrantLock();
    public void waitMethod(){
        try{
            lock.lockInterruptibly();
            System.out.println("lock begin " + Thread.currentThread().getName());
            for (int i = 0;i< Integer.MAX_VALUE;i++){new String();}
            System.out.println("lock end " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyLockInterr myLockInterr = new MyLockInterr();
        Runnable runnable = new Runnable() {
            @Override
            public void run() {
                myLockInterr.waitMethod();
            }
        };
        Thread aaa = new Thread(runnable, "aaa");
        Thread bbb = new Thread(runnable, "bbb");
        aaa.start();
        Thread.sleep(500);
        bbb.start();
        bbb.interrupt();
        System.out.println("main end");
    }
}

打印结果

lock begin aaa
lock end aaa
java.lang.InterruptedException
main end
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1220)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.wtj.lock.MyLockInterr.waitMethod(MyLockInterr.java:16)
	at com.wtj.lock.MyLockInterr$1.run(MyLockInterr.java:34)
	at java.lang.Thread.run(Thread.java:748)

若将lock.lockInterruptibly()改为lock.lock()则不会报错。

2 Condition 实现等待/通知

synchronization,wait()和notfiy()/notifyAll()方法结合可以实现等待/通知模式,类ReentrantLock也可以实现同样的功能,但需要借助Condition对象,Condition有更好的灵活性,如实现多路通知等功能,也就是在一个lock对象中可以创建多个Condition(即对象监视器)实例,线程对象可以注册在指定的Condition中,从而可以有选择的进行线程通知,在调度上更加灵活。

而synchronization相当于整个Lock对象中只有一个单一的Condition实例,所有的线程都注册在这一个实例上。

使用Condition类中的await()方法使线程进行等待,同时释放当前锁。使用signal()方法唤醒。

使用Condition实例方法前需要先保持锁定。Lock.lock()

使用多个Condition实现通知部分线程

public class MyCondition {

    private ReentrantLock lock = new ReentrantLock();
    private Condition con1 = lock.newCondition();
    private Condition con2 = lock.newCondition();

    public void await1(){
        try{
            lock.lock();
            System.out.println("await1 start : "+ System.currentTimeMillis() + " " + Thread.currentThread().getName());
            con1.await();
            System.out.println("await1 end : "+ System.currentTimeMillis() + " " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void await2(){
        try{
            lock.lock();
            System.out.println("await2 start : "+ System.currentTimeMillis() + " " + Thread.currentThread().getName());
            con2.await();
            System.out.println("await2 end : "+ System.currentTimeMillis() + " " + Thread.currentThread().getName());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public void signalAll_1(){
        try{
            lock.lock();
            System.out.println("signalAll_1 start : "+ System.currentTimeMillis() + " " + Thread.currentThread().getName());
            con1.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public void signalAll_2(){
        try{
            lock.lock();
            System.out.println("signalAll_2 start : "+ System.currentTimeMillis() + " " + Thread.currentThread().getName());
            con2.signalAll();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        MyCondition myCondition = new MyCondition();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myCondition.await1();
            }
        }, "aaa").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myCondition.await2();
            }
        },"bbb").start();
        Thread.sleep(3000);
        myCondition.signalAll_1();
    }
}

打印结果

await1 start : 1560238945661 aaa
await2 start : 1560238945661 bbb
signalAll_1 start : 1560238948666 main
await1 end : 1560238948666 aaa

从输出结果看,只有线程aaa被唤醒。

3 ReentrantReadWriteLock类

类ReentrantLock具有完全互斥排他的效果,即同一时间只有一个线程在执行lock()方法后的任务。这样虽然能保证线程安全,但是效率非常低。

JDK中提供了一种ReentrantReadWriteLock类,使用它可以加快运行效率,在某些不需要操作实例变量的方法中,可以使用读写锁ReentrantReadWriteLock类提升效率。

读写锁ReentrantReadWriteLock中有两个锁,一个是读操作相关的锁,也称共享锁;一个是写操作相关的锁,也称排他锁多个读锁之间不互斥,读锁与写锁互斥,写锁与写锁互斥

3.1 读读共享

public class MyReadLock {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();

    public void read(){
        try{
            lock.readLock().lock();     //读锁锁定
            System.out.println("获取读锁" + Thread.currentThread().getName()+ " "+ System.currentTimeMillis());
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }

    public static void main(String[] args) {
        MyReadLock myReadLock = new MyReadLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myReadLock.read();
            }
        },"aaa").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myReadLock.read();
            }
        },"bbb").start();
    }
}

打印结果

获取读锁aaa 1560241121534
获取读锁bbb 1560241121534

从结果可以看出两个线程是同时进入lock()方法后的代码,说明读锁允许多个线程同时执行lock()方法后的代码。

3.2 写写互斥

public class MyWriteLock {

    private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void write(){
        try{
            lock.writeLock().lock();        //写锁锁定
            System.out.println("获得写锁"+ Thread.currentThread().getName() + " " + System.currentTimeMillis());
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    public static void main(String[] args) {
        MyWriteLock myWriteLock = new MyWriteLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myWriteLock.write();
            }
        },"aaa").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myWriteLock.write();
            }
        },"bbb").start();
    }
}

打印结果

获得写锁aaa 1560241405832
获得写锁bbb 1560241409834

可以看到,在aaa线程执行4秒后bbb线程才进入,说明写锁同一时间只允许一个线程执行lock()后的方法。

3.3 读锁互斥

public class MyReadWriteLock {

    public ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
    public void read(){
        try{
            lock.readLock().lock();
            System.out.println("获取读锁 " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.readLock().unlock();
        }
    }
    public void write(){
        try{
            lock.writeLock().lock();
            System.out.println("获取写锁 " + Thread.currentThread().getName() + " " + System.currentTimeMillis());
            Thread.sleep(4000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.writeLock().unlock();
        }
    }

    public static void main(String[] args) {
        MyReadWriteLock myReadWriteLock = new MyReadWriteLock();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myReadWriteLock.read();
            }
        },"aaa").start();
        new Thread(new Runnable() {
            @Override
            public void run() {
                myReadWriteLock.write();
            }
        },"bbb").start();
    }
}

打印结果

获取读锁 aaa 1560241741764
获取写锁 bbb 1560241745770