【多线程 九】ReentrantLock的使用

思维导图:

【多线程 九】ReentrantLock的使用_第1张图片

1 ReentrantLock重入锁

和关键字synchronization相比,重入锁有着显示的操作过程。需要手动指定何时加锁,何时释放锁。重入锁对逻辑控制的灵活性要远远优于关键字synchronization。
在退出时,必须记得释放锁,否则其他线程就没有机会访问。
重入锁之所以叫重入锁,是因为这种锁能反复进入。但是只限于一个线程。font color=red 可重入就是说某个线程已经获得某个锁,可以再次获取锁而不会出现死锁

//锁定几个就要释放几个
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 isFair()方法

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方法

2.1awaitUniterruptibly(等待可中断)

造成当前线程在接到信号之前一直处于等待状态,我们知道线程在调用await()处于等待的状态,此时调用thread.interrupt()会报错,但是awaitUniterruptibly不会报错

2.2awaitUntil(time)(等待自动唤醒)

造成当前线程在接收到信号,被中断或者到达指定最后期限之前一直处于等待状态。

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

链接:https://juejin.cn/post/6844903869806460942

你可能感兴趣的:(多线程)