美文网首页
3.java并发机制及实现原理(2)

3.java并发机制及实现原理(2)

作者: 农民工进城 | 来源:发表于2021-02-19 10:19 被阅读0次

    说到java并发编程,就不得不说synchronized关键字,很多人称之为“重量级锁”,其实JAVASE 1.6对ynchronized进行的各种优化,synchronized并不会显得那么重了

    本章要点

    1.锁升级
    2.synchronized
    3.Lock

    1. AbstractQueuedSynchronizer

    java中任何一个对象都可以作为锁

    • synchronized修饰实例方法,锁是当前实例对象
    • synchronized修饰静态方法,锁是当前类的class对象
    • synchronized修饰代码块,锁是括号里面的对象

    3.1锁升级

    锁级别分为:无锁-->偏向锁-->轻量级锁-->重量级锁(真正意义上的加锁)

    • 偏向锁(CAS操作):大多数时候是不存在锁竞争的,通常是一个线程多次获得同一个锁,因此如果每次都要竞争锁会增大很多没有必要付出的代价,为了降低获取锁的代价,才引入的偏向锁。此时会在java对象头和栈帧中记录偏向的锁的threadID。
    • 轻量级锁(自旋锁):考虑的是竞争锁对象的线程不多,而且线程持有锁的时间也不长的情景(两个线程交替访问)。因为阻塞线程需要CPU从用户态转到内核态,代价较大,如果刚刚阻塞不久这个锁就被释放了,那这个代价就有点得不偿失了,因此这个时候就干脆不阻塞这个线程,让它自旋这等待锁释放。
    • 重量级锁(基于ObjectMonitor):多个线程竞争资源,竞争激烈,在操作一定次数的自旋锁(cas操作)后,还没有获得轻量级锁,锁膨胀为重量级锁,没有获取锁的线程会被阻塞。

    偏向锁:


    偏向锁.png

    轻量级锁:


    轻量级锁.png

    3.2 synchronized 基本原理

    synchronized是基于ObjectMonitor实现的,ObjectMonitor中有几个关键属性:
    _owner:指向持有ObjectMonitor对象的线程
    _WaitSet:存放处于wait状态的线程队列
    _EntryList:存放处于等待锁block状态的线程队列
    _recursions:锁的重入次数
    _count:用来记录该线程获取锁的次数
    1.只有_EntryList线列才有权利争抢锁资源 2.调研notify或者notifyAll方法,其实就是将线程从_WaitSet移到_EntryList中,然后让其去争抢锁资源
    总之,被synchronized修饰的代码,在被编译器编译后在被修饰的代码前后加上了一组字节指令。代码开始位置加上monitorenter,在代码结束位置加入monitorexit,这两个字节码指令配合完成了synchronized关键字修饰代码的互斥访问。
    当执行monitorenter时,若对象未被锁定时,或者当前线程已经拥有了此对象的monitor锁,则锁计数器+1,该线程获取该对象锁。
    当执行monitorexit时,锁计数器-1,当计数器为0时,此对象锁就被释放了。那么其他阻塞的线程则可以请求获取该monitor锁。

    3.3 Lock

    Lock接口中包含的方法:

    • 那获取锁:lock()、tryLock()、tryLock(long time, TimeUnit unit) 和 lockInterruptibly();
    • 释放锁:unLock();
    • 条件:newCondition() 返回 绑定到此 Lock 的新的 Condition 实例 ,用于线程间的协作。
    ReadWriteLock锁

    可重入锁:如果锁具备可重入性,当某个线程成功获取锁,调用了某个同步方法method1之后,此时如果再去调用同步方法method2,就不需要再去获取锁,而只是增加获取次数,直接去执行。

    ReadWriteLock与synchronized的区别:

    (1)synchronized是独占锁,加锁和解锁的过程自动进行,易于操作,但不够灵活。
    ReentrantLock也是独占锁,加锁和解锁的过程需要手动进行,不易操作,但非常灵活。
    (2)synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;
    ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。
    (3)synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以相应中断。
    (4)synchronized是非公平锁, ReentrantLock可以实现公平锁

    ReentrantReadWriteLock 读写锁

    读写锁:适用于读多写少的场景。
    ReentrantReadWriteLock,有两把锁,即:一个是读操作相关的锁(ReadLock),称为共享锁;一个是写相关的锁(WriteLock),称为排他锁

    3.4 AbstractQueuedSynchronizer(简称AQS)

    AQS使用一个FIFO的队列表示排队等待锁的线程,队列头节点称作“哨兵节点”,它不与任何线程关联。其他的节点与等待线程关联,每个节点维护一个等待状态waitStatus。


    image.png

    ReentrantLock的基本实现可以概括为:先通过CAS(预期值:0,设置为:1)尝试获取锁,如果CAS操作成功,则将当前线程设置为exclusiveOwnerThread线程;如果CAS操作失败或已经有线程占据了锁,那就加入AQS队列并且被挂起。当锁被释放之后,排在CLH队列队首的线程会被唤醒,然后CAS再次尝试获取锁。在这个时候,如果:

    非公平锁:如果同时还有另一个线程进来尝试获取,那么有可能会让这个线程抢先获取;

    公平锁:如果同时还有另一个线程进来尝试获取,当它发现自己不是在队首的话,就会排到队尾,由队首的线程获取到锁

    相关文章

      网友评论

          本文标题:3.java并发机制及实现原理(2)

          本文链接:https://www.haomeiwen.com/subject/aekhectx.html