公平锁与非公平锁

ReentrantLock有一个很大的特点,就是可以指定锁是公平锁还是非公平锁,公平锁表示线程获取锁的顺序是按照线程排队的顺序来分配的,而非公平锁就是一种获取锁的抢占机制,是随机获得锁的,先来的未必就一定能先得到锁,从这个角度讲,synchronized其实就是一种非公平锁。非公平锁的方式可能造成某些线程一直拿不到锁,自然是非公平的了。看一下例子,new ReentrantLock的时候有一个单一参数的构造函数表示构造的是一个公平锁还是非公平锁,传入true就可以了:

  1. public class ThreadDomain42
  2. {
  3. private Lock lock = new ReentrantLock(true);
  4.  
  5. public void testMethod()
  6. {
  7. try
  8. {
  9. lock.lock();
  10. System.out.println("ThreadName" + Thread.currentThread().getName() + "获得锁");
  11. }
  12. finally
  13. {
  14. lock.unlock();
  15. }
  16. }
  17. }
  1. public static void main(String[] args) throws Exception
  2. {
  3. final ThreadDomain42 td = new ThreadDomain42();
  4. Runnable runnable = new Runnable()
  5. {
  6. public void run()
  7. {
  8. System.out.println("◆线程" + Thread.currentThread().getName() + "运行了");
  9. td.testMethod();
  10. }
  11. };
  12. Thread[] threads = new Thread[5];
  13. for (int i = 0; i < 5; i++)
  14. threads[i] = new Thread(runnable);
  15. for (int i = 0; i < 5; i++)
  16. threads[i].start();
  17. }

看一下运行结果:

  1. ◆线程Thread-0运行了
  2. ◆线程Thread-3运行了
  3. ThreadNameThread-0获得锁
  4. ◆线程Thread-2运行了
  5. ◆线程Thread-1运行了
  6. ThreadNameThread-3获得锁
  7. ◆线程Thread-4运行了
  8. ThreadNameThread-2获得锁
  9. ThreadNameThread-1获得锁
  10. ThreadNameThread-4获得锁

我们的代码很简单,一执行run()方法的第一步就是尝试获得锁。看到结果里面获得锁的顺序和线程启动顺序是一致的,这就是公平锁。对比一下,如果是非公平锁运行结果是怎么样的,在new ReentrantLock的时候传入false:

  1. ◆线程Thread-1运行了
  2. ◆线程Thread-2运行了
  3. ◆线程Thread-0运行了
  4. ThreadNameThread-1获得锁
  5. ThreadNameThread-2获得锁
  6. ◆线程Thread-3运行了
  7. ◆线程Thread-4运行了
  8. ThreadNameThread-3获得锁
  9. ThreadNameThread-0获得锁
  10. ThreadNameThread-4获得锁

线程启动顺序是1 2 0 3 4,获得锁的顺序却是1 2 3 0 4,这就是非公平锁,它不保证先排队尝试去获取锁的线程一定能先拿到锁

从上面代码可以看出来

公平锁:根据加锁的顺序来分配锁,就是根据谁先执行到lock.lock这句代码来分配的
非公平:随机的,所有加锁的线程都会抢锁

getHoldCount()

getHoldCount()方法返回的是当前线程调用lock()的次数,看一下例子:

  1. public class ThreadDomain43
  2. {
  3. private ReentrantLock lock = new ReentrantLock();
  4.  
  5. public void testMethod1()
  6. {
  7. try
  8. {
  9. lock.lock();
  10. System.out.println("testMethod1 getHoldCount = " + lock.getHoldCount());
  11. testMethod2();
  12. }
  13. finally
  14. {
  15. lock.unlock();
  16. }
  17. }
  18.  
  19. public void testMethod2()
  20. {
  21. try
  22. {
  23. lock.lock();
  24. System.out.println("testMethod2 getHoldCount = " + lock.getHoldCount());
  25. }
  26. finally
  27. {
  28. lock.unlock();
  29. }
  30. }
  31. }
  1. public static void main(String[] args)
  2. {
  3. ThreadDomain43 td = new ThreadDomain43();
  4. td.testMethod1();
  5. }

看一下运行结果:

  1. testMethod1 getHoldCount = 1
  2. testMethod2 getHoldCount = 2

ReentrantLock和synchronized一样,锁都是可重入的,同一线程的同一个ReentrantLock的lock()方法被调用了多少次,getHoldCount()方法就返回多少

getQueueLength()和isFair()

getQueueLength()方法用于获取正等待获取此锁定的线程估计数。注意"估计"两个字,因为此方法遍历内部数据结构的同时,线程的数据可能动态变化

isFair()用来获取此锁是否公平锁

看一下例子:

  1. public class ThreadDomain44
  2. {
  3. public ReentrantLock lock = new ReentrantLock();
  4.  
  5. public void testMethod()
  6. {
  7. try
  8. {
  9. lock.lock();
  10. System.out.println("ThreadName = " + Thread.currentThread().getName() + "进入方法!");
  11. System.out.println("是否公平锁?" + lock.isFair());
  12. Thread.sleep(Integer.MAX_VALUE);
  13. }
  14. catch (InterruptedException e)
  15. {
  16. e.printStackTrace();
  17. }
  18. finally
  19. {
  20. lock.unlock();
  21. }
  22. }
  23. }
  1. public static void main(String[] args) throws InterruptedException
  2. {
  3. final ThreadDomain44 td = new ThreadDomain44();
  4. Runnable runnable = new Runnable()
  5. {
  6. public void run()
  7. {
  8. td.testMethod();
  9. }
  10. };
  11. Thread[] threads = new Thread[10];
  12. for (int i = 0; i < 10; i++)
  13. threads[i] = new Thread(runnable);
  14. for (int i = 0; i < 10; i++)
  15. threads[i].start();
  16. Thread.sleep(2000);
  17. System.out.println("有" + td.lock.getQueueLength() + "个线程正在等待!");
  18. }

看一下运行结果:

  1. ThreadName = Thread-0进入方法!
  2. 是否公平锁?false
  3. 9个线程正在等待!

ReentrantLock默认的是非公平锁,因此是否公平锁打印的是false。启动了10个线程,只有1个线程lock()了,其余9个等待,都符合预期。

hasQueuedThread()和hasQueuedThreads()

hasQueuedThread(Thread thread)用来查询指定的线程是否正在等待获取指定的对象监视器

hasQueuedThreads()用于查询是否有线程正在等待获取指定的对象监视器

看一下例子,换一个写法,ReentrantLock既然是一个类,就有类的特性,所以这次用继承ReentrantLock的写法,这也是很常见的:

  1. public class ThreadDomain45 extends ReentrantLock
  2. {
  3. public void waitMethod()
  4. {
  5. try
  6. {
  7. lock();
  8. Thread.sleep(Integer.MAX_VALUE);
  9. }
  10. catch (InterruptedException e)
  11. {
  12. e.printStackTrace();
  13. }
  14. finally
  15. {
  16. unlock();
  17. }
  18. }
  19. }
  1. public static void main(String[] args) throws InterruptedException
  2. {
  3. final ThreadDomain45 td = new ThreadDomain45();
  4. Runnable runnable = new Runnable()
  5. {
  6. public void run()
  7. {
  8. td.waitMethod();
  9. }
  10. };
  11. Thread t0 = new Thread(runnable);
  12. t0.start();
  13. Thread.sleep(500);
  14. Thread t1 = new Thread(runnable);
  15. t1.start();
  16. Thread.sleep(500);
  17. Thread t2 = new Thread(runnable);
  18. t2.start();
  19. Thread.sleep(500);
  20. System.out.println("t0 is waiting?" + td.hasQueuedThread(t0));
  21. System.out.println("t1 is waiting?" + td.hasQueuedThread(t1));
  22. System.out.println("t2 is waiting?" + td.hasQueuedThread(t2));
  23. System.out.println("is any thread waiting?" + td.hasQueuedThreads());
  24. }

这里加了几个Thread.sleep(500)保证线程按顺序启动(其实不按顺序启动也关系不大),看一下运行结果:

  1. t0 is waitingfalse
  2. t1 is waitingtrue
  3. t2 is waitingtrue
  4. is any thread waitingtrue

由于t0先启动获得了锁,因此不等待,返回false,另外两个线程则要等待获取t0的锁,因此返回的是true,而此ReentrantLock中有线程在等待,所以hasQueuedThreads()返回的是true

isHeldByCurrentThread()和isLocked()

isHeldByCurrentThread()表示此对象监视器是否由当前线程保持

isLocked()表示此对象监视器是否由任意线程保持

看一下例子:

  1. public class ThreadDomain46 extends ReentrantLock
  2. {
  3. public void testMethod()
  4. {
  5. try
  6. {
  7. lock();
  8. System.out.println(Thread.currentThread().getName() + "线程持有了锁!");
  9. System.out.println(Thread.currentThread().getName() + "线程是否持有锁?" +
  10. isHeldByCurrentThread());
  11. System.out.println("是否任意线程持有了锁?" + isLocked());
  12. Thread.sleep(Integer.MAX_VALUE);
  13. }
  14. catch (InterruptedException e)
  15. {
  16. e.printStackTrace();
  17. }
  18. finally
  19. {
  20. unlock();
  21. }
  22. }
  23.  
  24. public void testHoldLock()
  25. {
  26. System.out.println(Thread.currentThread().getName() + "线程是否持有锁?" +
  27. isHeldByCurrentThread());
  28. System.out.println("是否任意线程持有了锁?" + isLocked());
  29. }
  30. }
  1. public static void main(String[] args)
  2. {
  3. final ThreadDomain46 td = new ThreadDomain46();
  4. Runnable runnable0 = new Runnable()
  5. {
  6. public void run()
  7. {
  8. td.testMethod();
  9. }
  10. };
  11. Runnable runnable1 = new Runnable()
  12. {
  13. public void run()
  14. {
  15. td.testHoldLock();
  16. }
  17. };
  18. Thread t0 = new Thread(runnable0);
  19. Thread t1 = new Thread(runnable1);
  20. t0.start();
  21. t1.start();
  22. }

看一下运行结果:

  1. Thread-0线程持有了锁!
  2. Thread-1线程是否持有锁?false
  3. Thread-0线程是否持有锁?true
  4. 是否任意线程持有了锁?true
  5. 是否任意线程持有了锁?true

这个应该很好理解,当前持有锁的是Thread-0线程,所以对于Thread-1来说自然不持有锁。

tryLock()和tryLock(long timeout, TimeUnit unit)

tryLock()方法的作用是,在调用try()方法的时候,如果锁没有被另外一个线程持有,那么就返回true,否则返回false

tryLock(long timeout, TimeUnit unit)是tryLock()另一个重要的重载方法,表示如果在指定等待时间内获得了锁,则返回true,否则返回false

注意一下,tryLock()只探测锁是否,并没有lock()的功能,要获取锁,还得调用lock()方法,看一下tryLock()的例子:

  1. public class ThreadDomain47 extends ReentrantLock
  2. {
  3. public void waitMethod()
  4. {
  5. if (tryLock())
  6. System.out.println(Thread.currentThread().getName() + "获得了锁");
  7. else
  8. System.out.println(Thread.currentThread().getName() + "没有获得锁");
  9. }
  10. }
  1. public static void main(String[] args)
  2. {
  3. final ThreadDomain47 td = new ThreadDomain47();
  4. Runnable runnable = new Runnable()
  5. {
  6. public void run()
  7. {
  8. td.waitMethod();
  9. }
  10. };
  11. Thread t0 = new Thread(runnable);
  12. Thread t1 = new Thread(runnable);
  13. t0.start();
  14. t1.start();
  15. }

看一下运行结果:

  1. Thread-0获得了锁
  2. Thread-1没有获得锁

第一个线程获得了锁返回true,第二个线程自然返回的false。由于有了tryLock()这种机制,如果一个线程长时间在synchronzied代码/synchronized代码块之中,别的线程不得不长时间无限等待的情况将可以被避免。

ReentrantLock中的其他方法

篇幅原因,ReentrantLock中还有很多没有被列举到的方法就不写了,看一下它们的作用:

1、getWaitQueueLength(Condition condition)

类似getQueueLength(),不过此方法的前提是condition。比如5个线程,每个线程都执行了同一个await()的await()方法,那么方法调用的返回值是5,因为5个线程都在等待获得锁

2、hasWaiters(Condition condition)

查询是否有线程正在等待与此锁有关的condition条件。比如5个线程,每个线程都执行了同一个condition的await()方法,那么方法调用的返回值是true,因为它们都在等待condition

3、lockInterruptibly()

如果当前线程未被中断,则获取锁

4、getWaitingThreads(Condition condition)

返回一个collection,它包含可能正在等待与此锁相关给定条件的那些线程,因为构造结果的时候实际线程可能动态变化,因此返回的collection只是尽力的估计值

java多线程20 : ReentrantLock中的方法 ,公平锁和非公平锁的更多相关文章

  1. Java多线程12:ReentrantLock中的方法

    公平锁与非公平锁 ReentrantLock有一个很大的特点,就是可以指定锁是公平锁还是非公平锁,公平锁表示线程获取锁的顺序是按照线程排队的顺序来分配的,而非公平锁就是一种获取锁的抢占机制,是随机获得 ...

  2. Java中的公平锁和非公平锁实现详解

    前言 Java语言中有许多原生线程安全的数据结构,比如ArrayBlockingQueue.CopyOnWriteArrayList.LinkedBlockingQueue,它们线程安全的实现方式并非 ...

  3. Java并发指南8:AQS中的公平锁与非公平锁,Condtion

    一行一行源码分析清楚 AbstractQueuedSynchronizer (二) 转自https://www.javadoop.com/post/AbstractQueuedSynchronizer ...

  4. Java中的锁-悲观锁、乐观锁,公平锁、非公平锁,互斥锁、读写锁

    总览图 如果文中内容有错误,欢迎指出,谢谢. 悲观锁.乐观锁 悲观锁.乐观锁使用场景是针对数据库操作来说的,是一种锁机制. 悲观锁(Pessimistic Lock):顾名思义,就是很悲观,每次去拿数 ...

  5. 死磕 java同步系列之ReentrantLock源码解析(一)——公平锁、非公平锁

    问题 (1)重入锁是什么? (2)ReentrantLock如何实现重入锁? (3)ReentrantLock为什么默认是非公平模式? (4)ReentrantLock除了可重入还有哪些特性? 简介 ...

  6. Java多线程--公平锁与非公平锁

    上一篇文章介绍了AQS的基本原理,它其实就是一个并发包的基础组件,用来实现各种锁,各种同步组件的.它包含了state变量.加锁线程.等待队列等并发中的核心组件,现在我们来看一下多线程获取锁的顺序问题. ...

  7. 浅谈Java中的公平锁和非公平锁,可重入锁,自旋锁

    公平锁和非公平锁 这里主要体现在ReentrantLock这个类里面了 公平锁.非公平锁的创建方式: //创建一个非公平锁,默认是非公平锁 Lock lock = new ReentrantLock( ...

  8. 深入了解ReentrantLock中的公平锁和非公平锁的加锁机制

    ReentrantLock和synchronized一样都是实现线程同步,但是像比synchronized它更加灵活.强大.增加了轮询.超时.中断等高级功能,可以更加精细化的控制线程同步,它是基于AQ ...

  9. Java之ReentrantLock公平锁和非公平锁

    在Java的ReentrantLock构造函数中提供了两种锁:创建公平锁和非公平锁(默认).代码如下: public ReentrantLock() { sync = new NonfairSync( ...

随机推荐

  1. placement new 笔记

    之前看到 偶尔用placement new 的用法,当分配内存频繁,而且对效率要求很高的情况下,可以先申请一块大内存,然后在此内存上构建对象,关键是可以自动调用其构造函数,否则要悲剧. 但是之后要自己 ...

  2. linux中DHCP服务配置文件/etc/dhcpd.conf详细说明

    DHCP服务的配置 dhcpd.conf 是DHCP服务的配置文件,DHCP服务所有参数都是通过修改dhcpd.conf 文件来实现,安装后dhcpd.conf 是没有做任何配置的,将/usr/sha ...

  3. 什么是EPEL 及 Centos上安装EPEL

    RHEL以及他的衍生发行版如CentOS为了稳定,官方的rpm repository提供的rpm包为了服务器安全稳定更新往往是很滞后的,很多时候需要自己编译那太辛苦了,而EPEL恰恰可以解决这两方面的 ...

  4. Linux 源代码在线(http://lxr.linux.no/linux/)。

    LXR 是一个通用的源代码索引器和交叉引用器 它提供了一个基于 web 的可浏览任意定义以及任意标识的用法. 它支持很多种语言. LXR 曾经被作为 “Linux 交叉引用器” 但是已经被证明它可以用 ...

  5. linux下常用文件传输命令(转)

    因为工作原因,需要经常在不同的服务器见进行文件传输,特别是大文件的传输,因此对linux下不同服务器间数据传输命令和工具进行了研究和总结.主要是rcp,scp,rsync,ftp,sftp,lftp, ...

  6. 【MySQL】mysql在Windows下使用mysqldump命令备份数据库

    在cmd窗口中使用mysqldump命令首先需要配置环境变量 1,在计算机中找到MySQL的安装位置,找到MySQL Workbench,比如我的是C:\Program Files\MySQL\MyS ...

  7. maven最全教程

    Maven 教程 1.Maven概述 Maven 是什么? Maven 是一个项目管理和整合工具.Maven 为开发者提供了一套完整的构建生命周期框架.开发团队几乎不用花多少时间就能够自动完成工程的基 ...

  8. JAVA环境变量的脚本

    简单的一个脚本,用于自动设置环境变量.把代码拷贝,另存为javaEnv.bat.安装完Java 2 SDK之后,开一个命令行窗口,输入javaEnv java2SDKDir(java2SDKDir是你 ...

  9. SQLServer2008 全文检索摘记

    最近在做全文搜索的内容,google了一下全文检索,发现了一些问题,现在总结如下: 全文索引和查询概念(摘自SQL 联机帮助)SQL Server 2008 为应用程序和用户提供了对 SQL Serv ...

  10. Android ListView滚动条配置完全解析

    滚动条的相关显示效果 先来看下ListView的滚动条有哪些显示效果. 滚动条自身的外观 这点不用说,就是滚动条自身的颜色,形状等. Track的外观 默认的ListView是没有设置Track的.为 ...