如何保证原子性

如何保证原子性

同一时刻只有一个线程执行称之为互斥.如果我们 能够保证对共享变量的修改是互斥的那么就能保证原子性了.

锁:

java提供的synchronized关键字,就是锁的实现.

synchronized是独占锁,并不能改变CPU时间切换的特点,只有当其他线程访问资源时,发现锁未被释放,只能等待.

synchronized一定能保证原子性,因为被其修饰的某段代码,只能由一个线程执行,所以一定可以保证原子操作.

JUC原子变量

juc(java.util.concurrent包)中的lock包和atomic包,他们也可以解决原子性问题.

加锁是阻塞式方式实现

原子变量是非阻塞方式实现

原子类的原子性是通过volatile和CAS实现的.

AtomicInteger类中的value被volatile修饰,这就保证了value内存的可见性,这为后续的CAS提供了基础.

CAS(比较并交换)

CAS是一种乐观锁的实现方式,采用自旋的思想.

每次判断预估值和内存值是否相同,如果不同,则重新拿最新值与预估值进行重新判断.

CAS三个操作数

1.内存值 V

2.预估值 A

3.更新值 B

当且仅当V==A时,V=B否则不做处理

这样做效率高,不会阻塞

缺点:

由于是自旋的方式,锁不断循环,会消耗CPU,导致其饱满.

ABA问题

当某个线程将A改为B,再将B改为A,当另一个线程去判断预估值时,会认为他没有被修改过

解决:

添加一个版本号,来避免这个问题

比如(A,1)改为(B,2)时,再将其改回是(A,3)当另一个线程预估值判断时,会发现其版本号发生过修改.

ConcurrentHashMap

首先concurrentHashMap是线程安全的Map.

HashTable也是线程安全的,put()方法整个加锁,粒度高,效率低

concurrentHashMap放弃了分段锁,而采用了cas原则+synchronized.

放弃分段锁的原因:
  1. 加入多个锁浪费内存空间
  2. 为了提高GC效率
  3. 生产环境中,map竞争同一个锁的几率小,分段锁反而会造成更新的长时间等待

jdk8放弃了分段锁,而是用了Node锁,粒度低,效率高,并使用cas操作确保其Node的原子性,取代了锁

在put()时,首先用hash找到其链表,查看是否是第一个Node,如果是直接用CAS原则插入,无需加锁.

如果不是,则直接在其第一个Node加锁,(synchronized)

java中的锁分类

乐观锁/悲观锁

乐观锁:不加锁操作,认为对同一个数据的并发操作不做修改(读写操作;不加锁带来大量的性能提升)

悲观锁:加锁操作,认为对同一个数据的并发操作一定修改(适合操作多的场景)

共享锁/独占锁

共享锁:是指该锁被多个线程所持有,并发访问共享资源(读写锁中的读操作)

独占锁(互斥锁):该锁只能被一个线程所持有(读写锁中的写操作)

公平锁/非公平锁

公平锁:优先将锁分配给排队时间长的线程

非公平锁:不考虑线程等待,可以插队,直接获取锁,获取不到时在排到队尾等待

可重入锁

同一个线程中, 在外层获得锁后,进入方法内也可以获取锁,可一定程度避免死锁

读写锁
  1. 多个读者可以同时进行读操作
  2. 只允许一个写者写,不能与读同时进行
  3. 写者优先于读者(一旦有写操作,后续必须等待,唤醒时优先考虑写者)
分段锁

是一种思想,将数据分段,在每个分段上加锁

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

锁的状态:

无锁状态;偏向锁状态;轻量级锁状态;重量级锁状态

通过对象监视器在对象头的字段表明的

四种状态都不是java语言的锁,而是jvm为了提供锁的获取与释放效率而做的优化(使用synchronized)

偏向锁:

指同步代码被一个线程访问,会自动获取锁,降低获取锁的代价

轻量级锁:

当锁是偏向锁时,被另一个线程所访问,会升级成轻量级锁,其他线程会通过自旋的方式尝试获取锁,不会阻塞,提供性能.

重量级锁:

指当锁为轻量级锁时,另一个线程虽然是自旋,但是自旋一定会持续下去,当自旋一定的次数时,会进入阻塞.该锁会膨胀为重量级锁.会让其他申请锁的线程进入阻塞,降低性能

自旋锁

比较耗CPU,尽量不要阻塞

不断循环重试获取锁

Synchronized实现

可以保证方法或者代码在运行时,同一时刻只要一个方法进入临界区.还可以保证共享变量的可见性.

java中每个对象都可以作为锁:

  1. 普通同步方法,锁是当前对象实例
  2. 静态同步方法:锁是当前类的class对象
  3. 同步方法区:锁是括号里面的对象

java对象头:

在 Hotspot 虚拟机中,对象在内存的分布为三个区域:

对象头;

实例数据

对齐填充

synchronized使用的锁对象是存储在对象头中的,它是轻量级锁和偏向锁的关键.

Mawrk Word:

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

如:哈希码(hashcode),GC分代年龄**,锁状态标志**,线程持有的锁,偏向线程ID等等;

下面就是对象头的一些信息:
在这里插入图片描述

ReentrantLock

利用CAS和AQS队列实现.支持公平锁和非公平锁;

假设有三个线程去竞争锁,假设A的AQS操作成功了,那么B和C则设置状态失败;

由于线程A占用了锁,B和C失败,那么线程B和C会进入等待队列.如果A拿着锁死死不放,那么B和C就会被挂起.

B和C尝试获取锁,若当线程的前驱节点是head,就有资格获取锁.

unlock()

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

AQS(AbstractQueendSynchronizer)

抽象的队列式的同步器;

核心思想:

如果请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资源设置为锁定状态.如果被请求的资源被占用,那么就有需要线程阻塞等待以及被唤醒时锁分配的机制.这个机制AQS是用CLH队列锁实现的.即将暂时获取不到锁的线程加入到队列中.

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

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值