• synchronized和lock


    Synchronized的实现原理

    两个重要的概念:一个是对象头,另一个是monitor。
    Java 虚拟机中的同步(Synchronization)基于进入和退出Monitor对象实现
    monitorenter指令插入到同步代码块的开始位置,monitorexit指令插入到同步代码块的结束位置,
    JVM需要保证每一个monitorenter都有一个monitorexit与之相对应。
    任何对象都有一个monitor与之相关联,当且一个monitor被持有之后,他将处于锁定状态。
    线程执行到monitorenter指令时,将会尝试获取对象所对应的monitor所有权,即尝试获取对象的锁

    ReenTrantLock实现的原理:

    两个比较重要的概念

    第一个:状态变量,用于存储当前锁的状态,0为未被占用,1为被占用,2,3,4等均为被占用,且被同一线程重入了多次
    第二个:双线链表,用于存储排队获取锁的线程,只有队列中第一个线程被唤醒后,才有资格获取锁

    (1)获取锁

    通过CAS(乐观锁)去尝试修改状态变量的值(从0修改到1)
    若修改成功,即获得锁,并执行锁内程序
    若修改失败,将当前线程通过CAS加入上面锁的双向链表(等待队列)的尾部,并通过自旋,持续判断当前队列节点是否可以获取锁
    

    (2)释放锁

    1. 释放锁的线程将状态变量的值从1设置为0,并唤醒等待(锁)队列中的队首节点
    2. 被唤醒的线程(队列中的队首节点)和未进入队列并且准备获取的线程竞争获取锁,被唤醒的线程若竞争到了锁,则则将其出列,将下一个节点变成队列的头结点
    

    synchronized和lock的区别

    (1)Lock是一个接口,而synchronized是Java中的关键字。(设计层次)
    (2)Lock可以让等待锁的线程响应中断,线程可以中断去干别的事务,而synchronized却不行,使用synchronized时,等待的线程会一直等待下去。(锁的获取)
    (3)synchronized在发生异常时,会自动释放线程占有的锁,因此不会导致死锁现象发生。
    而Lock在发生异常时,不会自动是放占有的锁,如果没有主动通过unLock()去释放锁,则很可能造成死锁现象,因此使用Lock时需要在finally块中释放锁。(锁的释放)
    (4)通过Lock可以知道有没有成功获取锁,而synchronized却无法办到。(锁状态)
    (5)synchronized 可重入、不可中断、非公平;Lock 可重入、可中断、可公平可非公平(锁类型)
    (6)Lock可以提高多个线程进行读操作的效率。(性能)
    (7)ReenTrantLock提供了一个Condition(条件)类,用来实现分组唤醒需要唤醒的线程们,而不是像synchronized要么随机唤醒一个线程要么唤醒全部线程

    有了Synchronized为什么需要Lock

    线程进入同步代码块时,如果锁被其他线程占用,该线程只能等待
    Lock提供了这三种方式来弥补Synchronized的不足,使得我们能写出更加安全,健壮的代码。
    boolean tryLock();
    boolean tryLock(long time, TimeUnit unit) throws InterrptedException;
    void lockInterruptibly() throws InterruptedException;

    ReentrantLock获取锁的方式:

    (1) lock(), 如果获取了锁立即返回,如果别的线程持有锁,当前线程则一直处于休眠状态,直到获取锁
    (2)tryLock(), 如果获取了锁立即返回true,如果别的线程正持有锁,立即返回false;
    (3)tryLock(long timeout,TimeUnit unit), 如果获取了锁定立即返回true,如果别的线程正持有锁,会等待参数给定的时间,在等待的过程中,如果获取了锁定,就返回true,如果等待超时,返回false;
    (4)lockInterruptibly:如果获取了锁定立即返回,如果没有获取锁定,当前线程处于休眠状态,直到或者锁定,或者当前线程被别的线程中断

    为什么需要读写锁

    ReentrantLock是一个互斥锁,保证在同一时刻只有一个线程获取资源,无论是写写,读读,读写任何场景都是互斥的。
    这种互斥锁在常见的高并发场景下会显现出糟糕的低吞吐量问题。

    读写锁的作用

    一个资源可以被多个读操作同时访问,或者被一个写操作独占。但是两者不能同时访问。

    效率问题

    当有多个线程读写文件时,读操作和写操作会发生冲突现象,写操作和写操作会发生冲突现象,但是读操作和读操作不会发生冲突现象。
    Lock可以使得多个线程都只是进行读操作时,线程之间不会发生冲突
    但是采用synchronized关键字来实现同步的话,当一个线程在进行读操作时,其他线程只能等待无法进行读操作。

    ReadWriteLock也是一个接口,在它里面只定义了两个方法,readLock()和writeLock()
    一个用来获取读锁,一个用来获取写锁。也就是说将文件的读写操作分开,分成2个锁来分配给线程,从而使得多个线程可以同时进行读操作
    ReentrantReadWriteLock实现了ReadWriteLock接口

    ReentrantReadWriteLock是Lock的另一种实现方式,我们已经知道了ReentrantLock是一个排他锁,同一时间只允许一个线程访问,
    而ReentrantReadWriteLock允许多个读线程同时访问,但不允许写线程和读线程、写线程和写线程同时访问。相对于排他锁,提高了并发性。
    在实际应用中,大部分情况下对共享数据(如缓存)的访问都是读操作远多于写操作,这时ReentrantReadWriteLock能够提供比排他锁更好的并发性和吞吐量。

    Lock原理总结总结

    lock的存储结构:一个int类型状态值(用于锁的状态变更),一个双向链表(用于存储等待中的线程)
    lock获取锁的过程:本质上是通过CAS来获取状态值修改,如果当场没获取到,会将该线程放在线程等待链表中。
    lock释放锁的过程:修改状态值,调整等待链表。

    为了便于理解,附上两张图

  • 相关阅读:
    find module providing package github.com/go-sql-driver/mysql: working directory is not part of a module
    深度学习中的epoch、batchsize、iterations的理解
    淘宝软件质量属性分析
    Git
    多线程
    Spark基础之Scala
    机器学习十讲第十讲
    机器学习十讲第九讲
    机器学习十讲第六讲
    本地MarkDown优雅发表
  • 原文地址:https://www.cnblogs.com/jis121/p/11037370.html
Copyright © 2020-2023  润新知