理解Java中的互斥锁ReentrantLock和Condition

互斥锁

  • JDK1.5版本提供了java.util.concurrent.locks包,该包中提供了锁和等待条件的接口和类,可以用于代替JDK1.5之前的synchronized同步和监视器机制
  • 互斥锁指的是一次最多只能有一个线程持有的锁
  • 互斥锁在Java中的体现是Lock接口和其实现类ReentrantLock
  • Lock接口的出现主要替代了synchronized关键字的用处,其提供了一个比sychronized机制更广泛的锁定操作

Lock和sychronized机制的主要区别

  • synchronized机制提供了对于每个对象相关的隐式监视器锁的访问,并强制所有锁获取和释放都要出现在一个块结构中
  • 获取了多个锁时(多个synchronized代码块嵌套时),它们必须以相反的顺序释放
  • synchronized机制对锁的释放是隐式的,只要线程运行的代码超出了synchronized语句块范围,持有的锁对象就会自动释放
  • 锁定和取消锁定出现在不同作用范围中时,必须谨慎地确保保持锁定时所执行的所有代码用 try-finally 或 try-catch 加以保护,以确保在必要时释放锁
  • Lock机制必须是显式调用Lock对象的unlock()方法才能释放锁。
  • Lock机制可以不在同一个块结构中获取和释放锁,更加自由的释放锁

Lock接口

  • Lock 实现提供了使用 synchronized 方法和语句所没有的其他功能,包括提供了一个非块结构的获取锁尝试 tryLock()、一个获取可中断锁的尝试 lockInterruptibly() 和一个获取超时失效锁的尝试 tryLock(long, TimeUnit)
  • Lock 类还可以提供与隐式监视器锁完全不同的行为和语义,如保证排序、非重入用法或死锁检测。如果某个实现提供了这样特殊的语义,则该实现必须对这些语义加以记录。
  • 注意,Lock 实例只是普通的对象,其本身可以在 synchronized 语句中作为目标使用。获取 Lock 实例的监视器锁与调用该实例的任何 lock()方法没有特别的关系。为了避免混淆,建议除了在其自身的实现中之外,决不要以这种方式使用 Lock 实例。

lock

void lock() 获取锁

  • 如果锁处于空闲状态,当前线程将直接获取该lock对象锁。
  • 相反,如果锁已经被其他线程持有,将禁用当前线程,直到当前线程获取到锁.

unlock

void unlock() 释放锁

  • 当前线程将释放持有的锁。
  • 锁只能由持有者释放。
  • 如果线程并不持有锁,却执行了该方法,可能会导致异常的发生。

tryLock

boolean tryLock()

  • 仅在调用时锁为空闲状态才获取该锁。

  • 如果锁可用,则获取锁,并返回true

  • 如果锁不可用,立即返回false

  • 此方法可确保如果获取了锁,则会释放锁,如果未获取锁,则不会试图将其释放。即通常配合unlock()使用来释放锁。

    Lock lock = new ReentrantLock();
    if(lock.tryLock()){
    try{
       //获取到锁的一些操作
    }finally{
        //确保了获取锁,才能释放
        lock.unlock();
    }
    }else{
      //未获取到锁的一些操作
    }
    
  • tryLock()lock()方法的区别:

    • tryLock()方法只是试图获取锁,如果锁不可用,当前线程仍然可以继续往下执行.
    • lock()方法是一定要获取到锁,如果锁不可用,就会一直等待下去,锁定当前线程,在未获取指定锁对象之前,当前线程不会继续向下执行

Condition接口 - 条件

  • ConditionObject监视器方法wait、notify、notifyAll方法分解成不同的对象,为了方便通过将这些对象与任意Lock对象实现组合使用,为每个对象提供了多个等待set(wait-set)。其中,Lock替代了synchronized方法和语句的使用,Condition替代了Object监视器方法的使用

    扫描二维码关注公众号,回复: 2516844 查看本文章
  • 使用Condition对象的相关方法,可以方便的挂起和唤醒线程,而且可以特定的唤醒其其中某个线程。这也是和Object对象的wait()、notify()、notifyAll()方法的区别。

  • Object对象的wait()、notify()、notifyAll()方法存在一个问题:如果多个线程调用了obj的wait()方法而挂起,那么我们无法做到调用obj的notify()和notifyAll()方法唤醒其中特定的一个线程,而Conditon对象就可以做到。
  • Condition对象只能通过Lock类的newCondition()方法获取,因此一个Condition对象必然会有一个与其绑定的Lock锁

await

void await() 造成当前线程再接到信号或被中断之前一直处于等待状态

  • 将当前线程处于等待状态,并释放该Condition对象所绑定的锁.
  • 使用await()方法前当前线程必须持有与该Condition对象绑定的锁,否则程序可能会抛出异常。

signal

void signal() 唤醒一个在该Condition对象上挂起的线程

  • 如果存在多个线程同时等待该Condition对象的唤醒,则随机选择其中一个唤醒。
  • 线程被唤醒之前,必须重新获取到锁,即与该Condition对象绑定的Lock对象。

signalAll

void signalAll() 唤醒所有在该Condition对象上挂起的线程

  • 所有被唤醒的线程将竞争与该Condition对象绑定的锁,只有获取到锁的线程才能恢复到运行状态。

一个实例

问题的描述:

启动3个线程打印递增的数字, 线程1先打印1,2,3,4,5, 
然后是线程2打印6,7,8,9,10,然后是线程3打印11,12,13,14,15.
接着再由线程1打印16,17,18,19,20....以此类推,
直到打印到75. 程序的输出结果应该为:

 线程1: 1
 线程1: 2
 线程1: 3
 线程1: 4
 线程1: 5
 线程2: 6
 线程2: 7
 线程2: 8
 线程2: 9
 线程2: 10
 ...
 线程3: 71
 线程3: 72
 线程3: 73
 线程3: 74
 线程3: 75

利用ReentrantLockCondition接口组合,可以轻松指定和分配各个线程该完成的操作。代码如下:


public class ReentrantLockTest {

    public static void main(String[] args) {
        NumPrinter n = new NumPrinter();
        //此处确定每个线程应该执行几次,总共75个数分3个线程,每个线程分25个数字,
        // 5*5=25,每个线程执行5次,每次打印5个数字
        new Thread(()->{
            //每个线程执行5次
            for (int i = 0; i < 5 ; i++) {
                try {
                    n.print1();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i <5 ; i++) {

                try {
                    n.print2();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
        new Thread(()->{
            for (int i = 0; i < 5 ; i++) {
                try {
                    n.print3();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

class NumPrinter{
    private  int num = 1;
    private  int flag = 1;
    //使用ReentrantLock类和Condition接口来配合使用,指定唤醒哪个线程
    private ReentrantLock lock = new ReentrantLock();
    private Condition c1 = lock.newCondition();
    private Condition c2 = lock.newCondition();
    private Condition c3 = lock.newCondition();

    public void print1() throws InterruptedException {
        //三个线程同时占用临界区的资源,应该使用同步
        lock.lock();   //获取锁 和 下面的 unlock  代替了 synchronized
        if(flag != 1)  //当flag不为1时,c1进行等待,因为此时其他的线程正在执行他们的操作
            c1.await();
        if(num <= 75)
            for (int i = 0; i < 5; i++) {
                System.out.println("1---"+num++);
            }
        flag = 2;       //标记指定为2,在print2中限制除了flag不为2时,c2进行等待
        c2.signal();    //唤醒指定的c2
        lock.unlock();  //释放锁
    }

    public void print2() throws InterruptedException {
        lock.lock();
        if(flag != 2)
            c2.await();
        if(num <= 75)
            for (int i = 0; i < 5; i++) {
                System.out.println("2---"+num++);
            }
        flag = 3;
        c3.signal();
        lock.unlock();
    }

    public void print3() throws InterruptedException {
        lock.lock();
        if(flag != 3)
            c3.await();
        if(num <= 75)
            for (int i = 0; i < 5; i++) {
                System.out.println("3---"+num++);
            }
        flag = 1;
        c1.signal();
        lock.unlock();
    }

}

猜你喜欢

转载自blog.csdn.net/hxhaaj/article/details/81087905