• Java中的锁机制


    乐观锁

    乐观锁认为对于同一个数据的并发操作,是不会发生修改的。在更新数据的时候,会采用尝试更新,不断重新的方式更新数据。乐观的认为,不加锁的并发操作是没有事情的。(自旋锁)

    悲观锁

    悲观锁认为对于同一个数据的并发操作,一定会发生修改,哪怕没有修改,也会认为修改。因此,对于同一个数据的并发操作,悲观锁采取加锁的形式。悲观的认为,不加锁的并发操作一定会出现问题。

    悲观锁适合写操作多的场景,乐观锁适合读操作的场景,不加锁会带来大量性能的提升。(Synchronized)

    可重入锁

    又名递归锁,是指在同一个线程在外层方法获取锁的时候,在进入内层方法会自动获取锁。对于JavaReentrantLock而言,他的名字就可看出是一个可重入锁,其名字是Reentrant Lock重新进入锁。

    对于Synchronized而言,也是一个可重入锁。可重入锁的一个好处是可一定程度避免死锁。

    读写锁

    1.多个读者可以同时进行读

    2.写着必须互斥

    3.写着优先于读者
    (ReentrantReadWriteLock)

    分段锁

    分段锁并非一种实际的锁,而是一种思想,用于将数据分段,并在每个分段上都会单独加锁,把锁进一步细粒度化,以提高并发效率。(concurrenthashmap)

    自旋锁

    所谓自选其实指的就是自己重试,当线程抢锁失败后,重试几次,要是抢到锁了就继续,要是抢不到就阻塞线程。

    由此可见,自旋锁是比较消耗CPU的,因为要不断循环重试,不会释放CPU资源,另外,加锁时间普遍较短的场景非常适合自旋锁,可极大提高锁的效率。(cas)

    共享锁/独占锁

    共享锁是指该锁可被多个线程所持有,并发访问共享资源

    独占锁也叫互斥锁,是指该锁一次只能被一个线程所持有。

    对于java ReentrantLock,Synchronized而言,都是独享锁,但是对于Lock的另一个实现类ReadWriteLock,其读锁是共享锁,其余都是独享锁

    非公平锁/公平锁

    公平锁是指按照请求锁的顺序分配,拥有稳定获得锁的机会,但是性能可能比非公平锁低。

    非公平锁是指不按照请求锁的顺序分配,不一定拥有获得锁的机会,但是性能可能比公平锁高。

    对于synchronized而言,是一种非公平锁。

    ReentrantLock默认是非公平锁,但是DF底层可以通过AQS来实现线程调度,所以可以使其变成公平锁

    偏向锁/轻量级锁/重量级锁

    偏向锁

    偏向锁是指一段同步代码一直被一个线程所访问,那么该线程会自动获取锁,降低锁的代价

    轻量级:自旋

    轻量级锁是指当锁是偏向锁的时候,此时又有一个线程访问,偏向锁就会升级为轻量级锁,其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

    重量级锁:需要操作系统调度

    重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁膨胀为重量级锁。高并发情况下,出现大量线程自旋获得锁,对CPU销毁较大,升级为重量级锁后,获取不到锁的线程将阻塞,等待操作系统的调度

    AQS实现原理

    在内部有一个state变量表示锁是否使用,初始化0,在多线程条件下,线程

    要执行临界区的代码,必须首先获取state,某个线程获取成功之后,state加1,

    其他线程再获取的话由于共享资源已被占用,所以会到FIFO等待队列去等待,等占有state的线程执行完临界区的代码释放资源(state-1)后,会唤醒FIFO中的下一个等待线程(head的下一个节点)去获取state

    state由于是多线程共享变量,所以必须定义成volatile,以保证state的可见性,同时虽然volatile能保证可见性,但不能保证原子性,所以AQS提供了对state的原子方法,保证了线程安全。

    另外AQS中实现的FIFO队列其实是双向链表实现的,head节点代表当前占用的线程,其他节点由于暂时获取不到锁所以依次排队等待锁释放。

    队列由Node对象组成,Node是AQS中的内部类。

    AQS的锁模式分为:独占和共享

    独占锁:每次只能有一个线程持有锁,比如ReentrantLock就是以独占方式实现的互斥锁

    共享锁:允许多个线程同时获取锁,并发访问共享资源,比如ReentrantReadWriteLock

    ConcurrentHashMap

    ConcurrentHashMap 同步容器类是 Java 5 增加的一个线程安全的哈希

    表。对与多线程的操作,介于 HashMap 与 Hashtable 之间。内部采用“锁分

    段”机制(jdk8 弃用了分段锁,使用 cas+synchronized)替代 Hashtable 的独

    占锁。进而提高性能。

    放弃分段锁的原因

    1.加入多个分段锁浪费内存空间

    2.生产环境中,map在放入时竞争同一个锁的概率非常小,分段锁反而会造成更新等操作的长时间等待。

    jdk8放弃了分段锁而是用了Node锁,降低了锁的粒度,提高性能,并使用CAS操作来确保Node的一些操作的原子性,取代了锁。

    put时首先通过hash找到对应链表过后,查看是否是第一个Node,如果是,直接用CAS原则插入,无需加锁,

    然后,如果不是链表第一个Node,则直接用链表第一个Node加锁,这里加锁的是synchronized.

    ConcurrentHashMap不支持存储null键和null值

    ConcurrentHashMap不能put null是因为无法分辨是key没找到的null还是有key值为null,这是多线程里面模糊不清的

  • 相关阅读:
    windows任务栏卡死,重启也没用
    第一章 数据可视化和matplotlib
    echarts的legend的小图标与文本垂直对齐
    简单入门linux【三】linux 组和权限
    Java从零学起(九)----集合
    Maven的聚合 继承 属性 版本管理 多环境资源配置 跳过测试
    FMI标准:实现SkyEye与Simulink无需缝合的联合仿真
    [rtsp学习]-264rtp打包结构
    使用MySQL和SQL Server生成最近七天的日期
    在线问诊 Python、FastAPI、Neo4j — 创建药品节点
  • 原文地址:https://blog.csdn.net/m0_53680228/article/details/126366266