Java EE初阶 --多线程2

一. 常见的锁策略

1.悲观锁和乐观锁

悲观锁: 总是假设是最坏的情况,认为锁竞争十分激烈,每次访问共享数据的时候,都认为别人回家修改它,所以每次访问共享数据的时候,都会先获取锁,其他线程想获取就得阻塞等待,进入到等待队列,工作流程为先加锁再操作

乐观锁: 总是假设是最好的情况,认为锁竞争不激烈,访问共享数据时认为别人不会修改数据,所以不对数据进行加锁,工作流程为先操作再进行检查冲突

2.重量级锁和轻量级锁

悲观锁和乐观锁是加锁时遇到的场景,而重量级锁和轻量级锁是解决这些场景的解决方案,重量级锁是应对悲观锁的场景,轻量级锁是应对乐观锁的场景。

3.挂起等待锁和自旋锁

**自旋锁:**是轻量级锁的典型实现,应用程序级别的,当加锁式发现竞争,不会进入到阻塞状态,而是进入到忙等(不断的申请锁,直到拿到锁为止,此过程中要不断的消耗CPU,但在乐观锁的场景下,可以短时间内拿到锁)的状态。

**挂起等待锁:**是重量级锁的典型实现,操作内核级别的,加锁时遇到竞争时,会进入到阻塞状态,进入到等待队列,不会持续消耗CPU,此时可以拿CPU做其他事,后续要操作内核进行唤醒了

4.普通互斥锁和读写锁

**互斥锁:**如Java中的 synchronized

**读写锁:**把读和写操作区分对待,Java标准库提供了ReentrantReadWriteLock类,实现了读写锁,其类下有ReentrantReadWriteLock.ReadLock类表示读锁和ReentrantReadWriteLock.WriteLock类表示写锁,并分别提供了lock和unlock方法进行加锁和解锁。其中读和读操作不会互斥,多个线程可以同时读,而读和写操作、写和写操作会互斥

5.可重入锁和不可重入锁(讲过)

6.公平锁和非公平锁

**公平锁:**遵从"先来后到",多个线程申请一个刚释放的锁时,谁先申请谁就先拿到锁

**非公平锁:**不遵从"先来后到",谁都有机会拿到锁

二.synchronized原理

1.synchronized遵从那些锁策略

synchronized始终是悲观锁具有自适应性 (JVM内部会统计锁竞争程度,根据锁竞争程度会从偏向锁→轻量级锁→重量级锁逐步升级)、可从入锁、非公平锁、普通互斥锁

2.加锁过程

JVM将synchronized关键字分为无锁、偏向锁、轻量级锁、重量级锁。会根据情况,进行依次升级

加锁过程解析:在未刚进入synchronized时为无锁状态,一旦进入到synchronized,不会真正加锁,而是对锁进行标记,此时由无锁转化成偏向锁,该操作比加锁轻量很多,如果其他线程没有来竞争该锁,最终该线程执行代码解锁过程只是把这个标记清除,如果其他线程来竞争该锁,该线程会在锁被其他线程拿到之前加上锁,此时就是真正加锁了,从偏向锁转化为轻量级锁,如果JVM发现该锁竞争十分激烈,轻量级锁就会转化为重量级锁

三.锁消除

是编译器优化的体现,编译器会自行判断某代码块是否需要加锁,如果不需要,即使用户自己加上synchronized,但在编译后生成的机器码消除。

四.锁粗化

锁粒度 指的是同步块保护的共享资源范围大小

锁粗化就是针对反复对细粒度的代码块加锁时,就有可能优化成一次加锁操作和解锁操作,此时锁的粒度就会变粗,这就是锁粗化。

五.CAS

1.什么是CAS

**CAS:**全称为Compare And Swap,意思为比较和交换。CAS本质上是CPU指令,操作系统对该指令进行了封装,并提供了api给C++来使用,而JVM又是基于C++实现的,所以JVM可以通过C++调用CAS操作了,单都是由JVM和标准库封装好了。

2.CAS伪代码

**注意:**该代码块不是原子的,而真实的CAS操作是原子操作

3.CAS的使用

3.1 实现了原子类

Java标准库提供了java.util.concurrent.atomic包,里面对各种类型(如int、long等类型)进行了封装,都是基于CAS操作实现的,如之前的count++涉及到线程安全问题,得进行加锁操作,而该包下的类基于CAS实现count++来确保线程安全问题,就不需要进行加锁操作,提高了性能。

包下的类和使用:

3.2 实现自旋锁

4.CAS的ABA问题

4.1 什么是CAS的ABA问题

假设某一次CAS操作中value为A,此时和oldValue,如果判断相等就认为value没有被修改,但有可能该value被修改过(有其他线程从A改为B,再由B改为A).这就是ABA问题。

4.2 CAS的ABA问题引起的BUG

如出栈操作,刚开始栈类有节点A和节点B,进行线程一进行读取栈顶对old进行赋值,打算进行CAS(head,old,expect)操作就挂起等待,此时线程2进行读取进行CAS操作判断相等后进行pop和push一个新的节点A^,但A^的内存地址分配时刚好分配到和A一样的地址,push完之后线程1CAS操作判断相等,CAS操作成功,弹出A^,但用户并不知道,认为该弹出的是A.这是异常情况,正常情况为不相等,CAS操作失败,重新循环读取head到old重新CAS操作,相等后再进行pop真正弹出新的节点.

4.3解决方案

给要修改的值,引⼊版本号.在CAS⽐较数据当前值和旧值的同时,也要⽐较版本号是否符合预期.

• CAS操作在读取旧值的同时,也要读取版本号.

• 真正修改的时候,

◦ 如果当前版本号和读到的版本号相同,则修改数据,并把版本号+1.

◦ 如果当前版本号⾼于读到的版本号.就操作失败(认为数据已经被修改过了).

六.Callable接口

该接口也可以定义一个任务,线程执行任务时都是调用无返回值的run,但对run方法进行了封装,调用时封装后的可以返回值,方便程序员可以借助多线程的方式计算结果

举例:

总结创建线程的写法:

1.继承Thread类(定义单独的类/匿名内部类)

2.实现Runnable接口(定义单独的类/匿名内部类/lambda)

3.实现Callable

4.线程池

七.ReentrantLock类

**作用:**可以重入互斥锁,和synchronized关键字类似

1.ReentrantLock类下的方法:

**lock()方法:**获取锁,如果获取不到锁就会死等

**trylock(时间)方法:**获取锁,如果获取不到锁,等到超时间结束就放弃获取锁

**unlock()方法:**解锁

2.ReentrantLock类和synchronized的区别

1.synchronized是一个关键字,是基于JVM内部实现的(大概率基于C++实现的),ReentrantLock是Java标准库的一个类,是基于JVM外部实现的(Java代码实现的)

2.synchronized关键字在申请锁失败情况下会死等,而ReentrantLock提供了一个超时间的加锁方法,可以不死等。

3.synchronized一直都是非公平锁,ReentrantLock类也默认为非公平锁,但ReentrantLock提供了修改为公平锁的构造方法,只要创建实例时传入true就为公平锁。

4.synchronized关键字会自动释放锁,而ReentrantLock类必须通过类下的unlock方法进行解锁,一般容易遗漏,得搭配finally来使用

5.synchronized关键字唤醒锁是通过wait-notify来进行唤醒随机的一个线程,而RenntrantLock类搭配Condition类使用,可以精准唤醒某个线程

八.信号量Semaphore

信号量 ,也可以表示**"可用资源的数量"**,本质上是一个计数器。

**作用:**可以协调多个线程/进程之间的资源分配

使用:

利用可用资源被用完再申请资源可以设置可用资源为1,达到锁的效果:

九.CountDownLatch类

在多线程的使用中经常将一个大任务分成一个个小任务多线程执行,但何时任务全部完成呢?通过CountDownLatch类可以知道。

使用:

十.在多线程中使用线程不安全的类的解决办法

1.在多线程中使用ArrayList

1)自己使用同步机制(使用synchronized关键字ReentrantLock类

2)Collections.synchronizedList(new ArrayLsit) 返回的List 中的方法都是带有synchronized修饰的

3)使用CopyOnWriteArrayList类,该类不涉及加锁,但每次修改数据时都会复制存储数据的容器,在新的容器上增删改查,在复制过程中读取数据就会从旧的数据读取,复制成功之后把新容器的引用赋值给旧容器的引用(此操作为原子的),使引用指向新容器。

2.在多线程中使用哈希表

在多线程中使用HashMap是线程不安全的,但是使用Hashtable是线程安全的,但Hashtable给所有public修饰的方法中加以synchronized关键字修饰,以达到线程安全,效率比较低,于是对Hashtable进行了优化,提供了ConcurrentHashMap类。

相关推荐
coderxiaohan20 分钟前
【C++】多态
开发语言·c++
gfdhy30 分钟前
【c++】哈希算法深度解析:实现、核心作用与工业级应用
c语言·开发语言·c++·算法·密码学·哈希算法·哈希
闲人编程39 分钟前
Python的导入系统:模块查找、加载和缓存机制
java·python·缓存·加载器·codecapsule·查找器
Eiceblue1 小时前
通过 C# 将 HTML 转换为 RTF 富文本格式
开发语言·c#·html
故渊ZY1 小时前
Java 代理模式:从原理到实战的全方位解析
java·开发语言·架构
匿者 衍1 小时前
POI读取 excel 嵌入式图片(支持wps 和 office)
java·excel
leon_zeng01 小时前
Qt Modern OpenGL 入门:从零开始绘制彩色图形
开发语言·qt·opengl
会飞的胖达喵1 小时前
Qt CMake 项目构建配置详解
开发语言·qt
ceclar1231 小时前
C++范围操作(2)
开发语言·c++
一个尚在学习的计算机小白1 小时前
java集合
java·开发语言