java中Reentrantlock的基本使用


内容主要参考自《实战Java高并发程序设计》

Reentrantlock基本使用

Reentrantlock意为重入锁,可以算是对synchronized关键字的扩展,所以其作用也是用来保证线程间的同步,一个简单的示例如下:

public class ReentrantlockDemo implements Runnable {

    private static ReentrantLock reentrantLock = new ReentrantLock(); //创建ReentrantLock对象
    static int a = 0;

    public ReentrantlockDemo() {
    }

    @Override
    public void run() {
        reentrantLock.lock(); //加锁
        try {
            //每个线程对变量a进行100000次累计额
            for (int i = 0; i < 100000; i++) {
                a++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock(); //释放锁
        }

    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantlockDemo r = new ReentrantlockDemo();
        //创建两个线程,并打印输出a的结果
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("a = " + a);
    }
}

在run方法中,首先在要同步的位置调用lock()方法上锁,然后结尾调用unlock()方法释放锁,最后的效果等同于使用synchronized关键字。

为了做对比,将线程中同步的部分改为synchronized关键字的形式

public class ReentrantlockDemo implements Runnable {

    private static ReentrantLock reentrantLock = new ReentrantLock(); //创建ReentrantLock对象
    static int a = 0;

    public ReentrantlockDemo() {
    }

    @Override
    public void run() {
        synchronized (this){
            for (int i = 0; i < 100000; i++) {
                a++;
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ReentrantlockDemo r = new ReentrantlockDemo();
        //创建两个线程,并打印输出a的结果
        Thread t1 = new Thread(r);
        Thread t2 = new Thread(r);
        t1.start();
        t2.start();
        t1.join();
        t2.join();
        System.out.println("a = " + a);
    }
}

从两者的对比中可以看到,synchronized是通过关键字的形式来使用的,而Reentrantlock是通过对象的方式使用,加锁解锁都需要写在代码中,所以Reentrantlock会更灵活,使用也会复杂一些。

Reentrantlock注意事项

第一点是,一个加锁操作必须对应一个释放锁的操作,因为Reentrantlock是可重入锁,所以一个线程在加锁后可以再次持有同一把锁,这样就相当于加了两次锁,所以也要显示的释放两次锁

    @Override
    public void run() {
        reentrantLock.lock();
        reentrantLock.lock();
        try {
            a ++;
        }catch (Exception e){
            e.printStackTrace();
        }
        finally {
        	//释放两次锁
            reentrantLock.unlock();
            reentrantLock.unlock();
        }
    }

第二点是,释放锁的操作最好放到finally代码块中,因为Reentrantlock的加锁和释放锁操作都是要在代码中体现,这样就保证即使程序出现了异常,也能正确的释放锁。

Reentrantlock一些高级使用

设置锁等待时间

当一个线程迟迟得不到一把锁时,继续等待可能也是浪费时间。Reentrantlock可以给线程设置一个等待锁的时间,这样当超时还未获取锁时,线程就会放弃等待。
这里主要调用的是tryLock()方法
在这里插入图片描述
tryLock()的返回值为boolean,为true是线程获取到了锁,为false是指超过了等待时间线程还未获取锁。

tryLock()接收两个参数,第一个参数是等待时间,第二个参数是时间单位。无参的方法是将等待时间设置为0,也就是在线程获取不到锁时会立即返回false。

简单的示例如下

    @Override
    public void run() {
        try {
            //线程尝试获取锁
            if (!reentrantLock.tryLock(1l, TimeUnit.SECONDS)) {
                System.out.println("线程" + Thread.currentThread().getName() + "未获取锁");
                return;
            }
            //每个线程对变量a进行100000次累计额
            for (int i = 0; i < 100000; i++) {
                a++;
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放前先判断线程是否有锁
            if (reentrantLock.isHeldByCurrentThread()) {
                reentrantLock.unlock(); //释放锁
            }
        }
    }

公平锁

公平锁是指,锁会根据线程的申请先后顺序分配给线程,比如线程A先申请了锁,之后线程B也申请了锁,在公平锁下,锁会先被A持有,而在非公平锁下,A和B不一定谁会先持有锁。
要使用公平锁,只需要在Reentrantlock的构造方法中传递一个true参数即可
在这里插入图片描述
公平锁会维护一个线程申请的队列,所以会产生额外的开销,因此默认情况下锁都是非公平的,在没有特殊需求的情况下,使用默认的非公平锁即可。

Conditinon的使用

Condition对象是配合Reentrantlock使用的,其作用几乎等同于synchronized中常用的wait()方法和notify()、notifyAll()方法。
Condition中几个常用的方法
在这里插入图片描述

  • await():作用同wait()方法,让线程释放当前锁并进入等待状态,await()参数可以传递两个参数,分别是等待的时间和时间单位,无参表示无限等待。
  • signal():作用同notify()方法,随机唤醒一个处于等待状态的线程。
  • signalAll():作用同notifyAll()方法,唤醒所有处于等待状态的线程。

一个简单的示例如下:

public class ReentrantlockDemo implements Runnable {

    private static ReentrantLock reentrantLock = new ReentrantLock(true); //创建ReentrantLock对象
    private static Condition condition = reentrantLock.newCondition(); //通过Reentrantlock获取Condition对象
    int flag;

    public ReentrantlockDemo(int flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        System.out.println("线程" + Thread.currentThread().getName() + "进入");
        reentrantLock.lock();
        try {
            System.out.println("线程" + Thread.currentThread().getName() + "获取锁");
            //flag为1表示当前线程等待,为2表示唤醒等待的线程
            if(flag == 1){
                System.out.println("线程" + Thread.currentThread().getName() + "等待");
                condition.await();
                System.out.println("线程" + Thread.currentThread().getName() + "被唤醒");
            }
            else{
                System.out.println("线程" + Thread.currentThread().getName() + "唤醒等待线程");
                condition.signal();
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            reentrantLock.unlock();
            System.out.println("线程" + Thread.currentThread().getName() + "结束");
        }
    }

    public static void main(String[] args) throws InterruptedException {
        //这里虽然创建了两个Runnable实例,但锁是被static修饰的,所以两个实例使用的同一把锁
        ReentrantlockDemo r1 = new ReentrantlockDemo(1);
        ReentrantlockDemo r2 = new ReentrantlockDemo(2);
        Thread t1 = new Thread(r1,"thread1");
        Thread t2 = new Thread(r2,"thread2");
        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
}

输出结果
在这里插入图片描述
通过synchronized方式实现该程序

public class ReentrantlockDemo implements Runnable {

    private static ReentrantLock reentrantLock = new ReentrantLock(true); //创建ReentrantLock对象
    private static Condition condition = reentrantLock.newCondition(); //通过Reentrantlock获取Condition对象
    private static Object lock = new Object();
    int flag;

    public ReentrantlockDemo(int flag) {
        this.flag = flag;
    }

    @Override
    public void run() {
        System.out.println("线程" + Thread.currentThread().getName() + "进入");
        synchronized (lock){
            try {
                System.out.println("线程" + Thread.currentThread().getName() + "获取锁");
                //flag为1表示当前线程等待,为2表示唤醒等待的线程
                if(flag == 1){
                    System.out.println("线程" + Thread.currentThread().getName() + "等待");
                    lock.wait();
                    System.out.println("线程" + Thread.currentThread().getName() + "被唤醒");
                }
                else{
                    System.out.println("线程" + Thread.currentThread().getName() + "唤醒等待线程");
                    lock.notify();
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                System.out.println("线程" + Thread.currentThread().getName() + "结束");
            }
        }

    }

    public static void main(String[] args) throws InterruptedException {
        //这里虽然创建了两个Runnable实例,但锁是被static修饰的,所以两个实例使用的同一把锁
        ReentrantlockDemo r1 = new ReentrantlockDemo(1);
        ReentrantlockDemo r2 = new ReentrantlockDemo(2);
        Thread t1 = new Thread(r1,"thread1");
        Thread t2 = new Thread(r2,"thread2");
        t1.start();
        Thread.sleep(1000);
        t2.start();
    }
}

可以看到,通过Conditio实现等待和唤醒与通过Object实现方式没有太大区别,基本上是通用的逻辑。

这里要注意的是,在调用Condition的方法时,需要保证当前线程已经拿到对应的锁,否则会抛出异常

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值