Java 并发编程(Java中的锁)(4)

Java 中的锁分类:

乐观锁/悲观锁:

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

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

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

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

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

乐观锁在 Java 中的使用,是无锁编程,常常采用的是 CAS 算法,典型的例子就是原子类,通过 CAS 自旋实现原子操作的更新。

可重入锁:

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

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

Synchronized和ReentrantLock都是可重入锁

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

读写锁(ReadWriteLock):

是具体的锁实现 读和写是两把锁 进行分离使用.

读写锁特点:

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

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

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

在这里插入图片描述

分段锁:

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

不是一种锁实现, 是一种加锁的思想, 采用分段加锁,降低锁的粒度. 从而提高效率

自旋锁( SpinLock):

当一个线程尝试去获取某一把锁的时候,如果这个锁此时已经被别人获取(占用),那么此线程就无法获取到这把锁,该线程将会等待,间隔一段时间后会再次尝试获取。这种采用循环加锁 -> 等待的机制被称为自旋锁(spinlock)

不是一种锁实现, 采用自旋(循环重试)的方式进行尝试获取执行权. 不会让线程进入到阻塞的状态,
适用于锁的时间较短的情况.

共享锁/独占锁:

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

可以被多个线程共享的锁 ReadWriteLock 的读锁是共享. 多个线程可以同时读数据

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

一次只能有一个线程获取锁 ReentrantLock,Synchronized,ReadWriteLock的写锁都是独占锁.

对于 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 的来实现线程调度,所 以可以使其变成公平锁。

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

锁的状态:
无锁 / 偏向锁 / 轻量级锁 / 重量级锁

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

偏向锁

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

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

重量级锁
重量级锁是指当锁为轻量级锁的时候,另一个线程虽然是自旋,但自旋不会一 直持续下去,当自旋一定次数的时候,还没有获取到锁,就会进入阻塞,该锁 膨胀为重量级锁。重量级锁会让其他申请的线程进入阻塞,性能降低。

Java 对象头:

在 Hotspot 虚拟机中,对象在内存中的布局分为三块区域:对象头实例数据对齐填充;Java 对象头是实现 synchronized 的锁对象的基础,一般而言, synchronized 使用的锁对象是存储在 Java 对象头里。它是轻量级锁和偏向锁的关键.

Mawrk Word:
Mark Word 用于存储对象自身的运行时数据,如哈希码(HashCode)、 GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID、偏向时间戳等等。Java 对象头一般占有两个机器码(在 32 位虚拟机中,1 个机器码等于 4 字节,也就 是 32bit),下面就是对象头的一些信息:

在这里插入图片描述

Synchronized:

是可重入锁,非公平锁

是关键字,可以修饰代码块,也可以修饰方法 是隐式的 自动获取 释放锁.

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

同步方法使用 ACC_SYNCHRONIZED 标记是否为同步方法.当方法调用时, 调用指令将会检查方法的 ACC_SYNCHRONIZED 访问标志是否被设置,如果设置了,该标记表明线程进入该方法时,需要 monitorenter,退出该方法时需要 monitorexit。

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

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

使用 javap -verbose SynchronizedDemo 反编译后得到:
在这里插入图片描述

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

ReentrantLock:

是类 只能修饰代码块 是显示的, 手动添加 手动释放

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

在类的内部自己维护了一个锁的状态, 一旦有线程抢占到了,将状态改为1,其他线程进入到队列中等待锁的释放,

锁一旦释放了,那么就唤醒头结点 开始尝试去获得锁.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值