原子操作锁Java中同步加锁的关键字,知识是难以运用的?

内存模型 JVM 内存结构 VS Java 内存模型 VS Java 对象模型

容易混淆:三个不同的概念,但很多人容易混淆

什么是 JMM

Java 内存模型

JMM 是常态

JMM是工具类和关键字的原理

什么是重新排序

什么是reordering:线程1里面两行代码的实际执行顺序与Java文件中的代码顺序不一致,代码指令没有严格按照代码语句的顺序执行c语言中内存的申请和释放,它们的顺序已经改变了, 这是重新排序 , y=a 和 b=1 的两行语句在这里颠倒了。

重新排序的好处:提高处理速度

优化加权前后的指令。重新排序显着提高了处理速度

3例重新排序

什么是能见度

使用 volatile 解决问题并强制将数据更改刷新到主内存

为什么存在可见性问题

JMM的抽象:主内存和本地内存

主存和本地内存JMM的关系有如下规定:

所有的共享变量都存在于主存中,每个线程都有自己的本地内存,线程读写共享数据也是通过本地内存进行交换,导致可见性问题

什么是先发生原则

发生了什么-之前

之前没有发生的事情

什么是发生前规则?

Happens-Before有一个原理:如果A是对一个volatile变量的写操作,B是对同一个变量的读操作,那么hb(A,B)

易失性关键字

如果只沉迷于业务发展,不在业务发展中总结,提高业余时间,就永远解决不了复杂的问题。没有系统梳理多线程的知识点,什么是知识难用?

不适用场景:a++

应用:

影响:

volatile 和 Synchronized 的关系

使用 volatile 修复重新排序 OutOfOrderExecution

概括:

确保可见性的措施:

升华:对同步可见性的正确理解

什么是原子性

Java中的原子操作是什么?

原子操作+原子操作!= 原子操作

Java中同步锁的关键字是什么?

同步的

同步的原理是什么?

Java中的每一个对象都是一个对象监视器(Object monitor),主要是使用对象头标签字来实现的。

同步方法使用哪个对象锁?

实例方法锁定this所代表的对象;

静态方法锁定对应的Class对象;

同步块使用 this 对象。

synchronized(obj) 使用 obj 对象。

同步有哪些优化?

同步方法优化

偏向锁:BiaseLock,轻量级锁,其开销相当于无锁。

等待/通知方法有什么作用?同步和锁定有什么区别?

同步方法的问题:

同步块的阻塞不能被中断(不能被中断) 同步块的阻塞不能控制超时(不能自动解锁) 同步块不能异步处理锁(即不能立即知道锁是否可以获得)同步块不能根据条件灵活锁定和解锁(即只能与同步块范围一致)

Lock是一种更灵活的锁,使用灵活可控,支持更灵活的编程,性能开销低。锁接口设计:

// 1.支持中断的API
void lockInterruptibly() throws InterruptedException;
// 2.支持超时的API
boolean tryLock(long time, TimeUnit unit) throws InterruptedException;
// 3.支持非阻塞获取锁的API
boolean tryLock();
// 4.可以根据条件灵活控制,newCondition设置多个通知信号

与 synchronized 和 Lock 相比,谁的性能更高?

不一定,要看具体情况。

同步退化为权重锁(Mutex)后,在高负载情况下性能开销会很大。

什么是可重入锁?对象锁是可重入锁吗?

同一个线程在执行不同的方法时可以多次获取这个锁。

synchronized对应的锁是可重入锁。

Java中的锁一般都是可重入锁,比如最基本的ReentrantLock。

什么是公平锁?对象锁是公平锁吗?

图片[1]-原子操作锁Java中同步加锁的关键字,知识是难以运用的?-老王博客

公平锁是排队等待,按照应用的时间顺序依次分配。

synchronized对应的锁是不公平锁。这样做的目的是提高执行性能。缺点是可能会发生线程饥饿。

ReentrantLock 提供了公平和不公平锁的实现。默认情况下,无参数构造函数会创建一个不公平的锁。

公平锁:新的 ReentrantLock(true)

不公平锁:new ReentrantLock(false)

什么是乐观锁?什么是悲观锁定?

悲观锁和乐观锁是最早出现在数据库中的逻辑概念。

悲观锁适用于比较悲观的场景(并发竞争激烈),采用直接锁。悲观的是,没有锁的并发操作会有问题。比如同步锁,或者数据库选择更新等等。

锁的状态下并不真正存在乐观锁,适用于相对乐观、并发竞争较低的场景。避免了悲观锁独占锁资源的现象,也提高了乐观场景下并发程序的执行性能。例如,数据库操作使用版本号、Java 原子类等。

在具体使用中,乐观锁只在更新数据时,通过判断已有数据与原始数据是否一致来判断该数据是否被其他线程操作,如果没有被其他线程修改,则更新数据,如果被其他线程修改线程然后没有数据更新(+旋转重试/while循环)。

什么是自旋锁?

Spin一般是一个while循环,不断的进行条件比较,比如Java的CAS操作。

缺点是如果情况很悲观,如果长时间没有成功获取锁,一直在旋转,会给CPU带来很大的开销。

什么是独占锁和共享锁?

排他锁是在任何时候只有一个线程可以获取的锁。[信号量=1的场景]

共享锁是指可以被多个线程同时持有的锁[信号量=N+场景]。

什么是读写锁?

Java中的ReentrantReadWriteLock,允许一个线程写入,多个线程读取。

包括两个锁:

注意:ReadWriteLock 管理一组锁,一个读锁和一个写锁。

在没有写锁的情况下,读锁可以同时被多个线程持有,写锁是排他的。

所有读写锁的实现都必须保证写操作对读操作的内存影响。一次只能有一个写线程,但多个线程可以同时并发读取数据。ReadWriteLock 适用于读多写少的并发情况。

使用锁具有哪些注意事项?

粒度、性能、可重入性、公平性、自旋

根据具体场景确定:

Doug Lea 的《Java 并发编程:设计原则和模式》一书推荐了三种使用锁的最佳实践:

仅在更新对象的成员变量时始终锁定 仅在访问可变成员变量时始终锁定 在调用其他对象的方法时从不锁定

有这么锁经验:

降低同步锁的粒度,分离读写,同步锁升级是怎么回事?

锁的4种状态:无锁状态、偏向锁状态、轻量级锁状态、重量级锁状态(等级从低到高)

(1) 偏置锁:

为什么要引入偏向锁?

因为HotSpot的作者经过大量研究发现,大部分时间没有锁竞争,而且往往一个线程多次获得同一个锁,所以如果每次都需要竞争锁,会增加很多不必要的成本。为了降低获取锁的成本,引入了偏向锁。

偏置锁升级

当线程1访问代码块并获取锁对象时,偏向锁的threadID会记录在java对象头和栈帧中,因为偏向锁不会主动释放锁,所以当线程1再次获取锁时以后需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(或者线程1获取锁对象),就不需要使用CAS加锁了并解锁;锁不会被主动释放,所以还是存储的线程1的threadID),那么需要检查Java对象头中记录的线程1是否存活,如果没有,则将锁对象重置为锁-free 状态,和其他线程(线程 2) 可以竞争将其设置为偏置锁;如果它存活,

取消偏向锁:

偏向锁默认开启,启动时间一般比应用启动慢几秒。如果你不想要这个延迟,你可以使用 -XX:BiasedLockingStartUpDelay=0;

如果不想要偏向锁,可以通过 -XX:-UseBiasedLocking = false; 来设置

(2) 轻量级锁

为什么要引入轻量级锁?

轻量级锁考虑了没有多少线程竞争锁对象,线程没有长时间持有锁的情况。因为阻塞一个线程需要CPU从用户态切换到内核态,成本很高。如果在阻塞后不久就释放锁,那么付出的代价将不值得。因此,此时c语言中内存的申请和释放,干脆不要阻塞线程,让它自旋。这等待锁被释放。

轻量级锁何时升级为重量级锁?

线程1在获取轻量级锁时,会先将锁对象的对象头MarkWord复制到线程1的栈帧中创建的空间来存储锁记录(称为DisplacedMarkWord),然后使用CAS去除标记对象头。内容替换为线程1存储的锁记录(DisplacedMarkWord)的地址;

如果在线程1复制对象头的同时(线程1 CAS之前),线程2也准备获取锁,将对象头复制到线程2的锁记录空间,但是当线程2 CAS的时候,发现那就是线程1放了对象头反而是线程2的CAS失败了,然后线程2尝试使用自旋锁等待线程1释放锁。

但是如果自旋时间过长就不行了,因为自旋消耗CPU,所以自旋次数是有限制的,比如10次或者100次,如果自旋次数达到线程1还没有释放锁,或者线程 1 仍在执行,线程 2 仍在旋转和等待。这时,另一个线程3来竞争锁对象。这时,轻量级锁将扩展为重量级锁。重量级锁会阻塞除拥有锁的线程之外的所有线程,从而防止 CPU 空闲。

同步和易失有什么区别?什么是死锁?如何防止死锁?什么是死锁

死锁是指多个进程竞争资源而导致的死锁(相互等待)。没有外力,这些进程将无法向前推进。例如,在某个计算机系统中,只有一台打印机和一台输入设备,进程P1正在占用输入设备,同时请求使用打印机,但此时打印机正在被进程P1占用。处理 P2,并且 P2 没有释放打印机。以前,另一个请求是使用 P1 占用的输入设备。这样,两个进程就无休止地等待对方,谁也不能继续执行。此时,两个进程陷入死锁状态。

死锁的原因

1.争夺系统资源

系统资源竞争导致系统资源不足,资源分配不当,导致死锁。

2.进程运行提前顺序不合适

进程在运行过程中,请求和释放资源的顺序不正确,会导致死锁。

死锁的四个必要条件

1. 互斥条件:一个资源一次只能被一个进程使用,即一个资源在一段时间内只被一个进程占用。此时,如果另一个进程请求该资源,则请求进程只能等待。

2.请求和持有条件:进程已经持有至少一个资源,但是又提出了新的资源请求,并且该资源已经被其他进程占用。此时,请求进程被阻塞,但它已经获得的资源被阻塞。坚持,稍等。

3.不可剥夺条件:一个进程获得的资源在用完之前不能被其他进程强行夺走,即只能由获得该资源的进程自己释放(只能释放主动)。

4.循环等待条件:形成端到端循环等待资源的几个进程之间的关系

这四个条件是死锁的必要条件。只要系统发生死锁,这些条件就必须成立,只要不满足上述条件之一,就不会发生死锁。

避免死锁

避免死锁的基本思想:系统动态检查系统能满足的进程发出的每一个资源请求,根据检查结果决定是否分配资源。如果分配后系统可能死锁,则不分配,否则分配。,这是一种动态策略,确保系统不会进入死锁状态。

如果操作系统能够保证所有进程在有限的时间内获得所需的全部资源,则系统处于安全状态,否则系统不安全。

© 版权声明
THE END
喜欢就支持一下吧
点赞0
分享
评论 抢沙发

请登录后发表评论