JAVA并发编程(3)——(java各种锁和锁状态(乐/悲观锁,可重入锁,读写锁,分段锁,自旋锁,共享锁,AQS,公平锁,java对象头, Synchronized和ReentrantLock)

java各种锁和锁状态(思想)

8.1 锁(思想)

乐观锁/悲观锁

乐观锁与悲观锁不是指具体的什么类型的锁,而是指看待并发同步的角度。

乐观锁

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

悲观锁 :

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

从上面的描述我们可以看出,悲观锁适合写操作非常多的场景,乐观锁适合读操 作非常多的场景,不加锁会带来大量的性能提升

悲观锁在 Java 中的使用,就是利用各种锁。

乐观锁在 Java 中的使用,是无锁编程,常常采用的是 CAS 算法,典型的例子就

是原子类,通过 CAS 自旋实现原子操作的更新

可重入锁

可重入锁又名递归锁是指在同一个线程在外层方法获取锁的时候,在进入 内层方法会自动获取锁。对于 Java ReentrantLock 而言, 他的名字就可以看出 是一个可重入锁,其名字是 Reentrant Lock 重新进入锁。 对于 Synchronized 而言,也是一个可重入锁可重入锁的一个好处是可一定程度避免死锁

在这里插入图片描述

上面的代码就是一个可重入锁的一个特点,如果不是可重入锁的话,setB 不会 被当前线程执行,造成死锁。

读写锁(ReadWriteLock)

读写锁特点:

a)多个读者可以同时进行读

b)写者必须互斥(只允许一个写者写,也不能读者写者同时进行)

c)写者优先于读者(一旦有写者,则后续读者必须等待,唤醒时优先考虑写者)

在这里插入图片描述

分段锁

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

自旋锁( SpinLock)

自旋锁其实并不属于锁的状态,从 Mark Word 的说明可以看到,并没有一个锁 状态叫自旋锁。所谓自旋其实指的就是自己重试,当线程抢锁失败后,重试几次, 要是抢到锁了就继续,要是抢不到就阻塞线程说白了还是为了尽量不要阻塞线 程。由此可见,自旋锁是是比较消耗 CPU 的,因为要不断的循环重试,不会释放 CPU 资源。另外,加锁时间普遍较短的场景非常适合自旋锁,可以极大提高锁的效率

共享锁/独占锁

共享锁

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

独占锁

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

对于 Java ReentrantLock,Synchronized 而言,都是独享锁但是对于 Lock 的另一个实现类 ReadWriteLock,其读锁是共享锁,其写锁是独享锁。 读锁的共享锁可保证并发读是非常高效的,读写,写读 ,写写的过程是互斥的独享锁与共享锁也是通过 AQS 来实现的,通过实现不同的方法,来实现独享或者共享

AQS(AbstractQueuedSynchronizer)

类如其名,抽象的队列式的同步器,这个类在 java.util.concurrent.locks 包

AQS

定义了一套多线程访问共享资源的同步器框架,许多同步类实现都依赖于它,如 常用的 ReentrantLock…

AQS 的核心思想是,如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态,如果被请求的共享资源被 占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制 AQS 是用 CLH 队列锁(CLH 同步队列是一个 FIFO() 双向队列,AQS 依赖它来完成同步 状态的管理)实现的,即将暂时获取不到锁的线程加入到队列中

AQS 就是基于 CLH 队列,用 volatile 修饰共享变量 state,线程通过 CAS 去改变状态符,成功则获取锁成功,失败则进入等待队列,等待被唤醒

在这里插入图片描述

公平锁/非公平锁

公平锁(Fair Lock)

是指在分配锁前,检查是否有线程在排队等待获取该锁,优先 将锁分配给排队时间最长的线程。

非公平锁(Nonfair Lock)是指在分配锁时不考虑线程排队等待的情况,直接尝试 获取锁,在获取不到时再排到队尾等待.

因为公平锁需要在多核的情况下维护一个线程等待队列,基于该队列进行锁的分 配,因此效率比非公平锁低很多.

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

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

在这里插入图片描述

8.2 锁状态

无锁状态

偏向锁状态

轻量级锁状态

重量级锁状态

锁的状态是通过对象监视器在对象头中的字段来表明的。

四种状态会随着竞争的情况逐渐升级,而且是不可逆的过程,即不可降级。 这四种状态都不是 Java 语言中的锁,而是 Jvm 为了提高锁的获取与释放效率而 做的优化(使用 synchronized 时)。

偏向锁

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

降低获取锁的代价。

轻量级

轻量级锁是指当锁是偏向锁的时候,被另一个线程所访问,偏向锁就会升级为 轻量级锁

其他线程会通过自旋的形式尝试获取锁,不会阻塞,提高性能。

重量级锁

重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一 直持续下去

当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁 膨胀为重量级锁

重量级锁会让其他申请的线程进入阻塞,性能降低。

java对象头

在 Hotspot 虚拟机中,对象在内存中的布局分为三块区域:对象头、实例 数据和对齐填充

Java 对象头是实现 synchronized 的锁对象的基础

一般而言, synchronized 使用的锁对象是存储在 Java 对象头里。它是轻量级锁和偏向锁的关键

·

Mawrk Word:

Mark Word 用于存储对象自身的运行时数据

如哈希码(HashCode)、 GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。

Java 对象头一般占有两个机器码(在 32 位虚拟机中,1 个机器码等于 4 字节,也就 是 32bit),下面就是对象头的一些信息:

在这里插入图片描述

Synchronized和ReentrantLock

synchronizee

Java 提供的一种原子性性内置锁

Java 每个对象都可以把它当做是监视器锁,线程代码执行在进入 synchronized 代码块时候会自动获取内部锁,这个时候其他线程访问时候会被阻塞

直到进入 synchronized 中的代码执行完毕或者 抛出异常或者调用了 wait 方法,都会释放锁资源

在进入 synchronized 会从 主内存把变量读取到自己工作内存,在退出的时候会把工作内存的值写入到主内 存,保证了原子性

synchronized 基于进入和退出监视器对象来实现方法同步和代码块同步。

同步方法使用 ACC_SYNCHRONIZED 标记是否为同步方法.当方法调用时, 调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置

如果设置了,该标记表明线程进入该方法时,需要 monitorenter,退出该方法时需 要 monitorexit。

使用 javap -verbose SynchronizedDemo 反编译后得到:

在这里插入图片描述

代码块的同步是利用 monitorenter 和 monitorexit 这两个字节码指令。 在虚拟机执行到 monitorenter 指令时,首先要尝试获取对象的锁。

当前线程拥有了这个对象的锁,把锁的计数器+1;当执行 monitorexit 指 令时将模计数器-1;当计数器为 0 时,锁就被释放了.

在这里插入图片描述

Java 中 synchronized 通过在对象头设置标记,达到了获取锁和释放锁的目的。

ReentrantLock

ReentrantLock 主要利用 CAS+AQS 队列来实现。它支持公平锁和非公平锁

[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-HTa9HFvQ-1679141937644)(C:\Users\封纪元\AppData\Roaming\Typora\typora-user-images\1642693425113.png)]

lock();
在这里插入图片描述

假设当前有三个线程去竞争锁,假设线程 A 的 CAS 操作成功了,获得了锁,将 锁状态 state 改为 1,那么线程 B 和 C 则设置状态失败。

在这里插入图片描述

由于线程 A 已经占用了锁,所以 B 和 C 失败,并且入等待队列。如果线程 A 拿着锁死死不放,那么 B 和 C 就会被挂起。

B 和 C 相继入队尝试获取锁。

若当前线程的节点的前驱节点是 head,就有资格尝试获取锁
在这里插入图片描述

unlock()

尝试释放锁,若释放成功,那么查看头结点的状态,如果是则唤醒头结点的下个 节点关联的线程

在这里插入图片描述

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值