Java并发编程 | 第二篇:ReentrantLock重入锁

1、ReentrantLock的介绍

在java关键字synchronized也是重入锁,隐式支持重入性,synchronized通过获取自增,释放自减的方式实现重入。

synchronized的局限性:

  1. 当线程尝试获取锁的时候,如果获取不到锁会一直阻塞。
  2. 如果获取锁的线程进入休眠或者阻塞,除非当前线程异常,否则其他线程尝试获取锁必须一直等待。

ReentrantLock重入锁,是实现Lock接口的一个类,也是在实际编程中使用频率很高的一个锁,支持重入性,表示能够对共享资源能够重复加锁,即当前线程获取该锁再次获取不会被阻塞。

2、重入性的实现原理

要想支持重入性,就要解决两个问题:

  1. 在线程获取锁的时候,如果已经获取锁的线程是当前线程的话则直接再次获取成功;
  2. 由于锁会被获取n次,那么只有锁在被释放同样的n次之后,该锁才算是完全释放成功。

我们来看看ReentrantLock是怎样实现第一个问题的,以非公平锁为例,判断当前线程能否获得锁为例,核心方法为nonfairTryAcquire

final boolean nonfairTryAcquire(int acquires) {
    final Thread current = Thread.currentThread();
    int c = getState();
    //1. 如果该锁未被任何线程占有,该锁能被当前线程获取
    if (c == 0) {
        if (compareAndSetState(0, acquires)) {
            setExclusiveOwnerThread(current);
            return true;
        }
    }
    //2.若被占有,检查占有线程是否是当前线程
    else if (current == getExclusiveOwnerThread()) {
        // 3. 再次获取,计数加一
        int nextc = c + acquires;
        if (nextc < 0) // overflow
            throw new Error("Maximum lock count exceeded");
        setState(nextc);
        return true;
    }
    return false;
}

这段代码的逻辑也很简单,具体请看注释。为了支持重入性,在第二步增加了处理逻辑,如果该锁已经被线程所占有了,会继续检查占有线程是否为当前线程,如果是的话,同步状态加1返回true,表示可以再次获取成功。每次重新获取都会对同步状态进行加一的操作,那么释放的时候处理思路是怎样的了?(依然还是以非公平锁为例)核心方法为tryRelease:

protected final boolean tryRelease(int releases) {
    //1. 同步状态减1
    int c = getState() - releases;
    if (Thread.currentThread() != getExclusiveOwnerThread())
        throw new IllegalMonitorStateException();
    boolean free = false;
    if (c == 0) {
        //2. 只有当同步状态为0时,锁成功被释放,返回true
        free = true;
        setExclusiveOwnerThread(null);
    }
    // 3. 锁未被完全释放,返回false
    setState(c);
    return free;
}

代码的逻辑请看注释,需要注意的是,重入锁的释放必须得等到同步状态为0时锁才算成功释放,否则锁仍未释放。如果锁被获取n次,释放了n-1次,该锁未完全释放返回false,只有被释放n次才算成功释放,返回true。到现在我们可以理清ReentrantLock重入性的实现了,也就是理解了同步语义的第一条。

3、使用示例

首先重入锁用ReentrantLock来实现的,关于重入锁ReentrantLock的几个重要方法:
1、lock():获得锁,如果锁被占用,只能等待
2、lockInterruptibly():获得锁,可以响应中断
3、tryLock():尝试获得锁,如果成功返回true,失败返回false,该方法不等待,立即返回
4、tryLock(long time,TimeUnit unit):设置在限时尝试获得锁,其他同上
5、unlock():释放锁

import java.util.concurrent.locks.ReentrantLock;

public class ReenterLock implements Runnable {
    public static ReentrantLock lock = new ReentrantLock();
    public static int i = 0;

    @Override
    public void run() {
        for (int j = 0; j < 100000; j++) {
            lock.lock();
            try {
                i++;
            } finally {
                lock.unlock();
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
           ReenterLock lock=new ReenterLock();
           Thread t1=new Thread(lock);
           Thread t2=new Thread(lock);
           t1.start();
           t2.start();
           t1.join();
           t2.join();
           System.out.println(i);
    }
}

运行结果是200000,说明使用重入锁保护临界区资源i,确保多线程对i操作的安全性

中断响应

import java.util.concurrent.locks.ReentrantLock;

public class IntLock implements Runnable {
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    int lock;

    public IntLock(int lock) {
        this.lock = lock;
    }

    @Override
    public void run() {

        try {
            if (lock == 1) {
                lock1.lockInterruptibly();//可以响应中断的请求锁

                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                }
                lock2.lockInterruptibly();

            } else {
                lock2.lockInterruptibly();
                try {
                    Thread.sleep(500);
                } catch (Exception e) {
                }
                lock1.lockInterruptibly();
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (lock1.isHeldByCurrentThread())//检查是否还握着锁,如果有就解锁
                lock1.unlock();
            if (lock2.isHeldByCurrentThread())
                lock2.unlock();
            System.out.println(Thread.currentThread().getId() + ":线程退出");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        IntLock i1 = new IntLock(1);
        IntLock i2 = new IntLock(2);
        Thread t1 = new Thread(i1);
        Thread t2 = new Thread(i2);
        t1.start();
        t2.start();
        Thread.sleep(1000);
        // 中断其中一个线程
        t2.interrupt();
    }
}

这里写图片描述
这种情况是线程t1和t2启动后,t1先占用lock1,再占用lock2,t2先占用lock2,再请求lock1,因此很容易形成t1和t2之间的相互等待(t1占lock1,申请lock2,t2占lock2,申请lock1),lockInterruptibly()方法是可以对中断进行响应的锁请求动作,即等待过程中可以响应中断
t2.interrupt();当t2放弃对lock1的申请,同时释放lock2,这样使用t1获得lock2锁继续执行下去,所以t1完成了任务,t2放弃任务

锁申请等待限时

上面的中断是手动中断的,而我们使用tryLock()可以设置在一定时间内还没获得锁就返回false

public class TimeLock implements Runnable {

    public static ReentrantLock lock = new ReentrantLock();

    @Override
    public void run() {
        try {
            if (lock.tryLock(2, TimeUnit.SECONDS))// 时间值,时间单位
            {
                Thread.sleep(3000);
            } else {
                System.out.println("get lock failed");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (lock.isHeldByCurrentThread())
                lock.unlock();
        }
    }

    public static void main(String[] args) {
        TimeLock lock1=new TimeLock();

        Thread t1=new Thread(lock1);
        Thread t2=new Thread(lock1);
        t1.start();
        t2.start();

    }
}

在例子中一个线程占用锁3秒,而另一个线程设置在2秒如果无法获得锁就请求失败。所以另一个线程无法获得锁


参考《Java并发知识点总结》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值