Java的可重入锁

1.可重入锁Lock

锁主要是为了保护临界区资源,防止由于并发执行而引起的数据异常。而synchronized关键字就是一种最简单的控制方法。经过jdk1.6对synchronized的优化,Lock和synchronized的性能相差并不多。 那么为什么还需要Lock,这当然有它的用处,
先看一个示例,锁的普通情况的使用:

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockDemo implements Runnable {
    static int i = 0; //声明为静态变量,否则无法直接在main方法中调用
    ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        for (int j = 0; j < 10000; j++) {
            lock.lock(); //进行同步控制
            try {
                ++i;
            } finally {
                lock.unlock(); //加锁部分写进try finally语句中,保证锁一定会被释放掉
            }
        }
    }

    public static void main(String[] args) {
        ReentrantLockDemo lockDemo = new ReentrantLockDemo();

        Thread t1 = new Thread(lockDemo);
        Thread t2 = new Thread(lockDemo);
        t1.start();
        t2.start();
        try {
            t1.join(); //等待t1线程和t2线程执行完毕
            t2.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(i);
    }
}

Lock额外提供可几个以下的功能:
1)可重入
之所以把Lock称作可重入锁,是因为这把锁是可以反复进入的,当然这里反复进入仅仅局限于一个线程。上述代码的加锁部分,也可以加两把锁,如下:

lock.lock();
lock.lock();
try {
    ++i;
} finally {
    lock.unlock();
    lock.unlock();
}

注意:如果同一个线程多次获得锁,那么也必须释放相同次数的锁;如果释放次数多,那么会得到一个java.lang.IllegalMonitorStateException异常;如果释放次数少,那么其它线程将不能进入临界区。
2)中断响应
对于synchronized来说,如果一个线程在等待锁,那么结果只有两种情况,要么它获得锁继续执行,要么就等待。而使用重入锁,那么锁可以被中断,即在等待过程中,程序可以根据需要取消对锁的请求。

import java.util.concurrent.locks.ReentrantLock;

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

    @Override
    public void run() {
        //这里的if else主要是为了模拟死锁操作,这样可以看到通过调用中断方法,一个线程被中断,而另一个线程正常执行
        try {
            if (lock == 1){
                lock1.lockInterruptibly(); //调用这个方法加锁,同时可以响应中断
                Thread.sleep(500);
                lock2.lockInterruptibly();
                System.out.println(Thread.currentThread().getId() + ": 完成");
            }else {
                lock2.lockInterruptibly();
                Thread.sleep(500);
                lock1.lockInterruptibly();
                System.out.println(Thread.currentThread().getId() + ": 完成");
            }
        } catch (InterruptedException 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) {
        IntLock intLock1 = new IntLock(1);
        IntLock intLock2 = new IntLock(2);
        Thread t1 = new Thread(intLock1);
        Thread t2 = new Thread(intLock2);
        t1.start();
        t2.start();
        t1.interrupt(); //中断线程1
    }
}

3)锁申请等待限时

import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.ReentrantLock;

public class TryLockDemo implements Runnable{
    ReentrantLock lock = new ReentrantLock();
    @Override
    public void run() {
        try {
            if(lock.tryLock(5, TimeUnit.SECONDS)){ //锁申请等待限时,等待5秒钟
                Thread.sleep(6000);
                System.out.println(Thread.currentThread().getId() + "complete!");
            }else {
                System.out.println(Thread.currentThread().getId() + "get lock failed");
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            if(lock.isHeldByCurrentThread()) //如果当前线程持有该锁,则释放锁
                lock.unlock();
        }
    }

    public static void main(String[] args) {
        TryLockDemo tryLockDemo = new TryLockDemo();
        Thread t1 = new Thread(tryLockDemo);
        Thread t2 = new Thread(tryLockDemo);

        t1.start();
        t2.start();
    }
}

tryLock()也可以不带参数直接运行,在这种情况下,如果申请不成功,则直接返回false,不会等待。
4)总结
public void lock():获得锁,如果锁已经被占用,则等待;
public void lockInterruptibly():获得锁,但优先响应中断;
public boolean tryLock():尝试获得锁,如果成功返回true,继续执行;如果失败,返回false,不等待;
boolean tryLock(long timeout, TimeUnit unit):锁申请等待限时;
public void unlock():释放锁;

2.线程通信 (Condition)

Condition提供了一下几个方法:

void await() throws InterruptedException;
void awaitUninterruptibly();
boolean await(long time, TimeUnit unit) throws InterruptedException;
void signal();
void signalAll();

其和Object.wait()、Object.notify()方法作用类似;
await()方法使当前线程等待,同时释放锁;在其它线程中调用signal()方法或者signalAll()方法,线程会被唤醒,获得锁继续执行。当线程被中断时,也能跳出等待;
awaitUninterruptibly()作用和await()方法类似,但它不响应中断;
signal()方法用于唤醒线程。
示例如下:

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class ConditionDemo implements Runnable {

    private static Lock lock = new ReentrantLock(); //把变量声明为静态变量,这样可以直接在main方法中使用
    private static Condition condition =  lock.newCondition();

    @Override
    public void run() {
        try {
            lock.lock();
            condition.await();
            System.out.println("执行完毕");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }finally {
            lock.unlock();
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ConditionDemo conditionDemo = new ConditionDemo();
        Thread t1 = new Thread(conditionDemo);
        t1.start();
        Thread.sleep(2000);
        //通知线程t1继续执行
        lock.lock(); //和Object的notify方法同理,这里也需要先获得重入锁,才能执行signal()方法
        condition.signal();
        lock.unlock(); //在唤醒线程之后,需要释放锁;如果省略这行代码,那么就算t1被唤醒,但由于它无法获得重入锁,
        //因而就无法继续执行。
    }
}

一旦线程被唤醒,它会重新尝试获得与其绑定的重入锁,如果成功获取就继续执行。

### Java 可重入(ReentrantLock)的原理与机制 #### 1. **什么是可重入** 可重入是一种允许同一线程多次获取同一把而不发生死的同步机制。在 Java 中,`ReentrantLock` 是一种显式的实现,提供了比 `synchronized` 更加灵活的功能[^3]。 #### 2. **ReentrantLock 的基本特性** - **可重入性**:支持同一个线程对的重复获取,每次获取都会增加内部计数器的值。释放时会减少该计数值,直到计数为零时才真正解。 - **公平性和非公平性**:可以通过构造函数指定的行为模式。默认是非公平,即不保证等待时间最长的线程优先获取;也可以设置为公平,按照 FIFO 原则分配[^3]。 #### 3. **ReentrantLock 的底层实现** `ReentrantLock` 底层依赖于 AQS(AbstractQueuedSynchronizer),这是一个用来构建和其他同步组件的基础框架。AQS 使用了一个基于 CLH 队列的算法来管理线程排队和唤醒逻辑[^1]。 ##### (1)状态变量 AQS 维护了一个名为 `state` 的整型变量,表示当前的状态。对于 `ReentrantLock` 来说: - 当前持有的线程每调用一次 `lock()` 方法,`state` 就会自增; - 调用 `unlock()` 方法时,`state` 自减; - 如果 `state` 减到 0,则表明已被完全释放[^1]。 ##### (2)独占的核心操作 - 获取 (`acquire`):当一个线程尝试获取时,如果未被占用或者当前线程已经持有了这把,则直接成功并更新 `state` 计数器。否则,线程会被加入队列并挂起,直到轮到它为止。 - 释放 (`release`):只有拥有的线程才能执行此操作。通过减少 `state` 计数器的方式逐步释放,当计数降为 0 时通知其他等待中的线程重新竞争[^1]。 #### 4. **代码示例** 以下是使用 `ReentrantLock` 的简单例子: ```java import java.util.concurrent.locks.ReentrantLock; public class Counter { private int count = 0; private final ReentrantLock lock = new ReentrantLock(); public void increment() { lock.lock(); // 加 try { count++; } finally { lock.unlock(); // 解 } } public int getCount() { return count; } } ``` 上述代码展示了如何利用 `ReentrantLock` 对共享资源进行保护,防止多个线程同时修改数据而导致一致性问题[^2]。 #### 5. **与其他的区别** 相比传统的 `synchronized` 关键字,`ReentrantLock` 提供了更多的灵活性,比如超时获取、中断响应以及读写分离等功能。然而需要注意的是,在性能上两者差异不大,因此除非确实需要用到这些高级特性,否则推荐继续使用简单的 `synchronized` 方式简化开发过程。 --- ### 相关问题
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值