Kotlin进阶-6-重入锁+synchronized+volatile

2023-05-16

目录

1、介绍

2、线程的状态 

3、创建线程

4、线程同步

4.1、可重入锁

4.2、不可重入锁的实现

4.3、可重入锁的实现

4.4、Java中的可重入锁---ReentrantLock

4.5、同步方法---synchronized

5、volatile

5.1、Java 内存模型

5.1.1、原子性

5.1.2、可见性

5.1.3、有序性

5.2、volatile功能

5.2.1、volatile变量具有可见性

5.2.2、volatile不保证原子性

5.2.3、volatile保证有序性

5.3、正确使用volatile


1、介绍

进程:进程是操作系统结构的基础,是程序在一个数据集合上运行的过程,使系统进行资源分配和调度的基本单位。进程可以被看作程序的实体,同样,它也是线程的容器。

2、线程的状态 

Java线程在运行的声明周期中,可能处于6种不同的状态:

New:新创建状态-----线程被创建,但是还没调用start()方法。

Runnable:可运行状态-----一旦调用了start()方法,该线程就会处于Runnable状态。一个可运行状态的线程可能在运行,也可能不在运行,这取决于操作系统给该线程提供的运行的时间。

Blocked:阻塞状态-----表示该线程被锁阻塞,该线程暂时无法运行。

Waiting:等待状态------线程暂时不活动,并且不运行任何代码,这消耗最小的资源,直到其他线程通知它,它才会返回Runnable状态。

Timed Waiting:超时等待状态-----和Waiting 状态一样都会停止运行,但是和等待状态不同的是,它在指定的时间之后会自行返回Runnable状态。

Terminated:终止状态-----表示该线程已经执行结束。导致该线程终止有两种情况:1、run方法执行完毕正常退出;2、因为一个没有被捕获的异常而终止了run方法,导致了线程进入终止状态。

3、创建线程

线程的创建和运行一般有三种方式:

1、实现Thread的子类,重写它的run()方法;

2、实现Runnable接口,并且重写它的run()方法;

3、实现Callable接口,重写它的call()方法。Callable接口其实是Executor框架中的功能类,它和Runnable接口类似,但是比Runnable接口功能强大,主要表现在以下三点:

<1>Callable可以在运行结束后提供一个返回值,Runnable没有返回值

<2>Callable中的call()方法可以主动抛出异常

<3>运行Callable对象可以得到一个Future对象,Future表示异步计算的结果。由于线程属于异步计算模型,因此无法从别的线程中得到函数的返回结果,在这种情况下,就可以使用Future来监视目标线程调用call()的返回结果。当你调用Future的get()方法获取返回值的时候,当前线程会被阻塞,直到call()方法返回结果。

4、线程同步

在多线程应用中, 通常情况下,两个或者两个以上的线程需要共享对同一个数据的存取。如果两个线程存取相同的对象,并且每一个线程都调用了修改该对象的方法,这种情况通常被叫做竞争条件。而解决这种竞争情况的方法就是 锁机制。

在Java中,我们一般使用锁的方式是通过synchronized关键字来实现。但是这里我们先介绍以下可重入锁不可重入锁来让你对synchronized有一个更深的认识。

4.1、可重入锁

可重入锁:指的是以线程为单位,当一个线程获取对象A的锁之后,这个线程可以再次获取对象A上的锁,而其他的线程是不可以的。

synchronized 和   ReentrantLock 都是可重入锁。

可重入锁的作用:防止死锁。

实现原理:是通过为每个锁关联一个请求计数器和一个占有它的线程。当计数为0时,认为锁是未被占有的;线程请求一个未被占有的锁时,JVM将记录锁的占有者,并且将请求计数器置为1。

如果同一个线程再次请求这个锁,计数将递增;

每次占用线程退出同步块,计数器值将递减。直到计数器为0,锁被释放。

4.2、不可重入锁的实现

我们先来实现一下不可重入锁的实现,代码如下图:(这里实现用Java实现MyLock的原因是Kotlin中Any类无法调用wait()方法)

由上面的可重入锁的定义我们应该直到不可重入锁的定义:当一个线程获取对象A的锁之后,这个线程不可以再次获取对象A上的锁。

左侧是我们锁的实现,我们可以根据该锁的lock() 和unlock()方法来实现同步代码块,当一个线程第一次调用该锁的lock()方法时,会将isLock置为true,当另一个线程(或者自己)想再次调用lock()方法时,会进入阻塞状态,这也就和定义形成了照应,一个线程无法多次获取MyLock对象的锁。

输出结果如下:

从输出结果我们可以看出,线程Thread-0 只执行了add()方法的内容,当执行到decrease()的lock.lock()方法时,我们线程进入了阻塞的状态,而且一直阻塞下去,这就导致了死锁了状态,这也就解释了我们的可重入锁的作用为什么是防止死锁了。

4.3、可重入锁的实现

实现如下图:

它和不可重入锁的区别在于,如果是线程A执行了lock对象锁构成的同步代码块时,当该线程A再次执行获取lock对象的锁时,不会被阻塞,而是会继续执行,但是其他线程获取不到lock对象的锁。

输出结果也证明了它的定义。

4.4、Java中的可重入锁---ReentrantLock

Java中对于可重入锁的实现除了synchronized之外,还有ReentrantLock类,它的使用如下图:

我们可以通过lock()和unlock()方法来实现下同步代码块,使用try...finally 语句是必要的,因为在我们同步代码块执行异常的情况下,也应该正常释放锁。否则的话,其他线程将永远会被阻塞。

-------------------------------------------------------------------------------

对于ReentrantLock,当我们的线程在调用ReentrantLock构成的同步代码块时,进入死循环中时,可以通过条件对象来让该线程释放锁,给其他线程执行的机会。

如右下图绿色框中,当该线程执行内容不符合条件时,会进入到死循环中,这时候我们可以通过await()方法来让线程退出死循环,并且进入阻塞状态和放弃对锁的控制,让其他符合条件的线程进行执行。

那么你阻塞的线程怎么恢复执行呢?可以通过黄色框内的signalAll()来重新激活因为while循环条件不符合而进入阻塞的线程。

4.5、同步方法---synchronized

Lock和Condition接口为程序设计人员提供了高度的锁控制,然而大多数情况下, 并不需要那样的控制,并且可以使用一种嵌入到Java语言内部的机制。从Java 1.0版本开始,Java中的每一个对象都有一个内部锁 ,如果一个方法用synchronized声明,那么对象的锁将保护整个方法。

下图中,黄色框和绿色框内的内容是等价的。

在Kotlin中是没有Synchronized、Volatile关键字的,但是有他们的注解,注解出来的是同步的方法,对于同步代码块可以如下图那样编写。但是同步代码块是比较脆弱的,所以我们建议可以使用阻塞队列,或者上面讲到的Lock、Condition来实现同步代码。

5、volatile

有时仅仅为了读写一个或者两个实例域就使用同步的话,显得开销过大;而Volatile关键字为实例域的同步访问提供了免锁的机制。

在详细讲解volatile之前,我们先了解一下Java内存模型。

5.1、Java 内存模型

Java中堆内存用来存储对象实例,堆内存是所有线程的共享的运行时内存区域。

而局部变量、方法定义的参数则不会在线程之间共享。

Java内存模型定义了线程和主存之间的的抽象关系,线程之间的共享变量存储在主存中,每个线程都有自己的私有的本地内存,本地内存中存储了该线程共享变量的副本。

需要注意的是,主存和本地内存是Java内存模型的一个抽象概念,其实并不存在,它涵盖了缓存、写缓存区、寄存器等区域。

Java内存模型控制线程之间进行通行,它决定一个线程对主存共享变量的写入 何时对另一个线程可见。

----------------------------------------------------------------------------------------------

Java内存模型的抽象示意图如下:

那么线程A和线程B之间的通信过程:

线程A更新本地内存共享变量副本的值---->将该值刷新到主存中----->线程B从主存中去读取该共享变量的值---->更新线程B共享变量的副本

5.1.1、原子性

对于基本数据类型变量的读取和赋值操作是原子性操作,即这些操作是不可被中断的,要么执行完毕,要么就不执行。

对于上图三条语句,只有第一条语句是原子性操作;

语句2包含了两个操作:1、读取x的值;2、将x的值写入到工作内存中。这两个操作单独放的话就是原子性操作,但是合起来就不是了。

语句3包含了三个操作:1、读取x的值;2、将x的值+1;3、将x的值写入到工作内存中。

通过上面的介绍,我们应该直到一个语句包含多个操作的话,就不是原子性操作,只有简答的读取和赋值才是原子性操作。

java.util.concurrent.atomic包中有很多类使用了很高效的机器级指令(而不是使用锁)来保证其他操作的原子性。例如AtomicInteger类提供了方法incrementAndGet()和decrementAndGet,它们分别以原子方式将一个整数自增自减。可以安全地使用AtomicInteger类作为共享计数器而无需同步。

5.1.2、可见性

可见性,是指线程之间的可见性,一个线程修改的状态是对另一个线程是可见的。也就是说一个线程修改的结果,另一个线程可以马上看到。当一个共享变量被volatile修饰时,它会保证被修改的值立即更新到主存中,所以对其他线程是可见的。没有被volatile修饰的变量则无法保证变量的可见性,当其他线程读取该值的时候,该值有可能还没有被更新到主存中。

5.1.3、有序性

Java内存模型允许编译器和处理器对指令进行重排序,虽然重排序过程不会影响到单线程执行的正确性, 但是会影响到多线程并发执行的正确性。

这是可以通过volatile来保证有序性,除了volatile,也可以通过synchronized和Lock来保证有序性,我们知道synchronized和Lock构成的同步代码块,每个时刻只有一个线程执行,这相当于让多线程顺序地执行同步代码,从而保证了有序性。

5.2、volatile功能

当一个共享变量被vlatile修饰时,其就具备两个含义:

1、一个线程修改了变量的值,变量的新值对其他线程是立即可见的

2、禁止使用指令重排序

-------------------------------------------

什么是指令重排序呢?

重排序通常是编译器或者运行环境为了优化程序性能而采取的对指令重新排序执行的一种手段。重排序分为两类:编译期重排序和运行期重排序,分别对应着编译时和运行时环境。

5.2.1、volatile变量具有可见性

看下面的程序,如果按照正常的执行程序的话,我们线程1中的while循环应该每次都能在线程2改变isStop的值之后,退出这个死循环,执行count2++的语句,但是当我们执行一百次的时候,发现结果,并不是每次都能跳出while循环,这就是因为线程2在改变了isStop的值之后没有将值更新到主存中,这样线程1以为isStop还是false的状态,这样它就不会停止循环。

但是当我们用Volatile修饰该变量之后,就不会出现这种情况了。

5.2.2、volatile不保证原子性

看如下代码:

当你在多次执行该代码的时候,你会发现它每次的输出结果都是不一样的。

假设线程1读取了count1的原始值=0.然后开始自增操作,当自增到999的时候,线程1被阻塞了,这时候线程1还没有把5这个值写入到主存中,所以线程2去读取的时候,count1的值还是0,这时候线程2开始自增,当线程2自增结束,并将count1的值更新到了主存中,这时候线程1恢复了自增操作,并将1000的值也更新到了主存中,这时候你会发现总共进行了2000次自增操作,但是值还是1000。

前面讲过了 count1++语句不是原子性操作,它具有三个子操作步骤:1、读取x的值;2、将x的值+1;3、将x的值写入到工作内存中。

所以我们的volatile语句也不能保证对变量的操作是原子性的。

5.2.3、volatile保证有序性

volatile能禁止指令的重排序,因此volatile保证有序性。

volatile禁止指令重排序的含义:当程序执行到volatile变量的时候,在其前面的语句已经全部执行完成,并且结果对后面可见,而且该变量后面的语句都没有执行。

5.3、正确使用volatile

synchronized可以防止多个线程同时执行一段代码,这会阻塞一部分线程的执行,这样就会影响程序的执行效率。

而volatile在某些情况下的性能是优于synchronized的。

但是volatile无法替代synchronized,因为volatile无法保证操作的原子性。

--------------------------------------------------------------

通常情况下,我们使用volatile关键字要避开两种场景:

1、对变量的写操作依赖当前值;比如前面我们示例的自增操作。

2、该变量包含在具有其他变量的不变式中。如下图:我们的初始范围  0-5 ,但如果两个线程同时对该类进行执行的时候,比如setLower1(4),setUpper1(3),那么我们最后的范围将会变成  4-3  。这显然是不对的。

使用volatile的场景有很多,这里介绍两种常见的场景:

场景1:状态标志

当多线程执行该类的时候,我们需要对状态标志stop保持可见性,这样我们的运行才能实时保持正确的执行。这种情况如果使用sychronized的话显然要复杂的多。

场景2: 双重检查模式(DCL)

我们的单例模式经常会这样写,第一次判空是为了不必要的同步操作,第二次判断是只有在MyLock实例==null的时候才会去new一个实例出来,当多线程调用时,当进行这两次判空时,我们需要保证instance的可见性。

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

Kotlin进阶-6-重入锁+synchronized+volatile 的相关文章

  • 涉及优化器的局部变量构造和销毁

    如果我有这个代码 class A class B void dummy A a B b 我知道变量a and b将以相反的分配顺序销毁 b将首先被摧毁 然后a 但我可以确定优化器永远不会交换的分配和构造a and b 或者我必须使用vola
  • public open fun navigateUp() 的参数太多

    我在 Kotlin 中创建了一个新的 Android 项目 我还使用向导创建了一个新的导航抽屉活动 一如既往 没有任何东西是开箱即用的 以下行显示编译错误 val navController findNavController R id n
  • 如何创建无限间隔的 Observable 并在每个时间间隔发出新的对象?

    我正在尝试创建一个每秒都会发出新对象的 Observable 所以现在我只是压缩一个 Observable 它以 Observable 间隔从列表中发出有限数量的对象 val list1 mutableListOf
  • 如何在可组合项中使用 hilt 注入单例

    我正在尝试注入一个在可组合项内的 hiltmodule 中定义的单例类 我知道如何注入视图模型 但是单例类呢 Inject lateinit var mysingleton MySingletonClass 该代码在活动中运行良好 但将其从
  • 如何从 TextInputLayout 中删除底部填充

    第一张图就是我想要的 第二张图是我实现的 正如您所看到的 TextInputLayout 的底部有一个填充 这使得灰色背景溢出超过 edittext 行 请不要建议负边距或填充 因为它在较新的 API 中不起作用 这是我为第一个文本输入布局
  • 如何使用分页库在回收器视图中添加日期分隔符?

    经过大量搜索 我知道使用常规适配器是可能的 但我不知道如何使用分页库来做到这一点 我不需要代码 只是一个线索 Example 要添加分隔符 您基本上有两个选择 基于视图 您显式地将分隔符作为 项目 包含在列表中 并为这些分隔符定义新的视图类
  • 如何检查 SwitchPreference 的当前状态?

    我有一个SwitchPreference in my SettingsFragment kt根据它是否打开或关闭来更改图标和标题 这是代码 notificationsPreference onPreferenceChangeListener
  • Kotlin:乐趣与 val

    Kotlin 支持计算属性但我不确定何时使用它们 假设我有一堂课 class Car val color String 并有这个返回的函数true如果汽车是白色的 fun isWhite car Car Boolean return car
  • 从 kotlin 多平台项目创建 fat jar

    我最近从旧的 1 2 多平台切换到 1 3 不同之处在于 每个多平台模块都有一个 build gradle 文件 我有 5 个 因此配置少了很多 但是 我似乎无法配置使用 jvm 平台的所有依赖项创建可运行的 fat jar 我曾经在 jv
  • Spring Boot 2.1重复reactiveMongoTemplate bean

    我有以下 Spring Boot Configuration class Configuration EnableReactiveMongoRepositories class MongoConfiguration AbstractReac
  • Fat jar 内的 Kotlin JSR-223 ScriptEngineFactory - 找不到 kotlin 编译器 jar

    我有一个胖罐子 我试图在其中获取 Kotlin 的实例ScriptEngine 出于调试目的 我正在迭代可用的脚本引擎工厂并获取引擎 val scriptEngineManager ScriptEngineManager for facto
  • 如何在 Kotlin Android 中正确使用 URL

    我想用 override fun onCreate savedInstanceState Bundle super onCreate savedInstanceState setContentView R layout activity m
  • Kotlin 中的数据绑定无法与视图绑定一起使用

    安卓工作室3 6 构建 gradle buildscript ext kotlin version 1 3 50 repositories google jcenter dependencies classpath com android
  • LiveData无法观察到变化

    我正在更新一个ViewModel 中 DialogFragment 的 LiveData 值 但无法获取Fragment中的值 视图模型 class OtpViewModel private val otpUseCase OtpUseCas
  • 从 Kotlin 中的字符串中删除字符

    我正在尝试创建一个使用 Kotlin 中的字符串的 Android 计算器 如果逗号 或负数 已经包含一个 我不知道如何删除它 这是我的代码 它正确添加逗号 但如果用户再次单击则不会删除它 if buClickValue contains
  • Ktor 如何处理空响应

    我使用 Ktor 和这样的线路myentity client get url 从 API 响应获取并反序列化我的实体 当 API 返回某些内容时它工作正常 但是一旦 API 没有任何可返回的内容并发送 HTTP 204 响应 它就会失败并显
  • 为什么 Kotlin 数据类对象有反引号?

    这是我使用 Kotlin 数据类创建器插件创建的数据类 data class ResponseHealthInisghts val data List
  • 如何在发布版本中使用 Zebra EMDK?

    所以我有一台 Zebra MC330M 设备 我之前创建了一个应用程序 我想使用 PDA 内置的条形码扫描仪 如果在调试模式下运行我的应用程序 一切正常 我可以读取条形码 但是如果我创建暂存或释放版本 apk 则条形码读取器不活动 因此如果
  • Java - 同步方法导致程序大幅减慢

    我正在尝试了解线程和同步 我做了这个测试程序 public class Test static List
  • Kotlin 中是否有类似于 #region #endregion 的语法?

    我知道我可以使用 region endregion 包围 C 中的代码片段 Kotlin 中是否有类似的语法 谢谢 region MyRegion protected void Page Load object sender EventAr

随机推荐

  • iOS 关于UIAlertController常见使用方法

    Step 1 警告框 1 代码 1 创建提示窗口 参数1 xff1b Title xff1a 标题 参数1 xff1b message xff1a 提示内容 参数1 xff1b Style 风格 UIAlertControllerStyle
  • 2023年最新版kali linux安装教程

    一 前期准备 前排提醒 xff0c 文末有绿色版安装包免费领取 xff01 二 VMware虚拟机配置 1 打开vmware xff0c 点击创建新的虚拟机 2 选择自定义 高级 选项 xff0c 点击下一步 3 继续下一步 4 选择 稍后
  • CentOS7.1下 安装vncserver和删除vnc占有的端口

    今天给两台新服务器装CentOs7 1系统 xff0c 然后装VNCServer的时候感觉网上的教程要么复杂多此一举 xff0c 要么不清楚 xff0c 关于 list端口的部分都没讲 所以这里整理一下 xff0c 按着下面的顺序来就可以了
  • mac使用虚拟机(VirtualBox+centos7)搭建kubernetes(K8S)集群

    文章目录 说明一 环境准备1 配置主机网络2 配置磁盘空间3 安装虚拟机配置网络4 设置Linux环境 三台均需要设置 二 安装docker kubeadm kubelet kubectl 三台均需要设置 1 安装docker环境2 kub
  • .NET6入门:1.Windows开发环境搭建

    作为 NET的最新版本 NET6长期支持版已经发布 xff0c NET6宣称是迄今为止最快的 NET 那当然不能落下时代的潮流 xff0c 就让我们跟着文章进入 NET6的世界吧 1 NET6SDK下载 Download NET Linux
  • 音视频编解码原理(一) 封装格式和编码方式简介

    一 封装格式 要了解音视频编解码原理 xff0c 首先需要知道什么是封装格式 xff1f 所谓封装格式 xff0c 就是将已经编码压缩好的视频轨和音频轨按照一定的格式封装到一个文件中 xff0c 一般情况下 xff0c 不同的封装格式对应不
  • VS调试方法总结(二)

    通过结构化异常定位崩溃程序 程序崩溃时 xff0c 生成文本文件 xff0c 记录崩溃得堆栈信息 直接上代码 已经编译通过 xff0c 拷贝直接可用 h include lt Windows h gt include lt stdarg h
  • QT(C++) + OpenCV + Python库打包发布可执行EXE

    QT xff08 C 43 43 xff09 43 OpenCV 43 Python库打包发布可执行EXE 背景 最近写了一个操作界面 xff0c 不仅用到了OpenCV的函数 xff0c 还调用了一个python脚本 xff0c 所以这里
  • Linux 内存管理 页回收和swap机制

    页高速缓存和页写回机制 页是物理内存或虚拟内存中一组连续的线性地址 xff0c Linux内核以页为单位处理内存 xff0c 页的大小通常是4KB 当一个进程请求一定量的页面时 xff0c 如果有可用的页面 xff0c 内核会直接把这些页面
  • docker 创建 network,出现异常问题及解决

    最近在使用 docker 创建 network 时 xff0c 出现错误 xff1a Error response from daemon could not find plugin bridge in v1 plugin registry
  • 工作进度所占总进度的比例

    如果实现的功能模块全部完成为 或者说 工作进度 xff1a 100 那么 xff1a 1 产品原型全部完成 包括文档的整理 xff1a 15 2 UI设计全部完成 xff1a 10 3 后台全部完成 xff1a 25 4 前台全部完成 xf
  • Docx4J替换内容时,内容换行失败问题解决

    WordprocessingMLPackage wordMLPackage 61 WordprocessingMLPackage load new java io File templatePath MainDocumentPart doc
  • C语言经典例题-用4×4矩阵显示从1到16的所有整数,并计算每行、每列和每条对角线上的和

    编写一个程序 xff0c 要求用户 按任意次序 xff09 输入从1到16的所有整数 xff0c 然后用4 4矩阵的形式将它们显示出来 再计算出每行 每列和每条对角线上的和 include lt stdio h gt int main in
  • java中Map.hashCode()函数说明

    在java中 xff0c Map hashCode 函数是在具有一定工作积累后 xff0c 为了更好的成长不可避免需要研究的内容 首先 xff0c 我们先看下原始代码 xff1a static final int hash Object k
  • 不支持的特性: getMetaData,问题解决

    最近使用springboot 43 mybatis 时遇到 xff1a 不支持的特性 getMetaData 的异常 使用 xff1a 64 Options useGeneratedKeys 61 false 解决的该问题 xff1b 官方
  • jsp四种范围

    page代表是与一个页面相关的对象和属性 request代表是与 Web 客户机发出一个请求相关的对象和属性 session代表是与用于某个 Web 客户机的一个用户体验相关的对象和属性 application代表是与整个 Web 应用程序
  • Kotlin-17-等号比较(== 、===)

    目录 1 Java中的 61 61 2 Java中的 equals 3 两者的区别 3 对于基本数据类型的 61 61 比较 4 Kotlin中的 61 61 与 61 61 61 1 Java中的 61 61 Java中的 61 61 直
  • Kotlin-30-继承多个父类

    目录 1 Java中的继承 2 Kotlin中的继承 1 Java中的继承 Java中的类只能继承一个父类 xff0c 是无法实现继承多个父类 xff0c 但是一个类可以实现多个接口 Java中的接口是无法给函数添加函数体的 abstrac
  • 算法-9-快速排序

    目录 1 描述 2 特点 3 代码实现 3 1 切分函数partition 图示 4 性能 5 快速排序优化 xff08 小数组插入排序 xff09 6 快排优化 xff08 三向切分 xff09 1 描述 快速排序也是基于递归实现的 和归
  • Kotlin进阶-6-重入锁+synchronized+volatile

    目录 1 介绍 2 线程的状态 3 创建线程 4 线程同步 4 1 可重入锁 4 2 不可重入锁的实现 4 3 可重入锁的实现 4 4 Java中的可重入锁 ReentrantLock 4 5 同步方法 synchronized 5 vol