synchronized 锁与 ReentrantLock 锁的区别

简介: synchronized 锁与 ReentrantLock 锁的区别

相关知识点参考:ConcurrentHashMap 可以使用 ReentrantLock 作为锁吗?

线程可以通过加 synchronized 锁或 ReentrantLock 锁两种方式对代码加锁,实现线程安全。他们的区别如下:

共同点:

(1)都保证了可见性和互斥性

(2)都是可重入锁,同一线程可以多次获得同一个锁

(3)都是用来协调多线程对共享对象、变量的访问

不同点:

(1)synchronized 是 Java 关键字,ReentrantLock 是 lock 的实现类。

(2)synchronized 适合锁少量的代码同步问题,Lock 适合锁大量的同步代码

(3)synchronized 在程序发生异常或执行完时会自动释放锁,ReentrantLock 必须要手动释放锁(用 unlock 方法),如果不释放就成了死锁,因此使用 lock 时需要在 finally 块中释放锁。

(4)底层实现不一样,synchronized 是同步阻塞,使用悲观并发策略;ReentrantLock 是同步非阻塞,采用乐观并发策略,所以可以提高多个线程进行读操作的效率。

(5)synchronized 可以把任意一个非 NULL 的对象当做锁,它属于独占式的悲观锁、非公平锁,同时也属于可重入锁。ReentrantLock 继承接口 Lock 并实现了接口中定义的方法,它也是一种可重入锁,同时可以将其设置为公平锁。

(6)synchronized 无法判断获取锁的状态,ReentrantLock 可以判断线程是否获取到了锁,并且可以对获取锁的等待时间进行设置,避免死锁,同时可以获取各种锁的信息以及可以灵活地实现多路通知。

 

ReentrantLock 公平性的设置:

ReentrantLock fairLock = new ReentrantLock(true),参数为 true 时,表明设置为公平锁,会倾向于将锁赋予等待时间最久的线程。

公平锁:获取锁的顺序按先后调用 lock()方法的顺序(慎用),也就是排队一样。

非公平锁:抢占的顺序不一定,随机的。

注意:一般没有强需求按顺序执行的情况下,采用非公平锁好,因为 Java 的 JVM 设置很少情况下才会出现一个线程等待很久没有被分配到资源的情况。然后采用了公平锁的话,会造成一定的性能消耗,导致吞吐量下降。

synchronized 加锁实现方式:

关于同步方法的总结:

  1. 同步方法仍然涉及到同步监视器,只是不需要我们显式的声明
  2. 非静态的同步方法,其同步监视器是:this(当前类的对象)

静态同步方法:同步监视器是:当前类本身(类.class ( ))

class Ticket{
    private int num=100;//总共100张票
    public synchronized void sell() throws InterruptedException {
        //此时用synchronized修饰方法sell(),其同步监视器是:this(当前类的对象),由于使用的是
        while(num>0){
            System.out.println(Thread.currentThread().getName()+"卖票,票号为:"+(num--));
            //为了创造一些异常,让线程到此处sleep阻塞一下
            TimeUnit.MILLISECONDS.sleep(10);
        }
    }
}
class Ticket{
    private int num=100;//总共100张票
    public void sell() throws InterruptedException {
        synchronized (this){
            //此时使用synchronized修饰代码块
            while (num > 0) {
                System.out.println(Thread.currentThread().getName() + "卖票,票号为:" + (num--));
                //为了创造一些异常,让线程到此处sleep阻塞一下1
                TimeUnit.MILLISECONDS.sleep(10);
            }
        }
    }
}

ReentrantLock 加锁实现方式:

public void buy() {
        lock.lock();
        try {
            while (num==0){
                //此时没有商品,需要等待
                notNull.await();
            }
            //不为0则可以进行操作
            num--;
            System.out.println(Thread.currentThread().getName()+"消费商品,当前剩余商品:"+num);
           notNull.signalAll();//通知其他线程可以生产商品
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }
相关文章
npm 切换镜像后,npm i 安装依然卡,需要好久才完成
npm 切换镜像后,npm i 安装依然卡,需要好久才完成
1052 0
|
存储 SQL 分布式计算
Lakehouse架构指南
Lakehouse架构指南
341 2
|
前端开发 JavaScript API
React 之 Suspense
React 之 Suspense
375 0
Glide Caused by: javax.net.ssl.SSLPeerUnverifiedException: Hostname not verified:
Glide Caused by: javax.net.ssl.SSLPeerUnverifiedException: Hostname not verified:
1213 0
|
10月前
|
存储 算法
非递归实现后序遍历时,如何避免栈溢出?
后序遍历的递归实现和非递归实现各有优缺点,在实际应用中需要根据具体的问题需求、二叉树的特点以及性能和空间的限制等因素来选择合适的实现方式。
210 59
|
数据采集 机器学习/深度学习 人工智能
机器视觉:原理、应用与实现
机器视觉:原理、应用与实现
|
Prometheus 监控 Kubernetes
Prometheus 在微服务架构中的应用
【8月更文第29天】随着微服务架构的普及,监控和跟踪各个服务的状态变得尤为重要。Prometheus 是一个开源的监控系统和时间序列数据库,非常适合用于微服务架构中的监控。本文将详细介绍 Prometheus 如何支持微服务架构下的监控需求,包括服务发现、服务间的监控指标收集以及如何配置 Prometheus 来适应这些需求。
391 1
|
12月前
|
Go 开发工具 git
在Qemu+ARM上运行Minix3内核
在Qemu+ARM上运行Minix3内核
|
数据采集 监控 数据挖掘
打造高效用户旅程:埋点分析系统的实操指南
在数字化时代,了解用户如何与我们的产品或服务互动是至关重要的。用户行为,在广义上,指的是用户在网站、应用程序或其他数字界面上的所有动作和反应。这些行为可能包括点击链接、浏览页面、填写表单,甚至是在社交媒体上分享内容。每一个动作都是用户体验的一部分,并对我们理解他们的需求和偏好提供了宝贵的线索。 在技术层面上,用户行为的跟踪和分析可以让我们深入了解用户的互动模式,从而指导我们的产品改进和市场战略。通过分析这些数据,我们可以发现用户旅程中的关键触点,识别用户体验的痛点,以及揭示潜在的优化机会。这不仅有助于提升用户满意度和忠诚度,还可以增强产品的市场竞争力。
打造高效用户旅程:埋点分析系统的实操指南
|
运维 Ubuntu 网络协议
Ubuntu系统下修改网卡IP地址
【7月更文挑战第3天】Ubuntu系统下修改网卡IP地址
1128 1