代码编织梦想

多任务处理在现代计算机操作系统中几乎已是一项必备的功能了。在许多情况下,让计算机同时去做几件事情,不仅是因为计算机的运算能力强大了,还有一个很重要的原因是计算机的运算速度与它的存储和通信子系统速度的差距太大,大量的时间都花费在磁盘I/O、网络通信或者数据库访问上。如果不希望处理器在大部分时间里都处于等待其他资源的状态,就必须使用一些手段去把处理器的运算能力“压榨”出来,否则就会造成很大的浪费,而让计算机同时处理几项任务则是最容易想到、也被证明是非常有效的“压榨”手段

 关于可见性

在多核多线程环境中,为了提升运行时数据访问性能,经常会使用多层缓存策略。这种架构也就带来了共享变量可见性问题,每个核或线程都有自己的本地副本,对于共享变量的读写操作可能不会被其它核或线程知晓,也就是不可见了。



 Java内存模型

Java的世界也有属于它自己的内存模型,Java内存模型(Java Memory Model),简称JMM。由于Java被定义成一种跨平台的语言,所以在内存的描述上面也要能是跨平台的,Java虚拟机试图定义一种统一的内存模型,能将各种底层硬件及操作系统的内存访问差异进行封装,使Java程序在不同硬件及操作系统上都能达到相同的并发效果。它描述了程序中各个变量之间的关系,包括实例域、静态域、数据元素及在实际计算机系统中将变量存储到内存和从内存中取出变量的底层细节。  

为更好理解JMM的工作机制,我们通过下图进行理解。从整体上看有几个比较重要的概念:主内存、工作(本地)内存、共享变量、共享变量副本、线程等。首先看主内存与工作内存及他们的关系,主内存保存了Java程序的所有变量,当然这个变量不包括局部变量和方法参数。工作内存则包含了这些共享变量的副本。其次是线程与工作内存的关系,每个线程都有一个属于自己的工作内存,不同线程之间的工作内存是互相不可见的,且线程对变量的操作也只能是针对自己的工作内存。最后是关于线程之间的通信机制,线程之间不可直接传递。假如某个线程对一个变量进行了重新赋值,那么该如何让另一个条线程知道呢?线程A将变量改变反应到主存中,线程B再从主存中读取,这样才能完成线程之间的通信。



 JMM主要操作

JMM定义了八个主要的内存操作来完成工作内存与主存的通信。假如一条线程准备对一个变量进行新的赋值操作,它可能会先用lock操作锁住主内存中的某个变量,不让其他线程获得此变量的锁,直至使用unlock操作释放该变量的锁。接着使用read操作将变量从主存读到工作内存中,紧接着load操作将得到的变量值放到工作内存中的变量副本。use操作则将变量值传给线程执行引擎进行运算操作,assign操作把新的变量值从线程执行引擎中传递到工作内存。store操作则把变量值从工作内存传送到主存中,接着write操作将得到的值写入主存相应的变量中,最后使用unlock操作释放变量的锁。



 JMM可见性

在Java内存模型中,如果一个线程更改了共享变量的值,其他线程能马上知道这个更改,则我们说这个变量具有可见性。一般来说有四种方式能保证变量的可见性,分别为volatile、synchronized、final和锁。  

首先谈谈volatile,被此关键词声明的变量,每当有任何更改时都将立即同步到主存中,而每个线程要使用这个变量时都要重新从主存刷新到工作内存,这样就确保了变量的可见性。当然,普通变量最终也会同步到主存,再由主存同步到每个线程的工作内存,只是这个最终可能比较“长久”,不能保证可见性。  

其次,关于synchronized,由于synchronized底层也是通过锁进行实现,所以synchronized和锁的本质是一样的。当一个线程释放一个锁时,将会强制刷新工作内存中的变量值到主存中。而当另一个线程获取此锁的时候将会强制重新装载此变量值。当然这两个线程获取的是同一个锁,这样就保证了变量的可见性。  

最后,被final声明的变量一旦完成初始化,其他线程就能看到这个final变量。其实,可见性其实可以看成是一种机制,线程在进入/退出同步块程序时,它将发送/接收一个变量的更改。

 JMM有序性

有序性指在线程内看方法的执行,所有的指令都是有序的,都按照一种串行方式执行。而在线程内观察其他线程,所有指令都是无序的,指令都可能交叉执行。Java中提供了volatile和synchronized两个关键词保证线程之间操作的有序性,而这个有序性仅仅是相对的,volatile禁止指令重排序,synchronized则保证持有同一个锁的同步块只能串行运行。

 JMM原子性

Java内存模型保证了read、load、assign、use、store、write等操作具有原子性,我们可以认为除了long和double类型外,对其他基本数据类型所对应的内存单元的访问读写都是原子的。但由于这个原子性的颗粒度太小,通常情况下我们需要更大颗粒度的原子性,这时就需要用锁来保证了。



 总结

JMM可以说是Java的基础,也是Java多线程的基础,它的定义将直接影响JVM及Java多线程实现的机制。要想深入了解多线程并发中的相关问题现象,对Java内存模型的深入研究是必不可少的。它的定义必须考虑下面几个方面,其一是如何更加有效地提高线程的性能效率;其二是如何将底层物理硬件及操作系统的差异屏蔽掉提供统一的对外概念;最后是如何使它的模型既严谨又宽松,保证语义不会产生歧义和一些优化扩展。

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。 本文链接: https://blog.csdn.net/li1669852599/article/details/111151254

Java多并发之锁机制-爱代码爱编程

目录 一、Lock接口1.1 什么是锁?1.2 Lock对比synchronized关键字及常用API1.3 Lock接口源码二、队列同步器2.1 什么是队列同步器2.2 队列同步器的接口与示例三、重入锁3.1 什么是重入锁3.2 实现重进入四、读写锁4.1 什么是读写锁4.2 读写锁的接口与示例4.3 锁降级五、Condition接口5.1 什么

直接插入排序 (Java实现)-爱代码爱编程

/** * 直接插入排序 最好 O(n) 最坏 O(n^2) 平均 O(n^2) 稳定 */ public class MyInsertSort { public static void main(String[] args) { int arr[] = {5, 9, 7, 3, 5, 4, 1, 6};

网络安全(二)安全基础-爱代码爱编程

安全世界观 互联网本来是安全的,自从有看研究安全的人之后,互联网就不安全了。 web安全简史 起初,研究计算机系统和网络的人,被称其为 Hacker ,他们对计算机有这深入的了解,因此往往能够发现其中的问题。Hacker 在中国按照音译,被称为 黑客。在计算机安全领域,黑客是一群破坏规则、不喜欢被约束的人,因此总想着能够找到系统的漏洞,已获得一些规则

手写tomcat-爱代码爱编程

github地址 一。浏览器发送请求http://localhost:8080 返回hello world到浏览器 或者跳转到一个页面 跳转页面其实也是往浏览器返回页面内容(先读取页面的内容) @Data public class MyTomcat { private int port = 8080; public MyTomcat

LeetCode之两数之和-爱代码爱编程

LeetCode之两数之和 给定一个整数数组 nums 和一个目标值 target,请你在该数组中找出和为目标值的那两个整数,并返回他们的数组下标。 你可以假设每种输入只会对应一个答案。但是,数组中同一个元素不能使用两遍。 **方法一:暴力枚举** **思路及算法** 枚举数组中的每一个数 x,寻找数组中是否存在 target - x。

省市区三级联动显示-爱代码爱编程

省市区三级联动,数据库根据id规律存储,得到以下形式的数据 { “list”: { “first”: [ { “id”: “110000”, “name”: “北京”, “ssqename”: “”, “second”: [ { “id”: “110100”, “name”: “市辖区”, “ssqename”: “”, “third”: [ { “id

Java多并发之锁机制-爱代码爱编程

目录 一、Lock接口1.1 什么是锁?1.2 Lock对比synchronized关键字及常用API1.3 Lock接口源码二、队列同步器2.1 什么是队列同步器2.2 队列同步器的接口与示例三、重入锁3.1 什么是重入锁3.2 实现重进入四、读写锁4.1 什么是读写锁4.2 读写锁的接口与示例4.3 锁降级五、Condition接口5.1 什么

volatile底层原理-爱代码爱编程

一段代码来演示问题背景volatile解决内存可见性问题Java内存模型原子操作总线加锁太慢?MESI缓存一致性协议(总线嗅探机制)彻底掌握volatile底层原理1、多线程环境下共享变量的线程安全问题   点击运行,会有如下输出: thread1 action... thread2 action... thread2 end 可以知道的是,线

SpringBoot定时调度Scheduled默认配置(单线程)导致的业务延迟-爱代码爱编程

项目后台组件运用了Schedule每分钟启动一个job把数据发送到kafka(生产者),通过kafka的负载均衡分发到消费者中。在某个夜黑风高的夜晚,运维GG通过监控发现kafka写入出现每分钟不连续的现象,在没有数据写入的时段,消费线程一直处于等待状态。由于除了生产者任务job之外,还在存在其他定时job,随着业务的发展,其他job的数据量上升之后,加上

判断线程池中线程的状态-爱代码爱编程

第一种 Semaphore又称信号量,是操作系统中的一个概念,在Java并发编程中,信号量控制的是线程并发的数量。 public Semaphore(int permits) 其中参数permits就是允许同时运行的线程数目; 下面先看一个信号量实现单线程的例子,也就是permits=1: package concurrent.semaphore;

预习java线程同步锁-爱代码爱编程

线程同步 1.多线程同步 多个线程同时读写同一份共享资源时,可能会引起冲突,所以引入线程“同步”机制,即各线程间要有先来后到。 2.排队+锁 (1)几个线程之间要排队,一个个对共享资源进行操作,而不是同时进行操作; (2)为了保证数据在方法中被访问时的正确性,在访问时加入锁机制。 3.同步加锁 使用synchronized(同步监视器)关键字同步方法或代

多线程基础-5-Lock显示锁-爱代码爱编程

多线程 5 Lock显式锁 在 JDK5 中增加了Lock锁接口, 有 ReentrantLock 实现类,ReentrantLock 锁称为可重入锁, 它的功能比 synchronized多. 5.1 锁的可重入性 锁的可重入性是指,当一个线程获得一个对象锁后,再次请求该对象锁时是可以获得该对象的锁的. 5.2 ReentrantLock 5