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

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

文章目录:


写在前面

Demo1(公平锁与非公平锁)

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

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

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

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

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

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

Demo8boolean 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对象的条件上,等待的线程预估数为1Thread-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-1Thread-0执行过程一样,不再多说;再然后Thread-2获得了CPU执行权,它执行自己的run方法,获得了lock锁对象,sleep睡眠1秒(不会释放锁对象)。这里的for循环一共创建了5个子线程,当前执行到了Thread-2,后面还有两个线程都在等待获得lock锁对象呢。这个时候,因为main主线程睡眠了3秒,而上面执行的前三个线程的睡眠加起来也到了3秒,所以这个时候会执行到main主线程中的最后一个for循环,这里就会判断每个子线程是否还在等待获得锁,是返回true、不是返回false。显然Thread01都已经获得并且释放了lock锁对象,Thread-2也已经获得了锁对象,它们三个肯定不会再等待获得锁了,所以前三个返回了false,而后两个还在等待获得锁呢,自然就返回了truefor循环执行完,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-0condition.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();
    }
}

目录
打赏
0
0
0
0
85
分享
相关文章
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
锁状态bits1bit是否是偏向锁2bit锁标志位无锁状态对象的hashCode001偏向锁线程ID101轻量级锁指向栈中锁记录的指针000重量级锁指向互斥量的指针010尼恩提示,讲完 如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等优化手段 , 可以得到 120分了。如减少锁粒度、锁粗化、关闭偏向锁(-XX:-UseBiasedLocking)等‌。JVM锁的膨胀、锁的内存结构变化相关的面试题,是非常常见的面试题。也是核心面试题。
深入理解Java锁升级:无锁 → 偏向锁 → 轻量级锁 → 重量级锁(图解+史上最全)
|
19天前
|
《从头开始学java,一天一个知识点》之:方法定义与参数传递机制
**你是否也经历过这些崩溃瞬间?** - 看了三天教程,连`i++`和`++i`的区别都说不清 - 面试时被追问&quot;`a==b`和`equals()`的区别&quot;,大脑突然空白 - 写出的代码总是莫名报NPE,却不知道问题出在哪个运算符 🚀 这个系列就是为你打造的Java「速效救心丸」!我们承诺:每天1分钟,地铁通勤、午休间隙即可完成学习;直击痛点,只讲高频考点和实际开发中的「坑位」;拒绝臃肿,没有冗长概念堆砌,每篇都有可运行的代码标本。上篇:《输入与输出:Scanner与System类》 | 下篇剧透:《方法重载与可变参数》。
46 25
|
13天前
|
重学Java基础篇—Java Object类常用方法深度解析
Java中,Object类作为所有类的超类,提供了多个核心方法以支持对象的基本行为。其中,`toString()`用于对象的字符串表示,重写时应包含关键信息;`equals()`与`hashCode()`需成对重写,确保对象等价判断的一致性;`getClass()`用于运行时类型识别;`clone()`实现对象复制,需区分浅拷贝与深拷贝;`wait()/notify()`支持线程协作。此外,`finalize()`已过时,建议使用更安全的资源管理方式。合理运用这些方法,并遵循最佳实践,可提升代码质量与健壮性。
23 1
Java中的异常处理方法
本文深入剖析Java异常处理机制,介绍可检查异常、运行时异常和错误的区别与处理方式。通过最佳实践方法,如使用合适的异常类型、声明精确异常、try-with-resources语句块、记录异常信息等,帮助开发者提高代码的可靠性、可读性和可维护性。良好的异常处理能保证程序稳定运行,避免资源泄漏和潜在问题。
|
27天前
|
Java代码结构解析:类、方法、主函数(1分钟解剖室)
### Java代码结构简介 掌握Java代码结构如同拥有程序世界的建筑蓝图,类、方法和主函数构成“黄金三角”。类是独立的容器,承载成员变量和方法;方法实现特定功能,参数控制输入环境;主函数是程序入口。常见错误包括类名与文件名不匹配、忘记static修饰符和花括号未闭合。通过实战案例学习电商系统、游戏角色控制和物联网设备监控,理解类的作用、方法类型和主函数任务,避免典型错误,逐步提升编程能力。 **脑图速记法**:类如太空站,方法即舱段;main是发射台,static不能换;文件名对仗,括号要成双;参数是坐标,void不返航。
47 5
|
1月前
|
java.time常用方法汇总
`java.time` API 是从 Java 8 开始引入的时间日期处理库,旨在替代老旧的 `java.util.Date` 和 `Calendar`。它提供了更简洁、强大和灵活的方式处理日期、时间、时区及时间间隔,支持全球化和时间计算需求。API 包含获取当前时间、创建指定时间、解析和格式化字符串、进行加减运算、比较时间、获取年月日时分秒、计算时间间隔、时区转换以及判断闰年等功能。示例代码展示了如何使用这些功能,极大简化了开发中的时间处理任务。
|
1月前
|
Linux编程: 在业务线程中注册和处理Linux信号
本文详细介绍了如何在Linux中通过在业务线程中注册和处理信号。我们讨论了信号的基本概念,并通过完整的代码示例展示了在业务线程中注册和处理信号的方法。通过正确地使用信号处理机制,可以提高程序的健壮性和响应能力。希望本文能帮助您更好地理解和应用Linux信号处理,提高开发效率和代码质量。
57 17
|
1月前
|
Linux编程: 在业务线程中注册和处理Linux信号
通过本文,您可以了解如何在业务线程中注册和处理Linux信号。正确处理信号可以提高程序的健壮性和稳定性。希望这些内容能帮助您更好地理解和应用Linux信号处理机制。
63 26
|
3月前
|
Java多线程编程秘籍:各种方案一网打尽,不要错过!
Java 中实现多线程的方式主要有四种:继承 Thread 类、实现 Runnable 接口、实现 Callable 接口和使用线程池。每种方式各有优缺点,适用于不同的场景。继承 Thread 类最简单,实现 Runnable 接口更灵活,Callable 接口支持返回结果,线程池则便于管理和复用线程。实际应用中可根据需求选择合适的方式。此外,还介绍了多线程相关的常见面试问题及答案,涵盖线程概念、线程安全、线程池等知识点。
323 2
Java多线程编程的陷阱与解决方案####
本文深入探讨了Java多线程编程中常见的问题及其解决策略。通过分析竞态条件、死锁、活锁等典型场景,并结合代码示例和实用技巧,帮助开发者有效避免这些陷阱,提升并发程序的稳定性和性能。 ####

热门文章

最新文章