java并发编程--互斥锁, 读写锁及条件

素颜马尾好姑娘i 2022-01-14 13:07 435阅读 0赞

2019独角兽企业重金招聘Python工程师标准>>> hot3.png

java.util.concurrent.locks包提供了锁和等待条件的接口和类, 可用于替代JDK1.5之前的同步(synchronized)和监视器机制(主要是Object类的wait(), notify(), notifyAll()方法).

互斥锁—Lock接口及其实现类ReentrantLock

所谓互斥锁, 指的是一次最多只能有一个线程持有的锁. 在jdk1.5之前, 我们通常使用synchronized机制控制多个线程对共享资源的访问. 而现在, Lock提供了比synchronized机制更广泛的锁定操作, Lock和synchronized机制的主要区别:

  • synchronized机制提供了对与每个对象相关的隐式监视器锁的访问, 并强制所有锁获取和释放均要出现在一个块结构中, 当获取了多个锁时, 它们必须以相反的顺序释放. synchronized机制对锁的释放是隐式的, 只要线程运行的代码超出了synchronized语句块范围, 锁就会被释放. 而Lock机制必须显式的调用Lock对象的unlock()方法才能释放锁, 这为获取锁和释放锁不出现在同一个块结构中, 以及以更自由的顺序释放锁提供了可能. 以下代码演示了在不同的块结构中获取和释放锁:

Java代码

  1. public class LockTest {

    1. private static Lock lock = new ReentrantLock();
    2. public static void main(String[] args) {
    3. lock.lock();
    4. invokeMethod();
    5. }
    6. private static void invokeMethod() {
    7. lock.unlock();
    8. }

    }

为了确保锁被释放, 通常会采用如下的代码形式:

Java代码

  1. Lock lock = new ReentrantLock();
    // 获取锁
    lock.lock();
    1. try {
    2. // access the resource protected by this lock
    3. } finally {
    4. // 释放锁
    5. lock.unlock();
    6. }

|—void lock(): 执行此方法时, 如果锁处于空闲状态, 当前线程将获取到锁. 相反, 如果锁已经被其他线程持有, 将禁用当前线程, 直到当前线程获取到锁.

|—void unlock(): 执行此方法时, 当前线程将释放持有的锁. 锁只能由持有者释放, 如果线程并不持有锁, 却执行该方法, 可能导致异常的发生.

  • Lock提供了一个非块结构的获取锁尝试—tryLock(), 一个获取可中断锁的尝试—lockInterruptibly()和一个获取超时失效锁的尝试—tryLock(long time, TimeUnit unit).

|—boolean tryLock(): 如果锁可用, 则获取锁, 并立即返回true, 否则返回false. 该方法和lock()的区别在于, tryLock()只是”试图”获取锁, 如果锁不可用, 不会导致当前线程被禁用, 当前线程仍然继续往下执行代码. 而lock()方法则是一定要获取到锁, 如果锁不可用, 就一直等待, 在未获得锁之前,当前线程并不继续向下执行. 通常采用如下的代码形式调用tryLock()方法:

Java代码

  1. Lock lock = new ReentrantLock();
    if (lock.tryLock()) {
    1. try {
    2. // manipulate protected state
    3. } finally {
    4. lock.unlock();
    5. }
    } else {
    1. // perform alternative actions
    }

此用法可确保如果获取了锁, 则会释放锁; 如果未获取锁, 则不会试图将其释放.

  • Lock的newCondition()方法可以获得与该锁绑定的Condition对象, Condition的详细介绍如下.

条件—Condition

调用Condition对象的相关方法, 可以方便的挂起和唤醒线程. Object对象的wait(), notify(), notifyAll()方法当然也可以做到这一点, 但是Object对象的这些方法存在很不方便的地方—如果多个线程调用了obj的wait()方法而挂起, 那么我们无法做到调用obj的notify()和notifyAll()方法唤醒其中特定的一个线程. 而Condition对象就可以做到这一点. 具体的代码请参见我的上一篇博客http://coolxing.iteye.com/blog/1236696中的解法二部分.

  • void await(): 调用Condition对象的await()方法将导致当前线程被挂起, 并释放该Condition对象所绑定的锁. Condition对象只能通过Lock类的newCondition()方法获取, 因此一个Condition对象必然有一个与其绑定的Lock锁. 调用Condition对象的await()方法的前提是: 当前线程必须持有与该Condition对象绑定的锁, 否则程序可能抛出异常.
  • void signal(): 唤醒一个在该Condition对象上挂起的线程. 如果存在多个线程等待这个Condition对象的唤醒, 则随机选择一个. 线程被唤醒之前, 必须重新获取到锁(与该Condition对象绑定的Lock对象).
  • void signalAll(): 唤醒所有在该Condition对象上挂起的线程. 所有被唤醒的线程将竞争与该Condition对象绑定的锁, 只有获取到锁的线程才能恢复到运行状态.

读写锁—ReadWriteLock接口及其实现类ReentrantReadWriteLock

ReentrantReadWriteLock中定义了2个内部类, ReentrantReadWriteLock.ReadLock和ReentrantReadWriteLock.WriteLock, 分别用来代表读取锁和写入锁. ReentrantReadWriteLock对象提供了readLock()和writeLock()方法, 用于获取读取锁和写入锁.

  • 读取锁允许多个reader线程同时持有, 而写入锁最多只能有一个writter线程持有.
  • 读写锁的使用场合: 读取共享数据的频率远大于修改共享数据的频率. 在上述场合下, 使用读写锁控制共享资源的访问, 可以提高并发性能.
  • 如果一个线程已经持有了写入锁, 则可以再持有读写锁. 相反, 如果一个线程已经持有了读取锁, 则在释放该读取锁之前, 不能再持有写入锁.
  • 可以调用写入锁的newCondition()方法获取与该写入锁绑定的Condition对象, 此时与普通的互斥锁并没有什么区别. 但是调用读取锁的newCondition()方法将抛出异常.

使用读写锁的一个例子:

Java代码

  1. public class ReadWriteLockTest {
  2. private static ReadWriteLock lock = new ReentrantReadWriteLock();
  3. private static Person person = new Person("David Beckham", true);
  4. public static void main(String[] args) {
  5. new Thread() {
  6. public void run() {
  7. while(true) {
  8. try {
  9. lock.readLock().lock();
  10. System.out.print("name = " + person.getName());
  11. System.out.println(", isMan = " + person.isMan());
  12. } finally {
  13. lock.readLock().unlock();
  14. }
  15. }
  16. };
  17. }.start();
  18. new Thread() {
  19. public void run() {
  20. boolean state = true;
  21. while(true) {
  22. try {
  23. lock.writeLock().lock();
  24. if (state) {
  25. person.setName("Lady GaGa");
  26. person.setMan(false);
  27. state = false;
  28. } else {
  29. person.setName("David Beckham");
  30. person.setMan(true);
  31. state = true;
  32. }
  33. } finally {
  34. lock.writeLock().unlock();
  35. }
  36. }
  37. };
  38. }.start();
  39. }
  40. }
  41. class Person {
  42. private String name;
  43. private boolean isMan;
  44. public Person(String name, boolean isMan) {
  45. this.name = name;
  46. this.isMan = isMan;
  47. }
  48. public String getName() {
  49. return name;
  50. }
  51. public void setName(String name) {
  52. this.name = name;
  53. }
  54. public boolean isMan() {
  55. return isMan;
  56. }
  57. public void setMan(boolean isMan) {
  58. this.isMan = isMan;
  59. }
  60. }

不管程序运行多久, 也不会有人妖的出现.

转载于:https://my.oschina.net/u/131940/blog/698398

发表评论

表情:
评论列表 (有 0 条评论,435人围观)

还没有评论,来说两句吧...

相关阅读

    相关 并发编程-

    前言: 读写锁一般使用的场景是:读的操作远大于写操作,只有在这种情况下,才可以增加并发性。当写的操作大于读的操作(完全违背了读写锁的定义,后面会讲到),当频繁切换锁的话,性能

    相关 互斥

    一、线程互斥方式。 --- 互斥锁 1、什么是互斥锁?特点怎么样? 互斥锁是专门用于处理线程之间互斥的一种方式,它有两种:上锁状态/解锁状态。 如果互斥锁处于上锁状