===================================================================
我们都知道,当我们通过多线程方式尝试修改同一数据时,一般都可能引发线程安全问题,但当我们通过多线程方式尝试读取同一数据时,一般不会引发线程安全问题,因此,我们可以根据读和写的不同场景来给读和写操作分别加上不同的锁。
Java当中的synchronized不会对读和写进行区分,默认使用后线程都是互斥的
以Java为例,在标准库中存在这样一个类ReentrantReadWriteLock
源代码如下
public class ReentrantReadWriteLock
implements ReadWriteLock, java.io.Serializable {
private static final long serialVersionUID = -6992448646407690164L;
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
/** Performs all synchronization mechanics */
final Sync sync;
/**
-
Creates a new {@code ReentrantReadWriteLock} with
-
default (nonfair) ordering properties.
*/
public ReentrantReadWriteLock() {
this(false);
}
该类中提供了两个方法:
public ReentrantReadWriteLock.WriteLock writeLock() { return writerLock; }
此方法可以创建出一个读锁实例
public ReentrantReadWriteLock.ReadLock readLock() { return readerLock; }
此方法可以创建出一个写锁实例
某个线程被读锁修饰后,这两个线程之间不会互斥,而是完全同时并发执行,一般将读锁用于线程读取数据比较多的场景;而当某个线程被写锁修饰后,这两个线程会互斥,一个线程会执行,而另一个线程会阻塞等待,因此必须是一个线程执行完了,另一个线程才会执行,一般用于修改数据比较多的场景
=========================================================================
锁的核心特性 “原子性”,这样的机制追根溯源是 CPU 这样的硬件设备提供的
1.CPU 提供了 “原子操作指令”。
2. 操作系统基于 CPU 的原子指令,实现了 mutex 互斥锁.
3. JVM 基于操作系统提供的互斥锁。实现了 synchronized 和 ReentrantLock 等关键字和类。
注意:synchronized 并不仅仅是对 mutex 进行封装, 在 synchronized 内部还做了很多其他的工作
1.重量级锁依赖了OS提供的mutex,的开销一般很大,往往是通过内核来完成的
2.轻量级加锁一般不使用mutex,开销一般比较小,一般通过用户态就能直接完成
我们可以类比一个生活中的例子,当去银行办理业务时,如果是通过用户在银行工作人员的指导下自己在窗口外完成,那么效率会比较高,就像计算机中的用户态一样。而当我们把自己的业务交给银行相关人员去完成时,由于银行工作人员的闲忙时间是不可控的,因此无法保证效率,就好比计算机中的内核态。
===================================================================
当两个线程为了完成任务同时竞争一把锁时, 拿到锁的那个线程会立马执行任务,而没拿到就会阻塞等待,当一个线程把锁释放后,另一个线程不会被立即唤醒,而是等操作系统将其进行一系列的调度到CPU中的操作才能被唤醒然后执行任务,这种锁叫做挂起等待锁,线程在抢锁失败后进入阻塞状态,放弃 CPU,需要过很久才能再次被调度。但实际上,大部分情况下,虽然当前抢锁失败,但过不了很久,锁就会被释放,所以没必要就放弃 CPU。这个时候就可以使用自旋锁来处理这样的问题。
自旋锁的伪代码为:while (抢锁(lock) == 失败) {}
如果获取锁失败,就会立即再尝试获取锁,无限循环,直到获取到锁为止。第一次获取锁失败, 第二次的尝试会在非常短的时间内到来,一旦锁被其他线程释放, 就能第一时间获取到锁
自旋锁是一种典型的轻量级锁的实现方式,它没有放弃 CPU, 不涉及线程阻塞和调度,一旦锁被释放,就能第一时间获取到锁,这样会大大提高代码的执行效率,但如果锁被其他线程持有的时间比较久, 那么就会持续地消耗 CPU 资源。(而挂起等待的时候是不消耗 CPU 的)
因此,我们应该注意自旋锁的适用场合:
-
如果多个线程执行任务时锁的冲突比较低,或者线程持有锁的时间比较短,此时使用自旋锁比较合适
-
如果某个线程任务对CPU比较敏感,且不希望吃太多CPU资源,那么此时就不太适合使用自旋锁。
注意:synchronized自身已经设置好了自旋锁和挂起等待锁,会根据不同的情况自动选择最优的使用方案
========================================================================
若有三个线程 A,B,C。
A先尝试获取锁,获取成功了,因为只有一把锁,所以B和C线程都会阻塞等待,那么如果A用完了锁后,B和C线程哪一个会最先获取到锁呢?
-
公平锁:遵守先来后到原则,因为B线程比C线程来的早一点,所以B线程先获取到锁
-
非公平锁:没有先来后到原则,B和C线程获取到锁的概率是随机的
操作系统内部的线程调度就可以视为是随机的,如果不做任何额外的限制,锁就是非公平锁。如果要想实现公平锁,就需要依赖额外的数据结构(比如队列) 来记录线程们的先后顺序。公平锁和非公平锁没有好坏之分, 关键还是看适用场景(大部分情况下非公平锁就够用了,但当我们希望线程的调度时间成本是可控的,那么此时就需要用到公平锁了)
注意:synchronized为非公平锁
==========================================================================
(1)实例一
在介绍可重入锁和不可重入锁之前,大家先来思考一个问题,为什么Java中的main函数要用static来修饰?
public class Test {
public static void main(String[] args) {
}
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录大纲截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且后续会持续更新
如果你觉得这些内容对你有帮助,可以添加V:vip1024b 备注Java获取(资料价值较高,非无偿)
总结
阿里伤透我心,疯狂复习刷题,终于喜提offer 哈哈~好啦,不闲扯了
1、JAVA面试核心知识整理(PDF):包含JVM,JAVA集合,JAVA多线程并发,JAVA基础,Spring原理,微服务,Netty与RPC,网络,日志,Zookeeper,Kafka,RabbitMQ,Hbase,MongoDB,Cassandra,设计模式,负载均衡,数据库,一致性哈希,JAVA算法,数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。
2、Redis学习笔记及学习思维脑图
3、数据面试必备20题+数据库性能优化的21个最佳实践
base,MongoDB,Cassandra,设计模式,负载均衡,数据库,一致性哈希,JAVA算法,数据结构,加密算法,分布式缓存,Hadoop,Spark,Storm,YARN,机器学习,云计算共30个章节。
[外链图片转存中…(img-EfklPJsA-1711593539169)]
2、Redis学习笔记及学习思维脑图
[外链图片转存中…(img-vHJBZsvm-1711593539169)]
3、数据面试必备20题+数据库性能优化的21个最佳实践
[外链图片转存中…(img-u8dsbEq5-1711593539170)]