Java——多线程高并发系列之ReentrantLock实现(非)公平锁常用方法的举例

Posted 张起灵-小哥

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Java——多线程高并发系列之ReentrantLock实现(非)公平锁常用方法的举例相关的知识,希望对你有一定的参考价值。

文章目录:

写在前面

Demo1(公平锁与非公平锁)

Demo2(int getHoldCount() 返回当前线程调用 lock()方法的次数)

Demo3(int getQueueLength() 返回正等待获得锁的线程预估数)

Demo4(int getWaitQueueLength(Condition condition)返回与 Condition 条件相关的等待的线程预估数)

Demo5(boolean hasQueuedThread(Thread thread) 查询参数指定的线程是否在等待获得锁 & boolean hasQueuedThreads() 查询是否还有线程在等待获得该锁)

Demo6(boolean hasWaiters(Condition condition)查询是否有线程正在等待指定的 Condition 条件)

Demo7(boolean isFair() 判断是否为公平锁 & boolean isHeldByCurrentThread() 判断当前线程是否持有该锁)

Demo8(boolean isLocked() 查询当前锁是否被线程持有)


写在前面

首先说一下有关公平锁与非公平锁:

大多数情况下,锁的申请都是非公平的。如果线程1与线程2都在请求锁 A,当锁 A 可用时,系统只是会从阻塞队列中随机的选择一个线程,不能保证其公平性。

公平的锁会按照时间先后顺序,保证先到先得,公平锁的这一特点不会出现线程饥饿现象。

synchronized 内部锁就是非公平的。

ReentrantLock 重入锁提供了一个构造方法:ReentrantLock(boolean fair) ,当在创建锁对象时实参传递true 可以把该锁设置为公平锁。公平锁看起来很公平,但是要实现公平锁必须要求系统维护一个有序队列,公平锁的实现成本较高,性能也低。因此默认情况下锁是非公平的,不是特别的需求,一般不使用公平锁。


下面是ReentrantLock类中的一些常用方法:

  1. int getHoldCount() 返回当前线程调用 lock()方法的次数
  2. int getQueueLength() 返回正等待获得锁的线程预估数
  3. int getWaitQueueLength(Condition condition) 返回与 Condition 条件相关的等待的线程预估数
  4. boolean hasQueuedThread(Thread thread) 查询参数指定的线程是否在等待获得锁
  5. boolean hasQueuedThreads() 查询是否还有线程在等待获得该锁
  6. boolean hasWaiters(Condition condition) 查询是否有线程正在等待指定的 Condition 条件
  7. boolean isFair() 判断是否为公平锁
  8. boolean isHeldByCurrentThread() 判断当前线程是否持有该锁
  9. boolean isLocked() 查询当前锁是否被线程持有;

Demo1(公平锁与非公平锁)

package com.szh.lock.method;

import java.util.concurrent.locks.ReentrantLock;

/**
 * 公平锁与非公平锁
 * 运行程序
 * 1) 如果是非公平锁, 系统倾向于让一个线程再次获得已经持有的锁, 这种分配策略是高效的,非公平的
 * 2) 如果是公平锁, 多个线程不会发生同一个线程连续多次获得锁的可能, 保证了公平性
 */
public class Test01 {

    //默认是非公平锁
    //static ReentrantLock lock=new ReentrantLock();
    //公平锁
    static ReentrantLock lock=new ReentrantLock(true);

    public static void main(String[] args) {
        Runnable r=new Runnable() {
            @Override
            public void run() {
                while (true) {
                    try {
                        lock.lock();
                        System.out.println(Thread.currentThread().getName() + " 获得了锁对象");
                    }finally {
                        lock.unlock();
                    }
                }
            }
        };

        for (int i = 0; i < 5; i++) {
            new Thread(r).start();
        }
    }
}

如果是:static ReentrantLock lock=new ReentrantLock(); 这默认就是非公平锁,可以看到多线程执行之后,系统更倾向于让一个之前获得锁的线程再次获得锁,这显然就体现了非公平性。

如果是:static ReentrantLock lock=new ReentrantLock(true); 这就是公平锁, 可以看到系统会按照线程等待的先后时间顺序,有序的为每个线程分配锁对象,这个顺序一直就是 1-0-4-2-3。


Demo2(int getHoldCount() 返回当前线程调用 lock()方法的次数)

package com.szh.lock.method;

import java.util.concurrent.locks.ReentrantLock;

/**
 * int getHoldCount() 返回当前线程调用 lock()方法的次数
 */
public class Test02 {

    static ReentrantLock lock=new ReentrantLock(); //定义锁对象

    public static void method1() {
        try {
            lock.lock();
            //打印当前线程嗲用lock()方法的次数
            System.out.println(Thread.currentThread().getName() + " ---> method1 hold count: " + lock.getHoldCount());
            //调用method2方法,因为ReentrantLock支持锁的可重入性,所以这里可以再次获得锁
            method2();
        } finally {
            lock.unlock();
        }
    }

    public static void method2() {
        try {
            lock.lock();
            //打印当前线程嗲用lock()方法的次数
            System.out.println(Thread.currentThread().getName() + " ---> method2 hold count: " + lock.getHoldCount());
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        //main主线程调用method1方法
        method1();
    }
}


Demo3(int getQueueLength() 返回正等待获得锁的线程预估数)

package com.szh.lock.method;

import java.util.concurrent.locks.ReentrantLock;

/**
 * int getQueueLength() 返回正等待获得锁的线程预估数
 */
public class Test03 {

    static ReentrantLock lock=new ReentrantLock(); //定义锁对象

    public static void method1() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + "获得锁,执行方法,估计等待获得锁的线程数:"
                                + lock.getQueueLength());
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Runnable r=new Runnable() {
            @Override
            public void run() {
                method1();
            }
        };

        for (int i = 0; i < 10; i++) {
            new Thread(r).start();
        }
    }
}

这个方法返回的是正在等待获得锁的线程预估数。当Thread-0第一个抢到CPU执行权时,进来获得lock锁对象,此时只有它一个线程在执行,所以还没有等待的线程。当它执行到sleep方法时,转入睡眠等待状态,但不会释放lock锁对象。这个时候,剩下的9个子线程都来了,它们肯定要等待啊(因为lock锁对象被Thread-0占有了),那么这个时候,假如Thread-1在等待队列的最前面,那么当Thread-0醒了之后释放锁对象,Thread-1获得了锁对象,此时还在等待的子线程就是(10-Thread0-Thread1=8),是这样的。后面的那几个都是同样的理解,我就不再叙述了。


Demo4(int getWaitQueueLength(Condition condition)返回与 Condition 条件相关的等待的线程预估数)

package com.szh.lock.method;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * int getWaitQueueLength(Condition condition)
 * 返回与 Condition 条件相关的等待的线程预估数
 */
public class Test04 {

    static class Service {
        private ReentrantLock lock=new ReentrantLock(); //定义锁对象
        private Condition condition=lock.newCondition(); //获得Condition对象

        public void waitMethod() {
            try {
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "进入等待前,现在该condition条件上等待的线程预估数:"
                                    + lock.getWaitQueueLength(condition));
                condition.await();
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                lock.unlock();
            }
        }

        public void notifyMethod() {
            try {
                lock.lock();
                condition.signalAll();
                System.out.println("唤醒所有的等待后,该condition条件上等待的线程预估数:" + lock.getWaitQueueLength(condition));
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Service s=new Service();

        Runnable r=new Runnable() {
            @Override
            public void run() {
                s.waitMethod();
            }
        };

        //开启10个子线程
        for (int i = 0; i < 10; i++) {
            new Thread(r).start();
        }

        Thread.sleep(1000);
        s.notifyMethod(); //1秒钟之后,唤醒所有的等待线程
    }
}

这个案例说的是,在当前Condition对象的条件上,等待的线程预估数。当Thread-0执行,此时还没有线程执行 condition.await() 进入等待状态,所以线程预估数是0,当Thread-0通过condition对象调用await方法之后,他进入了等待状态,同时释放lock锁对象;这个时候,Thread-2进来执行,那么它就可以知道,在当前Condition对象的条件上,等待的线程预估数为1(Thread-0)。那么后面几个子线程进入等待都是同样的理解。最后main主线程中通过调用唤醒方法中的condition.signAll()之后,唤醒了所有还在await的子线程,那么此时在当前Condition对象的条件上,等待的线程预估数就是0了。


Demo5(boolean hasQueuedThread(Thread thread) 查询参数指定的线程是否在等待获得锁 & boolean hasQueuedThreads() 查询是否还有线程在等待获得该锁)

package com.szh.lock.method;

import java.util.concurrent.locks.ReentrantLock;

/**
 * boolean hasQueuedThread(Thread thread) 查询参数指定的线程是否在等待获得锁
 * boolean hasQueuedThreads() 查询是否还有线程在等待获得该锁
 */
public class Test05 {

    static ReentrantLock lock=new ReentrantLock(); //定义锁对象

    public static void waitMethod() {
        try {
            lock.lock();
            System.out.println(Thread.currentThread().getName() + " 获得了锁");
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + " 释放了锁对象");
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        Runnable r=new Runnable() {
            @Override
            public void run() {
                waitMethod();
            }
        };

        Thread[] threads=new Thread[5]; //定义线程数组

        //为每个线程赋值,并启动线程
        for (int i = 0; i < threads.length; i++) {
            threads[i]=new Thread(r);
            threads[i].setName("thread ---> " + i);
            threads[i].start();
        }

        Thread.sleep(1000 * 3);

        //判断数组中的每个线程对象是否还在等待获得锁
        for (int i = 0; i < threads.length; i++) {
            System.out.println(lock.hasQueuedThread(threads[i]));
        }

        Thread.sleep(1000 * 2);

        //再次判断是否还有线程正在等待获得锁
        System.out.println(lock.hasQueuedThreads());
    }
}

 首先Thread-0获得了CPU执行权,它执行自己的run方法,首先获得了lock锁对象,sleep睡眠1秒(不会释放锁对象)之后醒来,释放lock锁对象;之后Thread-1与Thread-0执行过程一样,不再多说;再然后Thread-2获得了CPU执行权,它执行自己的run方法,获得了lock锁对象,sleep睡眠1秒(不会释放锁对象)。这里的for循环一共创建了5个子线程,当前执行到了Thread-2,后面还有两个线程都在等待获得lock锁对象呢。这个时候,因为main主线程睡眠了3秒,而上面执行的前三个线程的睡眠加起来也到了3秒,所以这个时候会执行到main主线程中的最后一个for循环,这里就会判断每个子线程是否还在等待获得锁,是返回true、不是返回false。显然Thread0、1都已经获得并且释放了lock锁对象,Thread-2也已经获得了锁对象,它们三个肯定不会再等待获得锁了,所以前三个返回了false,而后两个还在等待获得锁呢,自然就返回了true。for循环执行完,Thread-2也醒了,释放锁对象,之后Thread4执行获得锁(睡了1秒)释放锁,然后Thread-3执行获得锁(睡了1秒)还没释放锁的时候,main主线程中也睡眠了2秒,此时main主线程醒了,所以最后再判断是否还有线程正在等待获得锁,此时5个子线程全部获得了锁,所以没有线程在等待获得锁了。最后Thread-3醒来释放锁。


Demo6(boolean hasWaiters(Condition condition)查询是否有线程正在等待指定的 Condition 条件)

package com.szh.lock.method;

import java.util.Random;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.ReentrantLock;

/**
 * boolean hasWaiters(Condition condition)
 * 查询是否有线程正在等待指定的 Condition 条件
 */
public class Test06 {

    static ReentrantLock lock=new ReentrantLock(); //创建锁对象
    static Condition condition=lock.newCondition(); //获得Condition对象

    static void method() {
        try {
            lock.lock();
            System.out.println("是否有线程正在等待当前Condition条件? " + lock.hasWaiters(condition)
                            + " --- waitqueuelength: " + lock.getWaitQueueLength(condition));
            System.out.println(Thread.currentThread().getName() + " waiting......");
            condition.await(new Random().nextInt(1000), TimeUnit.MILLISECONDS); //超时会自动唤醒
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            System.out.println(Thread.currentThread().getName() + " unlock......");
            lock.unlock();
        }
    }

    public static void main(String[] args) {
        Runnable r=new Runnable() {
            @Override
            public void run() {
                method();
            }
        };

        for (int i = 0; i < 5; i++) {
            new Thread(r).start();
        }
    }
}

最开始Thread-0抢到CPU执行权开始执行,此时只有它一个线程,而且也没执行到condition对象的某个方法,所以这里没有线程正在等待当前Condition条件(false),与 Condition 条件相关的等待的线程预估数为0。之后Thread-0执行到await方法进入等待状态,同时释放锁对象。

然后Thread-2来了,此时肯定有一个Thread-0在condition.await(XXX)等待啊,所以这里是true,那么与 Condition 条件相关的等待的线程预估数肯定就是1。然后Thread-2执行到await方法进入等待状态,同时释放锁对象。

后面几个子线程都是上述道理,不再多说了。此时5个子线程都等待了,根据 condition.await(new Random().nextInt(1000), TimeUnit.MILLISECONDS); //超时会自动唤醒 ,所以最后时间超出了1秒,所有的子线程都会被自动唤醒的,最后依次释放锁对象。


Demo7(boolean isFair() 判断是否为公平锁 & boolean isHeldByCurrentThread() 判断当前线程是否持有该锁)

package com.szh.lock.method;

import java.util.Random;
import java.util.concurrent.locks.ReentrantLock;

/**
 * boolean isFair() 判断是否为公平锁
 * boolean isHeldByCurrentThread() 判断当前线程是否持有该锁
 */
public class Test07 {

    static class Service {
        private ReentrantLock lock=new ReentrantLock();

        public Service(boolean isFair) {
            this.lock=new ReentrantLock(isFair);
        }

        public void method() {
            try {
                System.out.println("是否是公平锁? " + lock.isFair() + " ---> " + Thread.currentThread().getName()
                        + "调用lock方法之前是否持有锁? " + lock.isHeldByCurrentThread());
                lock.lock();
                System.out.println(Thread.currentThread().getName() + "调用lock方法后是否持有锁? " + lock.isHeldByCurrentThread());
            } finally {
                if (lock.isHeldByCurrentThread()) {
                    lock.unlock();
                }
            }
        }
    }

    public static void main(String[] args) {
        Runnable r=new Runnable() {
            @Override
            public void run() {
                int num=new Random().nextInt();
                new Service(num % 2 == 0 ? true : false).method();
            }
        };
        for (int i = 0; i < 5; i++) {
            new Thread(r).start();
        }
    }
}

这个案例说白了,就是下面这两行代码:

//默认是非公平锁
static ReentrantLock lock=new ReentrantLock();

//公平锁
static ReentrantLock lock=new ReentrantLock(true);


Demo8(boolean isLocked() 查询当前锁是否被线程持有)

package com.szh.lock.method;

import java.util.concurrent.locks.ReentrantLock;

/**
 * boolean isLocked() 查询当前锁是否被线程持有
 */
public class Test08 {

    static ReentrantLock lock=new ReentrantLock();

    static void method() {
        try {
            System.out.println("before lock... " + lock.isLocked());
            lock.lock();
            System.out.println("after lock... " + lock.isLocked());
            Thread.sleep(1000 * 2);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread()) {
                lock.unlock();
            }
        }
    }

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

 

以上是关于Java——多线程高并发系列之ReentrantLock实现(非)公平锁常用方法的举例的主要内容,如果未能解决你的问题,请参考以下文章

Java——多线程高并发系列之创建多线程的三种方式(ThreadRunnableCallable)

Java——多线程高并发系列之创建多线程的三种方式(ThreadRunnableCallable)

Java——多线程高并发系列之线程池(Executor)的理解与使用

Java——多线程高并发系列之线程池(Executor)的理解与使用

Java——多线程高并发系列之ThreadLocal的使用

Java——多线程高并发系列之ThreadLocal的使用