Java中ReentrantLock重入锁与synchronized同步锁区别详解

ReentrantLock实现类(Lock接口)详解:【Java】Lock锁接口和实现类详解
synchronized关键字线程同步详解:【Java】线程的基本同步方式和常用方法

1. 两者优劣特点对比(详细)

比较点 sychronized关键字 ReentrantLock实现类(Lock接口)
构成 它是java语言的关键字,是原生语法层面的互斥,需要jvm实现 它是JDK 1.5之后提供的API层面的互斥锁类
实现 通过JVM获取锁/释放锁 api层面的获取锁释放锁,释放锁需要手动
代码 采用synchronized不需要手动释放锁,当synchronized方法或者synchronized代码块执行完之后,系统会自动让线程释放对锁的占用,更安全 ReentrantLock则必须要手动释放锁,如果没有主动释放锁,就有可能导致出现死锁,需要lock()和unlock()方法配合try-finally语句块完成
灵活 锁的范围是整个方法或synchronized代码块部分 使用Lock接口的方法调用,可以跨方法,灵活性更强大
中断 不可中断,除非抛出异常(释放锁方式:
①代码执行完,正常释放锁;
②抛出异常,由JVM退出等待)
可中断,持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待(方法:
①设置超时方法 tryLock(long timeout, TimeUnit unit)时间过了就放弃等待;
②lockInterruptibly()放代码块中,调用interrupt()方法可中断)
公平 非公平锁,不考虑排队问题直接尝试获取锁 公平锁和非公平锁两者都可以,默认非公平锁,检查是否有排队等待的线程,先来者先得锁,构造器可以传入boolean值,true为公平锁,false为非公平锁
条件 通过多次newCondition可以获得多个Condition对象,可以简单的实现比较复杂的线程同步的功能
高级 提供很多方法用来监听当前锁的信息,如:
getHoldCount()
getQueueLength()
isFair()
isHeldByCurrentThread()
isLocked()
便利 方便简洁,由编译器去保证锁的加锁和释放 需要手工声明来加锁和释放锁
场景 资源竞争不是很激烈的情况下,偶尔会有同步的情形下,synchronized是很合适的。原因在于,编译程序通常会尽可能的进行优化synchronized,另外可读性非常好 提供了多样化的同步,比如有时间限制的同步,可以被Interrupt的同步等
性能 低并发优先,性能好,可读性好 高并发优先,性能最佳

2. ReentrantLock 的3个高级功能

由于ReentrantLock是java.util.concurrent包下提供的一套互斥锁,相比Synchronized,ReentrantLock类提供了3个高级功能(表格中也有锁体现)。

功能①:等待可中断

持有锁的线程长期不释放的时候,正在等待的线程可以选择放弃等待,这相当于Synchronized来说可以避免出现死锁的情况。通过lock.lockInterruptibly()来实现这个机制。

三个API说明:
1)lock(), 拿不到lock就不罢休,不然线程就一直block,死等。
2)tryLock(),马上返回,拿到lock就返回true,不然返回false,不等。带时间限制的tryLock(),拿不到lock,就等一段时间,超时返回false。
3)lockInterruptibly():

  1. 线程在sleep或wait,join此时如果别的进程调用此进程的 interrupt()方法,此线程会被唤醒并被要求处理InterruptedException;
  2. 此线程在运行中,则不会收到提醒。但是此线程的 “打扰标志”会被设置, 可以通过isInterrupted()查看并作出处理。线程在请求lock并被阻塞时,如果被interrupt,则“此线程会被唤醒并被要求处理InterruptedException”。并且如果线程已经被interrupt,再使用lockInterruptibly的时候,此线程也会被要求处理interruptedException。

代码示例:

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

public class TestInterrupt {
    public static void main(String[] args) throws Exception {
        new TestInterrupt().test();
    }

    public void test() throws Exception {
        final Lock lock = new ReentrantLock();
        lock.lock();

        Thread t1 = new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    int i = 5;
                    while (i-- > 0) {
                        System.out.println("I'm running " + i);
                        Thread.sleep(1000);
                    }
                    // 相当于一个可被中断的设置,会抛出中断异常(受查异常)
                    lock.lockInterruptibly();
                } catch (InterruptedException e) {
                    System.out.println(Thread.currentThread().getName() + " interrupted(我被中断了).");
                    System.out.println("被打断后所做的事情...");
                }
            }
        }, "子线程-1");

        t1.start();
        Thread.sleep(3000); // 2秒后打断t1线程,t1线程会中断执行
        t1.interrupt(); // 打断:触发t1线程任务run中的中断异常(抛出),进入执行异常代码块
    }
}
释义:线程中断(interrupt)

中断一个线程,其本意是给这个线程一个通知信号,会影响这个线程内部的一个中断标识位。这个线程本身并不会因此而改变状态(如阻塞,终止等)

  1. 调用 interrupt()方法并不会中断一个正在运行的线程。也就是说处于 Running 状态的线程并不会因为被中断而被终止,仅仅改变了内部维护的中断标识位而已。
  2. 若调用 sleep()而使线程处于 TIMED-WATING 状态,这时调用 interrupt()方法,会抛出InterruptedException,从而使线程提前结束 TIMED-WATING 状态
  3. 许多声明抛出 InterruptedException 的方法(如 Thread.sleep(long mills 方法)),抛出异常前,都会清除中断标识位,所以抛出异常后,调用 isInterrupted()方法将会返回 false。
  4. 中断状态是线程固有的一个标识位,可以通过此标识位安全的终止线程。比如,你想终止一个线程 thread 的时候,可以调用 thread.interrupt()方法,在线程的 run 方法内部可以根据 thread.isInterrupted()的值来优雅的终止线程。

功能②:公平锁机制

多个线程等待同一个锁时,必须按照申请锁的时间顺序获得锁,Synchronized锁非公平锁,ReentrantLock默认的构造函数是创建的非公平锁,可以通过参数true设为公平锁,但公平锁表现的性能不是很好。

公平锁:加锁前检查是否有排队等待的线程,优先排队等待的线程,先来先得;
非公平锁:加锁时不考虑排队等待问题,直接尝试获取锁,获取不到自动到队尾等待。

公平锁、非公平锁的创建方式:

//创建一个非公平锁,默认是非公平锁
Lock lock = new ReentrantLock();
Lock lock = new ReentrantLock(false);
 
//创建一个公平锁,构造传参true
Lock lock = new ReentrantLock(true);

功能③:锁绑定多个条件

一个ReentrantLock对象可以同时绑定对个对象。ReentrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程。
代码示例:

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

public class TestCondition {
    public static void main(String[] args) throws InterruptedException {
        MyService service = new MyService();
        Thread t1 = new Thread(new MyServiceThread1(service), "a");
        Thread t2 = new Thread(new MyServiceThread2(service), "b");
        
        t1.start();
        t2.start();

        // 线程sleep2秒钟
        Thread.sleep(2000);
        // 唤醒所有持有conditionA的线程
        service.signallA();

        Thread.sleep(3000);
        // 唤醒所有持有conditionB的线程
        service.signallB();
    }
}

// MyServiceThread1 使用了awaitA()方法,持有的是conditionA!
class MyServiceThread1 implements Runnable {
    private MyService service;
    public MyServiceThread1(MyService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.awaitA();
    }
}

// MyServiceThread2 使用了awaitB()方法,持有的是conditionB!
class MyServiceThread2 implements Runnable {
    private MyService service;
    public MyServiceThread2(MyService service) {
        this.service = service;
    }
    @Override
    public void run() {
        service.awaitB();
    }
}

// 主要的功能代码
class MyService {
    // 实例化一个ReentrantLock对象
    private ReentrantLock lock = new ReentrantLock();
    // 为线程A注册一个Condition
    public Condition conditionA = lock.newCondition();
    // 为线程B注册一个Condition
    public Condition conditionB = lock.newCondition();

    public void awaitA() {
        try {
            lock.lock();
            
            System.out.println(Thread.currentThread().getName() + "进入了awaitA方法");
            long timeBefore = System.currentTimeMillis();
            conditionA.await(); // 执行conditionA等待
            long timeAfter = System.currentTimeMillis();
            
            System.out.println(Thread.currentThread().getName() + "被唤醒");
            System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore) / 1000 + "s");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void awaitB() {
        try {
            lock.lock();
            
            System.out.println(Thread.currentThread().getName() + "进入了awaitB方法");
            long timeBefore = System.currentTimeMillis();
            conditionB.await(); // 执行conditionB等待
            long timeAfter = System.currentTimeMillis();
            
            System.out.println(Thread.currentThread().getName() + "被唤醒");
            System.out.println(Thread.currentThread().getName() + "等待了: " + (timeAfter - timeBefore) / 1000 + "s");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public void signallA() {
        try {
            lock.lock();
            System.out.println("启动唤醒程序");
            // 唤醒所有注册conditionA的线程
            conditionA.signalAll();
        } finally {
            lock.unlock();
        }
    }

    public void signallB() {
        try {
            lock.lock();
            System.out.println("启动唤醒程序");
            // 唤醒所有注册conditionB的线程
            conditionB.signalAll();
        } finally {
            lock.unlock();
        }
    }
}
Java-ReentrantLock重入锁
补充:Condition 类和 Object 类锁方法区别
  1. Condition 类的 awiat 方法和 Object 类的 wait 方法等效;
  2. Condition 类的 signal 方法和 Object 类的 notify 方法等效;
  3. Condition 类的 signalAll 方法和 Object 类的 notifyAll 方法等效;
  4. ReentrantLock 类可以唤醒指定条件的线程,而 Object 的唤醒是随机的
补充:trylock 和 lock 和 lockInterruptibly 方法区别
  1. tryLock 能获得锁就返回 true,不能就立即返回 false,tryLock(long timeout,TimeUnit unit),可以增加时间限制,如果超过该时间段还没获得锁,返回 false
  2. lock 能获得锁就返回 true,不能的话一直等待获得锁;
  3. lock 和 lockInterruptibly,如果两个线程分别执行这两个方法,但此时中断这两个线程,lock 不会抛出异常,而 lockInterruptibly 会抛出异常

3. 什么时候使用ReentrantLock?

答案是:

  1. 原子操作的颗粒度更小的加锁操作或跨方法释放锁时(灵活);
  2. 如果你需要使用ReentrantLock的三个高级功能时。
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 204,732评论 6 478
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 87,496评论 2 381
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 151,264评论 0 338
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,807评论 1 277
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,806评论 5 368
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,675评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,029评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,683评论 0 258
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 41,704评论 1 299
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,666评论 2 321
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,773评论 1 332
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,413评论 4 321
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,016评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,978评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,204评论 1 260
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,083评论 2 350
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,503评论 2 343

推荐阅读更多精彩内容

  • 姓名:周玉霞 六项精进:327期反省二组 公司:浙江意威服饰 【日精进打卡第405天】 【知~学习】 《六项精进》...
    Anne玉阅读 147评论 0 0
  • 亲爱的煊煊,小元宝: 展信佳!见字如面! 今天妈妈想和你们聊聊《幸运的坏男孩》这本绘本,这是妈...
    追梦的艳玲阅读 145评论 0 0
  • 女朋友下车之后说太晒,王二二赶紧捂住了女朋友的眼睛。
    思路Duang阅读 175评论 0 0
  • 前段时间,我的一个朋友跟相恋多年的男朋友分手了,分手的过程非常平静,没有争吵。两个人是因为房子分手的,中途也没有未...
    蜻蜓屋阅读 38评论 0 0