【Java并发锁】Lock

目录

1 前言

2 synchronized与Lock的区别

2.1 锁的获取与释放操作

2.2 锁状态的可知性

2.3 灵活性

3 lockInterruptibly()中断测试


1 前言

本人jdk版本8。

在Lock接口出现之前,java程序靠的是synchronized关键字实现共享锁功能的,而Java SE 5之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能。Concurrent包中已经提供的实现了Lock接口的锁包括:ReentrantLock(可重入锁)和ReadWriteLock(读写锁)等。其提供的接口方法如下:

public interface Lock {
    // 获取锁,获取不到则将当前线程加入阻塞队列
    void lock();
    // 响应中断的获取锁
    void lockInterruptibly() throws InterruptedException;
    // 尝试一次获取锁的动作
    boolean tryLock();
    // 有时间限制的获取锁
    boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
    // 释放当前线程持有的锁
    void unlock();
}

它提供了synchronized关键字类似的同步功能,只是在使用是需要显示的获取和释放锁,虽然他缺少了(通过synchronized块或者方法所提供的)隐式获取释放锁的便捷性,但是却拥有了锁获取与释放的可操作性、可中断的获取锁以及超时获取锁的多种synchronized关键字锁不具备的同步特性。官方给出的使用方法如下:

   Lock lock = new ReentrantLock();
  lock.lock();
  try{
        // synchronized block
    }finally{
    lock.unLock();
  }

2 synchronized与Lock的区别

2.1 锁的获取与释放操作

  • synchronized,线程执行到加锁区域会自动获取锁,当加锁区域执行完或执行中抛出异常会自动释放锁,程序猿不用也不能控制锁的获取与释放。
  • Lock,程序猿要手动进行加锁与释放,如果忘记释放锁则可能造成死锁

2.2 锁状态的可知性

  • synchronized,参与锁竞争的线程不知道锁是否已被获取,也不知道获取锁的线程是不是自己。
  • Lock,通过同步器的getState()能知道锁的获取情况,通过getExclusiveOwnerThread()也能知道独占锁的是哪个线程。

2.3 灵活性

  • synchronized,线程获取锁失败只能阻塞,等到拥有锁的线程释放锁才有可能被唤醒,不能响应中断。
  • Lock,提供了三类方法来对锁进行灵活的获取,获取失败也可以使线程不陷入阻塞,就算阻塞了也能通过中断阻塞线程来取消阻塞。具体见下:
操作描述
tryLock()尝试获取一次锁。通过锁的状态来判断是否能获取成功,true则获取到锁,false则返回。
lockInterruptibly()可中断的获取锁,该方法必须显示的处理(如catch)可能抛出的InterruptedException。若获取锁失败该线程会被加入阻塞队列,并一直检测线程的中断状态,若线程被中断,则会将该线程从阻塞队列中移除并抛出InterruptedException,接下来该线程会从catich代码块继续向下执行若线程已经获取了锁,则中断除了将线程标志置为true以外不会产生任何影响。(详情见下面代码测试)
tryLock(long time, TimeUnit unit)在lockInterruptibly()的基础上增加了时间限制,若在指定时间内还没获取到锁则返回false并退出阻塞,从下一行代码接着执行。

3 lockInterruptibly()中断测试

因为InterruptedException不是RuntimeException,所以lockInterruptibly()方法必须显示的处理可能抛出来的InterruptedException。下面代码对lockInterruptibly()进行了catch包围。

1)线程获取锁失败阻塞时被中断

下面开启了两个线程,用同一个ReentrantLock来进行同步。线程1(也可能是线程2)首先执行,获取到锁,进入try区域,执行无限循环;线程2接着执行,因为线程1已经获取到锁,所以陷入阻塞。线程2阻塞后,在main线程中对线程2进行了中断。

public class Test1 {

    public static void main(String[] args) {
        MyThread mt1 = new MyThread("线程1");
        MyThread mt2 = new MyThread("线程2");
        mt1.start();
        mt2.start();
        while (true){
            if (MyThread.lock.hasQueuedThread(mt2)){
                System.out.println(mt2.getName()+"进入阻塞队列");
                mt2.interrupt();
                System.out.println("中断了"+mt2.getName());
                break;
            }
        }
    }
}

class MyThread extends Thread{
    static ReentrantLock lock = new ReentrantLock();
    MyThread(String name){ super(name); }

    @Override
    public void run(){
        try {
            lock.lockInterruptibly();
            System.out.println(Thread.currentThread().getName()+"进入了lock后面的try区域");
            while (true){}
        }catch (InterruptedException e) {
            System.out.println(Thread.currentThread().getName()+":是否在阻塞队列中——"+lock.hasQueuedThread(Thread.currentThread()));
            e.printStackTrace();
        }finally {
            if (lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }
}

打印信息如下。当线程获取锁失败而加入阻塞队列后,若此时被其它线程中断,该方法会抛出InterruptedException,同时将此线程的节点从阻塞队列移除,该线程会接着执行catch下面的代码注意一下上面代码的finally块,在unlock()之前我作了个判断,判断当前节点是否是这个锁的持有者,是才unlock,否则线程2被中断后继续执行到unlock会抛出IllegalMonitorStateException,因为释放锁必须要当前线程是锁的持有者。

线程1进入了lock后面的try区域
线程2进入阻塞队列
中断了线程2
线程2:是否在阻塞队列中——false
java.lang.InterruptedException at ....(此处省略异常打印信息 )

...(两个线程无限循环执行)

2)线程持有锁时被中断

启动一个线程,线程进行无限循环的判断,判断自己是否被中断,是就退出循环。

public class Test1 {

    public static void main(String[] args) throws InterruptedException {
        MyThread mt1 = new MyThread("线程1");
        mt1.start();
        Thread.sleep(2000);
        mt1.interrupt();
    }
}

class MyThread extends Thread{
    static ReentrantLock lock = new ReentrantLock();
    MyThread(String name){ super(name); }

    @Override
    public void run(){
        try {
            lock.lockInterruptibly();
            while (true){
                if (Thread.currentThread().isInterrupted()){
                    System.out.println("线程已被中断");
                    break;
                }
            }
            System.out.println("被中断了我还在执行");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            if (lock.isHeldByCurrentThread()){
                lock.unlock();
            }
        }
    }
}

打印信息如下。显然,持有锁的线程被中断只会将中断标志置为true,而不会使线程释放锁,更不会中断线程的执行。

线程已被中断
被中断了我还在执行

Process finished with exit code 0

wait()和join()被中断的过程参考:https://blog.csdn.net/qq_34039868/article/details/105103430

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值