Java基础--->并发部分(2)【Java中的锁】

synchronized和ReentrantLock的区别

synchronizedReentrantLock 都可以用来实现 Java 中的线程同步。它们的作用类似,但是在用法和特性上还是有一些区别的。

  1. synchronized 是 Java 内置的关键字,可以修饰代码块和方法,自动获取锁、释放锁,可以避免因为锁的释放问题导致的死锁;而 ReentrantLock 是Java类,只能对某段代码进行修饰,需要手动进行锁的获取和释放。
  2. ReentrantLock 的灵活性更高,比如支持可重入锁、支持公平锁和非公平锁、支持多个条件变量等,而 synchronized 则相对简化,更加方便快捷。
  3. 多个线程争抢 synchronized 的锁时,其中一个线程拿到锁后,其他线程进入锁池等待,直到持有锁的线程释放锁,其他等待线程才能继续竞争锁。而 ReentrantLock 可以灵活地控制锁的公平性和非公平性,以及等待的顺序。
  4. synchronized 在底层是依赖于 JVM 实现的,而 ReentrantLock 是使用 java.util.concurrent 包提供的一种基于接口的可重入锁,这种可重入锁的性能比较优秀,适用于高并发场景。

综上所述,ReentrantLock 更加灵活,支持更多的特性和操作,适用于复杂的场景;而 synchronized 更加简化,使用方便,适用于一些简单的场景。

Java中锁的名词

​ 每个名字并不都代表一个锁,有些锁的名字指的是锁的特性,锁的设计,锁的状态

乐观锁(不加锁):认为并发的操作,不加锁的方式实现是没有问题的,每次操作前判断(CAS,自旋)是否成立,不加锁实现。

悲观锁:认为对于同一个数据的并发操作,一定是会发生修改的,即使没有修改,也会认为修改。认为并发操作肯定会有问题,必须加锁,对于同一个数据的并发操作,悲观锁采用加锁的形式

总结:悲观锁适合写操作非常多的场景,乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升。

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

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

可重入锁:当一个线程获取到外层方法的同步锁对象后,可以获取到内部其他方法的同步锁,如果不使用,就容易出现死锁

在这里插入图片描述

读写锁(ReentrantReadWriteLock):支持读,写加锁,总的来说就是如果是读操作,就不加锁,如果一旦有写操作,那么读写互斥

特点:读读不互斥,读写互斥,写写互斥
加读锁是防止在另外的线程在此时写入数据,防止读取脏数据

一个读写锁同时只能有一个写者或多个读者(与CPU数相关),不能同时有读者和写者

​ 在读写锁保持期间也是抢占失效的

​ 如果读写锁当前没有读者,也没有写者,那么写者可以立刻获得读写锁,否者它必须自旋在那,直到没有读者或者写者。如果读写锁没有写者,那么读者可以立即获得该读写锁,否则读者必须自旋在那里,直到写者释放该读写锁。

分段锁(不是锁):是一种思想,将数据分段,并在每个分段上单独加锁,以此来将锁的粒度拆分,提高效率

自旋锁(不是锁):是一种自旋思想,是以自旋的方式重试获取,当线程抢锁失败后,重试几次,要是抢到锁了就继续,抢不到就阻塞线程,目的就是还是为了尽量不要阻塞线程。

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

共享锁/独占锁
​ 共享锁:读写锁中的读锁,该锁可被多个线程持有,并发访问共享资源
​ 独占锁:互斥锁,synchronized ReentrantLock都属于独占锁,一次之能被一个线程持有

公平锁/非公平锁
​ 公平锁:根据线程先来后到公平的获取锁,例ReentrantLock就可以实现公平锁,按照锁请求的顺序分配,拥有稳定获得锁的机会
​ 非公平锁:不按照锁请求顺序分配,没有先来后到,谁抢到谁获得执行权,synchronized 是非公平锁

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

synchronized锁

对象结构

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

​ 对象头中有一块区域称为 Mark Word,用于存储对象自身的运行时数据,如哈 希码(HashCode)、GC 分代年龄、锁状态标志、线程持有的锁、偏向线程 ID 等等。

在synchronized锁的底层实现中,提供锁的状态,又来区别对待
这个锁的状态在同步锁的对象头中,有一个区域叫Mark Word中存储

偏向锁/轻量级锁/重量级锁(都是锁的状态):这四种状态都不是Java语言中的锁,而是JVM为了提高锁(synchronized)的获取与释放效率而做的优化

​ 无锁
偏向锁
​ 一直是一个线程访问,线程ID被记录,快速获取锁
轻量级锁
​ 当锁状态为偏向锁时,又继续有其他线程访问,此时升级为轻量锁,没有获取到锁的线程,不会阻塞,继续不断尝试获取锁
重量级锁
​ 当锁的状态为轻量级锁时,线程自旋达到一定的次数,进入达到阻塞状态,锁状态升级为重量级锁,等待操作系统调度

synchronized锁的实现

​ synchronized锁是依赖底层编译后的指令来控制的,需要我们提供一个同步对象,来记录锁状态

​ 线程在进入synchronized代码块时候会自动获取内部锁,这个时候其他线程访问时会被阻塞,直到执行完或抛出异常或者调用了wait方法,都会释放锁资源,主内存把变量读取到自己工作内存,在退出时会把工作内存的值写入到主内存,保证原子性
​ synchronized基于进入和退出监视器对象来实现方法同步和代码块同步
​ 设置ACC_SYNCHRONIZED标记是否为同步方法,调用指令先检查是否设置,如果设置了,需要monitorenter表明线程进入该方法,使用monitorexit退出该方法
​ 在虚拟机执行monitorenter指令时,首先要尝试获取对象的锁,如果当前线程拥有了这个对象的锁,把锁的计数器+1,当执行monitorexit指令时将所得计数器-1,当计数器为0时,锁就被释放了
​ Java中synchronized通过在对象头设置标记,达到了获取锁和释放锁的目的

ReentrantLock锁

​ ReentrantLock 是 java.util.concurrent.locks 包 下 的 类,ReentrantLock 基于 AQS,在并发编程中它可以实现公平锁和非公平锁来

​ ReentrantLock 类内部总共存在 Sync、NonfairSync、FairSync 三个类,

​ NonfairSync 与 FairSync 类 继 承 自 Sync 类 , Sync 类 继 承 自 AbstractQueuedSynchronizer 抽象类。对共享资源进行同步,同时和 synchronized 一样,

​ ReentrantLock 支持可重入,

除此之外,ReentrantLock 在调度上更灵活,支持更多丰富的功能。

​ NonfairSync 类继承了 Sync 类,表示采用非公平策略获取锁,其实现 了 Sync 类中抽象的 lock 方法.

  • 非公平
NonfairSync
final void lock() {
         if (compareAndSetState(0, 1))//线程来到后,直接尝试获取锁,是非公平
             setExclusiveOwnerThread(Thread.currentThread());
         else//获取不到
             acquire(1);
}

FairSync 类也继承了 Sync 类,表示采用公平策略获取锁,其实现了 Sync 类中 的抽象 lock 方法.
  • 公平实现
FairSync
final void lock() {
            acquire(1);
            }
            ```
  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值