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();
    }
}

相关文章
|
10天前
|
存储 Java 程序员
Java基础的灵魂——Object类方法详解(社招面试不踩坑)
本文介绍了Java中`Object`类的几个重要方法,包括`toString`、`equals`、`hashCode`、`finalize`、`clone`、`getClass`、`notify`和`wait`。这些方法是面试中的常考点,掌握它们有助于理解Java对象的行为和实现多线程编程。作者通过具体示例和应用场景,详细解析了每个方法的作用和重写技巧,帮助读者更好地应对面试和技术开发。
50 4
|
21天前
|
Java API
Java 对象释放与 finalize 方法
关于 Java 对象释放的疑惑解答,以及 finalize 方法的相关知识。
42 17
|
14天前
|
Java 测试技术 Maven
Java一分钟之-PowerMock:静态方法与私有方法测试
通过本文的详细介绍,您可以使用PowerMock轻松地测试Java代码中的静态方法和私有方法。PowerMock通过扩展Mockito,提供了强大的功能,帮助开发者在复杂的测试场景中保持高效和准确的单元测试。希望本文对您的Java单元测试有所帮助。
28 2
|
22天前
|
Java 开发者
在Java多线程编程中,选择合适的线程创建方法至关重要
【10月更文挑战第20天】在Java多线程编程中,选择合适的线程创建方法至关重要。本文通过案例分析,探讨了继承Thread类和实现Runnable接口两种方法的优缺点及适用场景,帮助开发者做出明智的选择。
15 2
|
16天前
|
Java Spring
JAVA获取重定向地址URL的两种方法
【10月更文挑战第17天】本文介绍了两种在Java中获取HTTP响应头中的Location字段的方法:一种是使用HttpURLConnection,另一种是使用Spring的RestTemplate。通过设置连接超时和禁用自动重定向,确保请求按预期执行。此外,还提供了一个自定义的`NoRedirectSimpleClientHttpRequestFactory`类,用于禁用RestTemplate的自动重定向功能。
|
Java
java源码 - ReentrantLock之FairSync
开篇  这篇文章主要是讲解FairSync公平锁的源码分析,整个内容分为加锁过程、解锁过程,CLH队列等概念。  首先一直困扰我的CLH队列的CLH的缩写我终于明白,看似三个人的人名的首字符缩写"CLH" (Craig, Landin, andHagersten)。
1029 0
|
Java
java源码 - ReentrantLock之NonfairSync
开篇  NonfairSync和FairSync相比而言,多了一次抢占机会,其他处理逻辑几乎是一模一样。 NonfairSync的tryAcquire的操作流程中如果发现当前锁未被占用那么立即抢占锁。
1080 0
|
8天前
|
安全 Java 测试技术
Java并行流陷阱:为什么指定线程池可能是个坏主意
本文探讨了Java并行流的使用陷阱,尤其是指定线程池的问题。文章分析了并行流的设计思想,指出了指定线程池的弊端,并提供了使用CompletableFuture等替代方案。同时,介绍了Parallel Collector库在处理阻塞任务时的优势和特点。
|
4天前
|
安全 Java 开发者
深入解读JAVA多线程:wait()、notify()、notifyAll()的奥秘
在Java多线程编程中,`wait()`、`notify()`和`notifyAll()`方法是实现线程间通信和同步的关键机制。这些方法定义在`java.lang.Object`类中,每个Java对象都可以作为线程间通信的媒介。本文将详细解析这三个方法的使用方法和最佳实践,帮助开发者更高效地进行多线程编程。 示例代码展示了如何在同步方法中使用这些方法,确保线程安全和高效的通信。
23 9
|
7天前
|
存储 安全 Java
Java多线程编程的艺术:从基础到实践####
本文深入探讨了Java多线程编程的核心概念、应用场景及其实现方式,旨在帮助开发者理解并掌握多线程编程的基本技能。文章首先概述了多线程的重要性和常见挑战,随后详细介绍了Java中创建和管理线程的两种主要方式:继承Thread类与实现Runnable接口。通过实例代码,本文展示了如何正确启动、运行及同步线程,以及如何处理线程间的通信与协作问题。最后,文章总结了多线程编程的最佳实践,为读者在实际项目中应用多线程技术提供了宝贵的参考。 ####

热门文章

最新文章

  • 1
    高并发场景下,到底先更新缓存还是先更新数据库?
    66
  • 2
    Java面试题:解释Java NIO与BIO的区别,以及NIO的优势和应用场景。如何在高并发应用中实现NIO?
    74
  • 3
    Java面试题:设计一个线程安全的单例模式,并解释其内存占用和垃圾回收机制;使用生产者消费者模式实现一个并发安全的队列;设计一个支持高并发的分布式锁
    68
  • 4
    Java面试题:如何实现一个线程安全的单例模式,并确保其在高并发环境下的内存管理效率?如何使用CyclicBarrier来实现一个多阶段的数据处理任务,确保所有阶段的数据一致性?
    62
  • 5
    Java面试题:结合建造者模式与内存优化,设计一个可扩展的高性能对象创建框架?利用多线程工具类与并发框架,实现一个高并发的分布式任务调度系统?设计一个高性能的实时事件通知系统
    55
  • 6
    Java面试题:假设你正在开发一个Java后端服务,该服务需要处理高并发的用户请求,并且对内存使用效率有严格的要求,在多线程环境下,如何确保共享资源的线程安全?
    69
  • 7
    在Java中实现高并发的数据访问控制
    42
  • 8
    使用Java构建一个高并发的网络服务
    29
  • 9
    微服务06----Eureka注册中心,微服务的两大服务,订单服务和用户服务,订单服务需要远程调用我们的用,户服务,消费者,如果环境改变,硬编码问题就会随之产生,为了应对高并发,我们可能会部署成一个集
    37
  • 10
    如何设计一个秒杀系统,(高并发高可用分布式集群)
    129