• Java锁的分类、概念和状态


    img

    在Java中,synchronizedLock的实现类锁都是悲观锁。

    一、悲观锁和乐观锁

    悲观锁:获取数据时会先加锁,确保数据不会被其他线程修改。悲观锁适合写操作多的场景。

    乐观锁:认为自己在使用数据时,不会有别的线程来修改数据,更新数据前会判断有没有别的线程更新了这个数据。乐观锁适合读操作多的场景。

    乐观锁在Java中是通过使用无锁编程来实现,最常采用的是CAS算法,Java原子类中的递增操作就通过CAS自旋实现的。

    img

    二、自旋锁

    实现原理:CAS算法

    阻塞或唤醒一个Java线程需要操作系统切换CPU状态来完成,这种状态转换需要耗费处理器时间。如果物理机器有多个处理器,能够让两个或以上的线程同时并行执行,我们就可以让后面那个请求锁的线程不放弃CPU的执行时间,让当前线程进行自旋,如果在自旋完成后前面锁定同步资源的线程已经释放了锁,那么当前线程就可以不必阻塞而是直接获取同步资源,从而避免切换线程的开销。这就是自旋锁。

    但是,自旋不能代替阻塞!如果锁被占用的时间太长,自旋的线程就会浪费CPU资源。所以,自旋等待的时间必须要有一定的限度,如果自旋超过了限定次数(默认是10次,可以使用-XX:PreBlockSpin来更改)没有成功获得锁,就应当挂起线程。

    img

    三、无锁,偏向锁,轻量级锁,重量级锁

    这四种是锁的状态,针对的是synchronized。

    Synchronized能实现线程同步需要了解两个重要的概念Java对象头,Monitor。synchronized会对同步资源加锁,这把锁就存在Java对象头。

    Java对象头包括两部分:Mark Word(标记字段)、Klass Pointer(类型指针)。Mark Word:默认存储对象的HashCode,分代年龄和锁标志位信息。它会根据对象的状态复用自己的存储空间,也就是说在运行期间Mark Word里存储的数据会随着锁标志位的变化而变化。

    Klass Point:对象指向它的类元数据的指针,虚拟机通过这个指针来确定这个对象是哪个类的实例。

    每一个Java对象就有一把看不见的锁,称为内部锁或者Monitor锁。

    Monitor 是线程私有的数据结构,每个线程都有一个可用的monitor record列表和一个全局列表。每个被锁的对象都会和一个monitor关联,monitor中的owner字段存放拥有该锁的线程唯一标识,表示该锁被这个线程占用。synchronized通过Monitor来实现线程同步,Monitor是依赖于底层的操作系统的Mutex Lock(一种互斥锁)来实现的线程同步。这种依赖于操作系统Mutex Lock所实现的锁我们称之为“重量级锁”。

    image-20220207164858313

    无锁

    所有线程都能访问并修改同步资源,但是只有一个线程能修改成功。

    特点:修改操作在循环内进行,线程会不断的尝试修改共享资源。

    偏向锁

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

    偏向锁只有遇到其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁,线程不会主动释放偏向锁。

    偏向锁通过对比Mark Word解决加锁问题,避免执行CAS操作。

    轻量级锁

    当锁是偏向锁的时候,被另外的线程所访问,偏向锁就会升级为轻量级锁。其他线程会通过自旋的形式尝试获取锁,不会阻塞,从而提高性能。

    若当前只有一个等待线程,则该线程通过自旋进行等待。但是当自旋超过一定的次数,或者一个线程在持有锁,一个在自旋,又有第三个来访时,轻量级锁升级为重量级锁。

    轻量级锁是通过用CAS操作和自旋来解决加锁问题,避免线程阻塞和唤醒而影响性能

    重量级锁

    升级为重量级锁时,锁标志的状态值变为“10”,此时Mark Word中存储的是指向重量级锁的指针,此时等待锁的线程都会进入阻塞状态。

    重量级锁是将除了拥有锁的线程以外的线程都阻塞。

    四、公平锁和非公平锁

    公平锁:按照申请锁的顺序来获取锁,线程直接进入队列中排队。

    优点:等待锁的线程不会饿死。

    缺点:整体吞吐量比不上非公平锁,除队列第一个线程外,其他线程都会阻塞。

    img

    非公平锁:多个线程加锁时,直接获取锁(插队),如果成功,这个线程就不用阻塞直接就获取到锁,失败才会进入等待队列队尾等待。

    优点:减少CPU唤醒线程的开销。吞吐量大。

    缺点:处于等待队列的线程有可能饿死,或者等很久才会获取到锁。

    img

    五、可重入锁和非可重入锁

    可重入锁

    又名递归锁,是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),不会因为之前已经获取过还没释放而阻塞。Java中ReentrantLock和synchronized都是可重入锁。

    优点:可一定程度避免死锁。

    img

    非可重入锁

    在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁(前提锁对象得是同一个对象或者class),会因为之前已经获取过还没释放而阻塞。

    img

    六、共享锁和独占锁

    共享锁

    该锁可被多个线程所持有。如果线程T对数据A加上共享锁后,则其他线程只能对A再加共享锁,不能加排它锁(独享锁)。获得共享锁的线程只能读数据,不能修改数据。

    独占锁

    又叫排他锁,该锁一次只能被一个线程所持有。如果线程T对数据A加上排它锁后,则其他线程不能再对A加任何类型的锁。获得排它锁的线程即能读数据又能修改数据。JDK 中的 synchronized 和 JUC 中 Lock 的实现类就是互斥锁。

    知识来源:Java锁的概念|不可不说的Java“锁”事

  • 相关阅读:
    [整理]ADB命令行学习笔记
    3、HTML的body内标签1
    2、HTML的head内标签
    1、HTML的本质以及在web中的作用
    3.11-3.15 HDFS HA
    3.9-3.10 分布式协作服务框架Zookeeper
    3.6-3.8 分布式环境启动、测试
    3.1-3.5 分布式部署hadoop2.x的准备和配置
    2.28 MapReduce在实际应用中常见的优化
    2.27 MapReduce Shuffle过程如何在Job中进行设置
  • 原文地址:https://www.cnblogs.com/qqkkOvO/p/15881041.html
Copyright © 2020-2023  润新知