欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

JUC--ReenTrantLock学习(一)简介与使用

程序员文章站 2024-03-24 19:29:10
...

1 概述

对java多线程编程有一定掌握的同学肯定知道synchronized和volatile关键字来实现对共享对象的加锁,针对着两个关键字不熟悉的同学,可以参考我的博文 Java高并发--synchronized关键字详解 Java高并发--volatile使用及实现原理。我们都知道这两个关键字加锁无法对所进行手动加锁和释放,并且容易造成死锁。那么有没有一种工具类能够实现手动加锁和释放呢?并且针对获取锁的操作我们可以在一定时间如果无响应就返回呢?

当然有,这就是我们接下来要提到的ReenTrantLock。

2 ReentrantLock实现简介

首先我们来看一下ReentrantLock的UML类图。

JUC--ReenTrantLock学习(一)简介与使用

上图我们仅仅给出了类之间的关系,没有给出类的具体内部实现。结合源码我们可以看出,ReentrantLock实现了Lock接口,并且其内部很多方法的实现都依靠了Sync。当然Sync的实现又依靠了AQS,也就是Sync就是AQS模板方法设计模式中的基本方法的实现者。

3 ReentrantLock使用

3.1 基本使用

针对基本使用其实就和synchronized关键字的使用效果相同,不同的是我们需要手动来获取锁和释放锁。

import java.util.concurrent.locks.ReentrantLock;

public class ReentrantLockTest {
    public static Integer i = 0;
    public static ReentrantLock reentrantLock = new ReentrantLock();

    public static class ReenterLock implements Runnable {

        @Override
        public void run() {

            //获取锁
            reentrantLock.lock();
            for (int j = 0; j < 10000000; j++) {
                i++;
            }

            //释放锁
            reentrantLock.unlock();
        }
    }

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

}

程序执行输出为20000。

在概述中我们提到了ReentrantLock与synchronized的不同之处有ReentrantLock可以中断响应,那么具体是怎么实现的呢?

3.1 中断响应

/**
 * show the use of ReentrantLock
 *
 * @author LIUTAO
 * @version 2017/10/2
 * @see
 */
public class ReentrantLockTest {
    /**
     * show the use of ReentrantLock interrupt
     */
    public static ReentrantLock lock1 = new ReentrantLock();
    public static ReentrantLock lock2 = new ReentrantLock();
    public static class IntLock implements Runnable{
        int lock; //用于控制加锁的顺序,便于构造死锁
        String name;
        public IntLock(int lock,String threadName){
            this.name = threadName;
            this.lock = lock;
        }
        @Override
        public void run() {
            try{
                if(lock == 1){
                    lock1.lockInterruptibly();
                    try{
                        Thread.sleep(500);
                        System.out.println("name:"+name);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    lock2.lockInterruptibly();
                }else{
                    lock2.lockInterruptibly();
                    try{
                        Thread.sleep(500);
                        System.out.println("name:"+name);
                    }catch (InterruptedException e){
                        e.printStackTrace();
                    }
                    lock1.lockInterruptibly();
                }
            }catch (InterruptedException e){
                e.printStackTrace();
            }finally {
                if(lock1.isHeldByCurrentThread()){
                    lock1.unlock();
                }

                if(lock2.isHeldByCurrentThread()){
                    lock2.unlock();
                }
                System.out.println(Thread.currentThread().getId()+",name:"+name+"线程退出!");
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        IntLock intLock1 = new IntLock(1,"thread1");
        IntLock intLock2 = new IntLock(2,"thread2");
        Thread thread1 = new Thread(intLock1);
        Thread thread2 = new Thread(intLock2);
        thread1.start();thread2.start();
        Thread.sleep(1000);
        thread2.interrupt();

    }

}

程序执行结果:

name:thread2
name:thread1
14,name:thread2线程退出!
java.lang.InterruptedException
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.doAcquireInterruptibly(AbstractQueuedSynchronizer.java:898)
	at java.util.concurrent.locks.AbstractQueuedSynchronizer.acquireInterruptibly(AbstractQueuedSynchronizer.java:1222)
	at java.util.concurrent.locks.ReentrantLock.lockInterruptibly(ReentrantLock.java:335)
	at com.liutao.concurrent.ReentrantLockTest$IntLock.run(ReentrantLockTest.java:46)
	at java.lang.Thread.run(Thread.java:748)
13,name:thread1线程退出!

我们可以看见并没有造成死锁,而是在没有获取到锁的情况下中断了线程等待。

3.1 锁申请等待

有些时候我们希望在获取锁的时候一段时间没有返回就直接放弃等待。这个时候就需要用到tryLock了。

import java.util.concurrent.locks.ReentrantLock;
import java.util.concurrent.TimeUnit;
/**
 * show the use of ReentrantLock
 *
 * @author LIUTAO
 * @version 2017/10/2
 * @see
 */
public class ReentrantLockTest {
    /**
     * show the use of limiting the time of wait
     */
    public static ReentrantLock lock3 = new ReentrantLock();

    public static class TimeLock implements Runnable {

        @Override
        public void run() {
            try {
                if (lock3.tryLock(5, TimeUnit.SECONDS)) {
                    Thread.sleep(6000);
                } else {
                    System.out.println("get lock failed!");
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
            } finally {
                if (lock3.isHeldByCurrentThread()) {
                    lock3.unlock();
                }
            }
        }
    }

    public static void main(String[] args) {

        TimeLock timeLock = new TimeLock();
        Thread thread1 = new Thread(timeLock);
        Thread thread2 = new Thread(timeLock);
        thread1.start();
        thread2.start();
    }

}

程序执行结果:

get lock failed!

我们可以看见两个线程同时去获取锁,在第一个线程获取到锁,并且持有锁6秒没有释放,而第二个线程等待五秒没有获取到锁就自动放弃了。

4 性能因素

当把ReentrantLock添加到jdk1.5的时候,它比内置锁提供了更好的竞争性能,所以对多线程编程来说更加地友好,但是随着后面jdk1.6将内置锁的算法进行改进,我们就可以发现内置锁和ReentrantLock的竞争性能基本上一致,所以也就没有多大差别了。

5 公平性

ReentrantLock提供了公平锁和非公平锁,这两者的主要区别如下:

这个概念是针对锁的获取的,在绝对时间上,先对锁进行获取的请求一定先满足,那么这个锁是公平的,反之就是不公平的。公平锁的获取就是等待时间最长的线程最先获取锁,也就是锁获取是顺序的。但是公平锁的机制往往效率不高。

但是非公平锁有可能导致:一直获取不到锁的现象:

饿死:一些客户端不能获得服务,而其他客户端却可以;违反了公平原则---服务器必须为所有的客户端提供公平的服务;

6 和synchronized比较

(1)可重入性

从名字上理解,ReenTrantLock的字面意思就是再进入的锁,其实synchronized关键字所使用的锁也是可重入的,两者关于这个的区别不大。两者都是同一个线程没进入一次,锁的计数器都自增1,所以要等到锁的计数器下降为0时才能释放锁。

(2)锁的实现

Synchronized是依赖于JVM实现的,而ReenTrantLock是JDK实现的,有什么区别,说白了就类似于操作系统来控制实现和用户自己敲代码实现的区别。前者的实现是比较难见到的,后者有直接的源码可供阅读。

(3)性能的区别

在Synchronized优化以前,synchronized的性能是比ReenTrantLock差很多的,但是自从Synchronized引入了偏向锁,轻量级锁(自旋锁)后,两者的性能就差不多了,在两种方法都可用的情况下,官方甚至建议使用synchronized,其实synchronized的优化我感觉就借鉴了ReenTrantLock中的CAS技术。都是试图在用户态就把加锁问题解决,避免进入内核态的线程阻塞。

(4)功能区别

便利性:很明显Synchronized的使用比较方便简洁,并且由编译器去保证锁的加锁和释放,而ReenTrantLock需要手工声明来加锁和释放锁,为了避免忘记手工释放锁造成死锁,所以最好在finally中声明释放锁。

锁的细粒度和灵活度:很明显ReenTrantLock优于Synchronized

(5)ReenTrantLock独有的能力:

  • ReenTrantLock可以指定是公平锁还是非公平锁。而synchronized只能是非公平锁。所谓的公平锁就是先等待的线程先获得锁。
  • ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
  • ReenTrantLock提供了一种能够中断等待锁的线程的机制,通过lock.lockInterruptibly()来实现这个机制。
  • ReenTrantLock提供了获取锁超时则返回的机制。
相关标签: ReentrantLock