并发编程相关知识

1、并发编程领域的关键问题

    线程之间的通信

    线程间的同步

1.1 线程之间的通信

    线程之间的通信机制有两种,共享内存和消息传递。

    在共享内存的并发模型里,线程之间通过写-读内存中的公共状态来隐式进行通信,典型的共享内存通信方式就是通过共享对象进行通信。

    在消息传递的并发模型里,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,在java中典型的消息传递方式就是wait()和notify()

1.2 线程间的同步

    同步是指程序用于控制不同线程之间操作发生相对顺序的机制

2、计算机内存模型

    处理器要与内存交互才可以完成一项执行任务(如读取运算数据、存储运算结果等)

    早期计算机中cpu和内存的速度是差不多的,但在现代计算机中,cpu的指令速度远超内存的存取速度

2.1 引入高速缓存区

    所以现代计算机系统都不得不加入一层读写速度尽可能接近处理器运算速度的高速缓存(Cache)来作为内存与处理器之间的缓冲

    将运算需要使用到的数据复制到缓存中,让运算能快速进行,当运算结束后再从缓存同步回内存之中,这样处理器就无须等待缓慢的内存读写了

2.2 带来的问题

    引入高速缓存区,很好地解决了处理器与内存的速度矛盾,但是也为计算机系统带来更高的复杂度,缓存一致性问题(Cache Coherence)。

    在多处理器系统中,每个处理器都有自己的高速缓存,仅对当前处理器可见,而它们又共享同一主内存(MainMemory),

    当多个处理器的运算任务都涉及同一块主内存区域时,将可能导致各自的缓存数据不一致。

3、java 内存模型

    Java的并发采用的是共享内存模型

    JMM定义了Java 虚拟机(JVM)在计算机内存(RAM)中的工作方式,JVM是整个计算机虚拟模型,所以JMM是隶属于JVM的。

    JMM定义了线程和主内存之间的抽象关系:线程之间的共享变量存储在主内存(Main Memory)中,每个线程都有一个私有的本地内存(Local Memory),本地内存中存储了该线程需要用到的变量的副本。

3.1 JVM对Java内存模型的实现

    在JVM内部,Java内存模型把内存分成了两部分:线程栈区和堆区

    JVM中运行的每个线程都拥有自己的线程栈

    所有原始类型的局部变量都直接保存在线程栈当中,对于它们的值各个线程之间都是独立的。
    (boolean,byte,short,char,int,long,float,double)

    堆区包含了Java应用创建的所有对象信息,不管对象是哪个线程创建的,其中的对象包括原始类型的封装类(如Byte、Integer、Long等等)

    一个局部变量如果是原始类型,那么它会被完全存储到栈区。 一个局部变量也有可能是一个对象的引用,这种情况下,这个本地引用会被存储到栈中,但是对象本身仍然存储在堆区。

3.2 Java内存模型带来的问题

3.2.1 可见性问题

    CPU中运行的线程从主存中拷贝共享对象obj到它的CPU缓存,把对象obj的count变量改为2。

    但这个变更对于其他CPU中的线程不可见,因为这个更改还没有flush到主存中,要解决共享对象可见性这个问题,我们可以使用java volatile关键字或者是加锁

    线程A和线程B共享一个对象obj。假设线程A从主存读取Obj.count变量到自己的CPU缓存,同时,线程B也读取了Obj.count变量到它的CPU缓存,

    并且这两个线程都对Obj.count做了加1操作。此时,Obj.count加1操作被执行了两次,不过都在不同的CPU缓存中。

    不管是线程A还是线程B先flush计算结果到主存,最终主存中的Obj.count只会增加1次变成2,尽管一共有两次加1操作。 要解决上面的问题我们可以使用java synchronized代码块。

3.3 Java内存模型中的重排序

    重排序是为了提高执行效率

3.3.1 数据依赖性

    如果两个操作访问同一个变量,且这两个操作中有一个为写操作,此时这两个操作之间就存在数据依赖性。只要重排序两个操作的执行顺序,程序的执行结果就会被改变。

3.3.2 as-if-serial

    不管如何重排序,都必须保证代码在单线程下的运行正确,连单线程下都无法正确,更不用讨论多线程并发的情况

3.4 解决在并发下的问题

3.4.1 内存屏障

    禁止重排序

3.4.2 临界区

    临界区内的代码可以重排序

3.4.3 Happens-Before

    两个操作之间具有happens-before关系,并不意味着前一个操作必须要在后一个操作之前执行!

    happens-before仅仅要求前一个操作(执行的结果)对后一个操作可见,且前一个操作按顺序排在第二个操作之前

    as-if-serial语义保证单线程内程序的执行结果不被改变,

    happens-before关系保证正确同步的多线程程序的执行结果不被改变。

3.5 实现原理

    内存语义:可以简单理解为 volatile,synchronize,atomic,lock 之类的在 JVM 中的内存方面实现

3.5.1 volatile的两个特性

    (1)可见性
    对volatile修饰的变量的修改,会及时刷到主内存中,并让其他线程已读取的此变量失效,使其他线程可以看到此线程的执行结果

    (2)原子性
    对于任意volatile变量的读/写具有原子性,但是类似于volatile++这种操作,不具备原子性

    volatile写的内存语义如下:当写一个volatile变量时,JMM会把该线程对应的本地内存中的共享变量值刷新到主内存。

    volatile读的内存语义如下:当读一个volatile变量时,JMM会把该线程对应的本地内存置为无效。线程接下来将从主内存中读取共享变量。
        

3.5.2 volatile的实现原理

    有volatile变量修饰的共享变量进行写操作的时候会使用CPU提供的Lock前缀指令:

    将当前处理器缓存行的数据写回到系统内存

    这个写回内存的操作会使在其他CPU里缓存了该内存地址的数据无效。

3.5.3 锁

    当线程释放锁时,JMM会把该线程对应的本地内存中的共享变量刷新到主内存中。。

    当线程获取锁时,JMM会重新获取该线程对应的本地内存中的共享变量。

3.5.4 synchronized的实现原理

    使用monitorenter和monitorexit指令实现的:

    monitorenter指令是在编译后插入到同步代码块的开始位置,而monitorexit是插入到方法结束处和异常处

    每个monitorenter必须有对应的monitorexit与之配对

    任何对象都有一个monitor与之关联,当且一个monitor被持有后,它将处于锁定状态

3.5.5 锁一共有4种状态,级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态。

(1)偏向锁:大多数情况下,锁不仅不存在多线程竞争,而且总是由同一线程多次获得,为了让线程获得锁的代价更低而引入了偏向锁。无竞争时不需要进行CAS操作来加锁和解锁。

(2)轻量级锁:无竞争时通过CAS操作来加锁和解锁。(CAS(自旋锁)——是一种锁的机制,不是状态)

(2)重量级锁:真正的加锁操作

原文地址:https://www.cnblogs.com/jis121/p/11022416.html

时间: 2024-08-24 22:47:17

并发编程相关知识的相关文章

Java并发编程核心知识体系精讲

第1章 开宗明义[不看错过一个亿]本章一连串设问:为什么学并发编程?学并发编程痛点?谁适合学习本课?本课程包含内容和亮点?首先4大个理由告诉你为什么要学,其实源于JD岗位要求就不得不服了.其次5个痛点+12个亮点,是否说服你要死磕Java并发编程呢?... 第2章 跨越第一座山“线程八大核心”[适用于纵观全貌]八大核心-序章.从本章开始将带你攻克并发编程领域的“第一座大山”:多线程八大核心. 第3章 核心1:实现多线程的正确姿势[解读官方文档,够权威]相信很多小伙伴经常在各大技术博客或者论坛甚至

Java并发编程核心知识体系精讲 完整版

第1章 开宗明义[不看错过一个亿]本章一连串设问:为什么学并发编程?学并发编程痛点?谁适合学习本课?本课程包含内容和亮点?首先4大个理由告诉你为什么要学,其实源于JD岗位要求就不得不服了.其次5个痛点+12个亮点,是否说服你要死磕Java并发编程呢?... 第2章 跨越第一座山“线程八大核心”[适用于纵观全貌]八大核心-序章.从本章开始将带你攻克并发编程领域的“第一座大山”:多线程八大核心. 第3章 核心1:实现多线程的正确姿势[解读官方文档,够权威]相信很多小伙伴经常在各大技术博客或者论坛甚至

Java多线程视频教程并发编程面试知识

课程目录:  1-1.并发编程入门到实战课程简介1-2.什么是并发编程1-3.并发编程的挑战之频繁的上下文切换1-4.并发编程的挑战之死锁1-5.并发编程的挑战之线程安全1-6.并发编程的挑战之资源限制2-1.进程与线程的区别2-2.线程的状态及其相互转换2-3.创建线程的方式(上)2-4.创建线程的方式(下)2-5.线程的挂起及其恢复2-6.线程的中断操作2-7.线程的优先级2-8.守护线程3-1.什么是线程安全性3-2.从字节码角度剖析线程不安全操作3-3.原子性操作3-4.深入理解sync

并发编程相关面试题四

一.Java开发中用过哪些锁 1.乐观锁 乐观锁顾名思义,就是很乐观,每次去拿数据的时候都认为别人不会修改,所以不会上锁,但是在更新的时候会判断一下在此期间别人有没有去更新这个数据,可以使用版本号等机制.乐观锁适用于多读的应用类型,这样可以提高吞吐量,在Java中java.util.concurrent.atomic包下面的原子变量类就是使用了乐观锁的一种实现方式CAS(Compare and Swap 比较并交换)实现的 乐观锁适合读操作非常多的场景,不加锁会带来大量的性能提升: 乐观锁在Ja

java并发编程使用锁进行数据同步操作一

项目中总是出现招标项目超投的情况,最开始总是觉得应该使用框架Hibernate自带的并发策略中的乐观锁(version)解决问题,参考了很多网上的资料,也参考了Hibernate的帮助文档,由于对Hibernate乐观锁机制不了解,问题就一直没有解决. 最近在看Java并发编程相关知识,了解了些许并发,线程,锁的知识.想到了这个问题,曾经使用Synchroized关键字时总是苦于无法获取同一个对象,导致解决方案无效.这次采用的方案是:创建了静态的HashMap<Integer,Lock>,初始

聊聊高并发(五)理解缓存一致性协议以及对并发编程的影响

Java作为一个跨平台的语言,它的实现要面对不同的底层硬件系统,设计一个中间层模型来屏蔽底层的硬件差异,给上层的开发者一个一致的使用接口.Java内存模型就是这样一个中间层的模型,它为程序员屏蔽了底层的硬件实现细节,支持大部分的主流硬件平台.要理解Java内存模型以及一些处理高并发的技术手段,理解一些基本的硬件知识是必须的.这篇会说一下跟并发编程相关的一些硬件知识. 一个基本的CPU执行计算的过程如下: 1. 程序以及数据被加载到主内存 2. 指令和数据被加载到CPU的高速缓存 3. CPU执行

Java 面试知识点解析(二)——高并发编程篇

前言: 在遨游了一番 Java Web 的世界之后,发现了自己的一些缺失,所以就着一篇深度好文:知名互联网公司校招 Java 开发岗面试知识点解析 ,来好好的对 Java 知识点进行复习和学习一番,大部分内容参照自这一篇文章,有一些自己补充的,也算是重新学习一下 Java 吧. 前序文章链接: Java 面试知识点解析(一)--基础知识篇 (一)高并发编程基础知识 这里涉及到一些基础的概念,我重新捧起了一下<实战 Java 高并发程序设计>这一本书,感觉到心潮澎湃,这或许就是笔者叙述功底扎实的

Java 并发编程之图形界面应用程序及死锁问题

不知道为什么这本书还要讲一个界面应用程序,Java的界面做的很糟糕,效率低下,而且界面是java的弱项,可能是因为这里边是有一些并发编程的知识吧. 为什么GUI是单线程的 无论是Swing还是AWT都是单线程的.但它不仅限于在java中,在Qt,NexiStep,macOs CoCoa X windows以及其它环境中的GUI框架都是单线程的,许多人都曾经尝试编写多线程的GUI框架,但最终都由于竞态条件和死锁导致的稳定性问题而又重新回到单线程的事件队列模型:采用一个专门的线程从队列中抽取事件,并

java并发编程--Runnable Callable及Future

1.Runnable Runnable是个接口,使用很简单: 1. 实现该接口并重写run方法 2. 利用该类的对象创建线程 3. 线程启动时就会自动调用该对象的run方法 通常在开发中结合ExecutorService使用,将任务的提交与任务的执行解耦开,同时也能更好地利用Executor提供的各种特性 ExecutorService executor = Executors.newCachedThreadPool(); executor.submit(new Runnable() { pub