Java 线程/内存模型的缺陷和增强-Java基础-Java-编程开发

Scala语言的多线程编程 在现代软件开发中,随着多核处理器的普及,采用多线程编程的方式来提高程序运行效率已经成为一种趋势。Scala作为一种兼容Java的平台的多范式编程语言,具备了出色的并发编程能力。本文将深入探讨Scala语言的多线程编程,包括基础概念、常用库、最佳实践以及示例代码,帮助你更好地理解和使用Scala进行多线程开发。 阅读详情
<script type="text/javascript" src="http://pagead2.googlesyndication.com/pagead/show_ads.js"> </script>

本文是由JR主持写作的《J2SE进阶》一书的部分章节整理而成,《J2SE进阶》正在写作、完善阶段。您阅读后,有任何建议、批评,请 和我联系 ,或在 这儿留言 。《J2SE进阶》写作项目组感谢您阅读本文。


Java在语言层次上实现了对线程的支持。它提供了Thread/Runnable/ThreadGroup等一系列封装的类和接口,让程序员可以高效的开发Java多线程应用。为了实现同步,Java提供了synchronize关键字以及object的wait()/notify()机制,可是在简单易用的背后,应藏着更为复杂的玄机,很多问题就是由此而起。

一、Java内存模型

在了解Java的同步秘密之前,先来看看JMM(Java Memory Model)。
Java被设计为跨平台的语言,在内存管理上,显然也要有一个统一的模型。而且Java语言最大的特点就是废除了指针,把程序员从痛苦中解脱出来,不用再考虑内存使用和管理方面的问题。
可惜世事总不尽如人意,虽然JMM设计上方便了程序员,但是它增加了虚拟机的复杂程度,而且还导致某些编程技巧在Java语言中失效。

JMM主要是为了规定了线程和内存之间的一些关系。对Java程序员来说只需负责用synchronized同步关键字,其它诸如与线程/内存之间进行数据交换/同步等繁琐工作均由虚拟机负责完成。如图1所示:根据JMM的设计,系统存在一个主内存(Main Memory),Java中所有变量都储存在主存中,对于所有线程都是共享的。每条线程都有自己的工作内存(Working Memory),工作内存中保存的是主存中某些变量的拷贝,线程对所有变量的操作都是在工作内存中进行,线程之间无法相互直接访问,变量传递均需要通过主存完成。

图1 Java内存模型示例图

线程若要对某变量进行操作,必须经过一系列步骤:首先从主存复制/刷新数据到工作内存,然后执行代码,进行引用/赋值操作,最后把变量内容写回Main Memory。Java语言规范(JLS)中对线程和主存互操作定义了6个行为,分别为load,save,read,write,assign和use,这些操作行为具有原子性,且相互依赖,有明确的调用先后顺序。具体的描述请参见JLS第17章。

我们在前面的章节介绍了synchronized的作用,现在,从JMM的角度来重新审视synchronized关键字。
假设某条线程执行一个synchronized代码段,其间对某变量进行操作,JVM会依次执行如下动作:
(1) 获取同步对象monitor (lock)
(2) 从主存复制变量到当前工作内存 (read and load)
(3) 执行代码,改变共享变量值 (use and assign)
(4) 用工作内存数据刷新主存相关内容 (store and write)
(5) 释放同步对象锁 (unlock)
可见,synchronized的另外一个作用是保证主存内容和线程的工作内存中的数据的一致性。如果没有使用synchronized关键字,JVM不保证第2步和第4步会严格按照上述次序立即执行。因为根据JLS中的规定,线程的工作内存和主存之间的数据交换是松耦合的,什么时候需要刷新工作内存或者更新主内存内容,可以由具体的虚拟机实现自行决定。如果多个线程同时执行一段未经synchronized保护的代码段,很有可能某条线程已经改动了变量的值,但是其他线程却无法看到这个改动,依然在旧的变量值上进行运算,最终导致不可预料的运算结果。

二、DCL失效

这一节我们要讨论的是一个让Java丢脸的话题:DCL失效。在开始讨论之前,先介绍一下LazyLoad,这种技巧很常用,就是指一个类包含某个成员变量,在类初始化的时候并不立即为该变量初始化一个实例,而是等到真正要使用到该变量的时候才初始化之。
例如下面的代码:
代码1

class Foo {
private Resource res = null ;

public Resource getResource() {
if (res == null )
res = new Resource();
return res;
}
}

由于LazyLoad可以有效的减少系统资源消耗,提高程序整体的性能,所以被广泛的使用,连Java的缺省类加载器也采用这种方法来加载Java类。
在单线程环境下,一切都相安无事,但如果把上面的代码放到多线程环境下运行,那么就可能会出现问题。假设有2条线程,同时执行到了if(res == null),那么很有可能res被初始化2次,为了避免这样的Race Condition,得用synchronized关键字把上面的方法同步起来。代码如下:
代码2

Class Foo {
Private Resource res = null ;
Public synchronized Resource getResource() {
If (res == null )
res = new Resource();
return res;
}
}

现在Race Condition解决了,一切都很好。

N天过后,好学的你偶然看了一本Refactoring的魔书,深深为之打动,准备自己尝试这重构一些以前写过的程序,于是找到了上面这段代码。你已经不再是以前的Java菜鸟,深知synchronized过的方法在速度上要比未同步的方法慢上100倍,同时你也发现,只有第一次调用该方法的时候才需要同步,而一旦res初始化完成,同步完全没必要。所以你很快就把代码重构成了下面的样子:
代码3

Class Foo {
Private Resource res = null ;
Public Resource getResource() {
If (res == null ){
synchronized ( this ){
if (res == null ){
res = new Resource();
}
}
}
return res;
}
}

这种看起来很完美的优化技巧就是Double-Checked Locking。但是很遗憾,根据Java的语言规范,上面的代码是不可靠的。

造成DCL失效的原因之一是编译器的优化会调整代码的次序。只要是在单个线程情况下执行结果是正确的,就可以认为编译器这样的“自作主张的调整代码次序”的行为是合法的。JLS在某些方面的规定比较自由,就是为了让JVM有更多余地进行代码优化以提高执行效率。而现在的CPU大多使用超流水线技术来加快代码执行速度,针对这样的CPU,编译器采取的代码优化的方法之一就是在调整某些代码的次序,尽可能保证在程序执行的时候不要让CPU的指令流水线断流,从而提高程序的执行速度。正是这样的代码调整会导致DCL的失效。为了进一步证明这个问题,引用一下《DCL Broken Declaration》文章中的例子:
设一行Java代码:

Objects[i].reference = new Object ();

经过Symantec JIT编译器编译过以后,最终会变成如下汇编码在机器中执行:


0206106A mov eax,0F97E78h
0206106F call 01F6B210 ;为Object申请内存空间
; 返回值放在eax中
02061074 mov dword ptr [ebp],eax ; EBP 中是objects[i].reference的地址
; 将返回的空间地址放入其中
; 此时Object尚未初始化
02061077 mov ecx,dword ptr [eax] ; dereference eax所指向的内容
; 获得新创建对象的起始地址
02061079 mov dword ptr [ecx],100h ; 下面4行是内联的构造函数
0206107F mov dword ptr [ecx 4],200h
02061086 mov dword ptr [ecx 8],400h
0206108D mov dword ptr [ecx 0Ch],0F84030h

可见,Object构造函数尚未调用,但是已经能够通过objects[i].reference获得Object对象实例的引用。
如果把代码放到多线程环境下运行,某线程在执行到该行代码的时候JVM或者操作系统进行了一次线程切换,其他线程显然会发现msg对象已经不为空,导致Lazy load的判断语句if(objects[i].reference == null)不成立。线程认为对象已经建立成功,随之可能会使用对象的成员变量或者调用该对象实例的方法,最终导致不可预测的错误。

原因之二是在共享内存的SMP机上,每个CPU有自己的Cache和寄存器,共享同一个系统内存。所以CPU可能会动态调整指令的执行次序,以更好的进行并行运算并且把运算结果与主内存同步。这样的代码次序调整也可能导致DCL失效。回想一下前面对Java内存模型的介绍,我们这里可以把Main Memory看作系统的物理内存,把Thread Working Memory认为是CPU内部的Cache和寄存器,没有synchronized的保护,Cache和寄存器的内容就不会及时和主内存的内容同步,从而导致一条线程无法看到另一条线程对一些变量的改动。
结合代码3来举例说明,假设Resource类的实现如下:

Class Resource{
Object obj;
}

即Resource类有一个obj成员变量引用了Object的一个实例。假设2条线程在运行,其状态用如下简化图表示:

图2
现在Thread-1构造了Resource实例,初始化过程中改动了obj的一些内容。退出同步代码段后,因为采取了同步机制,Thread-1所做的改动都会反映到主存中。接下来Thread-2获得了新的Resource实例变量res,由于没有使用synchronized保护所以Thread-2不会进行刷新工作内存的操作。假如之前Thread-2的工作内存中已经有了obj实例的一份拷贝,那么Thread-2在对obj执行use操作的时候就不会去执行load操作,这样一来就无法看到Thread-1对obj的改变,这显然会导致错误的运算结果。此外,Thread-1在退出同步代码段的时刻对ref和obj执行的写入主存的操作次序也是不确定的,所以即使Thread-2对obj执行了load操作,也有可能只读到obj的初试状态的数据。(注:这里的load/use均指JMM定义的操作)

有很多人不死心,试图想出了很多精妙的办法来解决这个问题,但最终都失败了。事实上,无论是目前的JMM还是已经作为JSR提交的JMM模型的增强,DCL都不能正常使用。在William Pugh的论文《Fixing the Java Memory Model》中详细的探讨了JMM的一些硬伤,更尝试给出一个新的内存模型,有兴趣深入研究的读者可以参见文后的参考资料。

如果你设计的对象在程序中只有一个实例,即singleton的,有一种可行的解决办法来实现其LazyLoad:就是利用类加载器的LazyLoad特性。代码如下:

Class ResSingleton {
public static Resource res = new Resource();
}

这里ResSingleton只有一个静态成员变量。当第一次使用ResSingleton.res的时候,JVM才会初始化一个Resource实例,并且JVM会保证初始化的结果及时写入主存,能让其他线程看到,这样就成功的实现了LazyLoad。
除了这个办法以外,还可以使用ThreadLocal来实现DCL的方法,但是由于ThreadLocal的实现效率比较低,所以这种解决办法会有较大的性能损失,有兴趣的读者可以参考文后的参考资料。

最后要说明的是,对于DCL是否有效,个人认为更多的是一种带有学究气的推断和讨论。而从纯理论的角度来看,存取任何可能共享的变量(对象引用)都需要同步保护,否则都有可能出错,但是处处用synchronized又会增加死锁的发生几率,苦命的程序员怎么来解决这个矛盾呢?事实上,在很多Java开源项目(比如Ofbiz/Jive等)的代码中都能找到使用DCL的证据,我在具体的实践中也没有碰到过因DCL而发生的程序异常。个人的偏好是:不妨先大胆使用DCL,等出现问题再用synchronized逐步排除之。也许有人偏于保守,认为稳定压倒一切,那就不妨先用synchronized同步起来,我想这是一个见仁见智的问题,而且得针对具体的项目具体分析后才能决定。还有一个办法就是写一个测试案例来测试一下系统是否存在DCL现象,附带的光盘中提供了这样一个例子,感兴趣的读者可以自行编译测试。不管结果怎样,这样的讨论有助于我们更好的认识JMM,养成用多线程的思路去分析问题的习惯,提高我们的程序设计能力。

三、Java线程同步增强包

相信你已经了解了Java用于同步的3板斧:synchronized/wait/notify,它们的确简单而有效。但是在某些情况下,我们需要更加复杂的同步工具。有些简单的同步工具类,诸如ThreadBarrier,Semaphore,ReadWriteLock等,可以自己编程实现。现在要介绍的是牛人Doug Lea的Concurrent包。这个包专门为实现Java高级并行程序所开发,可以满足我们绝大部分的要求。更令人兴奋的是,这个包公开源代码,可自由下载。且在JDK1.5中该包将作为SDK一部分提供给Java开发人员。

Concurrent Package提供了一系列基本的操作接口,包括sync,channel,executor,barrier,callable等。这里将对前三种接口及其部分派生类进行简单的介绍。

sync接口: 专门负责同步操作,用于替代Java提供的synchronized关键字,以实现更加灵活的代码同步。其类关系图如下:

图3 Concurrent包Sync接口类关系图
Semaphore:和前面介绍的代码类似,可用于pool类实现资源管理限制。提供了acquire()方法允许在设定时间内尝试锁定信号量,若超时则返回false。

Mutex:和Java的synchronized类似,与之不同的是,synchronized的同步段只能限制在一个方法内,而Mutex对象可以作为参数在方法间传递,所以可以把同步代码范围扩大到跨方法甚至跨对象。

NullSync:一个比较奇怪的东西,其方法的内部实现都是空的,可能是作者认为如果你在实际中发现某段代码根本可以不用同步,但是又不想过多改动这段代码,那么就可以用NullSync来替代原来的Sync实例。此外,由于NullSync的方法都是synchronized,所以还是保留了“内存壁垒”的特性。

ObservableSync:把sync和observer模式结合起来,当sync的方法被调用时,把消息通知给订阅者,可用于同步性能调试。

TimeoutSync:可以认为是一个adaptor,其构造函数如下:
public TimeoutSync(Sync sync, long timeout){…}
具体上锁的代码靠构造函数传入的sync实例来完成,其自身只负责监测上锁操作是否超时,可与SyncSet合用。

Channel接口: 代表一种具备同步控制能力的容器,你可以从中存放/读取对象。不同于JDK中的Collection接口,可以把Channel看作是连接对象构造者(Producer)和对象使用者(Consumer)之间的一根管道。如图所示:

图4 Concurrent包Channel接口示意图

通过和Sync接口配合,Channel提供了阻塞式的对象存取方法(put/take)以及可设置阻塞等待时间的offer/poll方法。实现Channel接口的类有LinkedQueue,BoundedLinkedQueue,BoundedBuffer,BoundedPriorityQueue,SynchronousChannel,Slot等。

图5 Concurrent包Channel接口部分类关系图

使用Channel我们可以很容易的编写具备消息队列功能的代码,示例如下:
代码4

Package org.javaresearch.j2seimproved.thread;

Import EDU.oswego.cs.dl.util.concurrent.*;

public class TestChannel {
final Channel msgQ = new LinkedQueue(); //log信息队列

public static void main( String [] args) {
TestChannel tc = new TestChannel();
For( int i = 0;i < 10;i ){
Try{
tc.serve();
Thread .sleep(1000);
} catch ( InterruptedException ie){
}
}
}

public void serve() throws InterruptedException {
String status = doService();
//把doService()返回状态放入Channel,后台logger线程自动读取之
msgQ.put(status);
}

private String doService() {
// Do service here
return "service completed OK! " ;
}

public TestChannel() { // start background thread
Runnable logger = new Runnable () {
public void run() {
try {
for (; ; )
System .out.println( "Logger: " msgQ.take());
}
catch ( InterruptedException ie) {}
}
};
new Thread (logger).start();
}
}

Excutor/ThreadFactory接口: 把相关的线程创建/回收/维护/调度等工作封装起来,而让调用者只专心于具体任务的编码工作(即实现Runnable接口),不必显式创建Thread类实例就能异步执行任务。
使用Executor还有一个好处,就是实现线程的“轻量级”使用。前面章节曾提到,即使我们实现了Runnable接口,要真正的创建线程,还是得通过new Thread()来完成,在这种情况下,Runnable对象(任务)和Thread对象(线程)是1对1的关系。如果任务多而简单,完全可以给每条线程配备一个任务队列,让Runnable对象(任务)和Executor对象变成n:1的关系。使用了Executor,我们可以把上面两种线程策略都封装到具体的Executor实现中,方便代码的实现和维护。
具体的实现有: PooledExecutor,ThreadedExecutor,QueuedExecutor,FJTaskRunnerGroup等
类关系图如下:

图6 Concurrent包Executor/ThreadFactory接口部分类关系图
下面给出一段代码,使用PooledExecutor实现一个简单的多线程服务器
代码5

package org.javaresearch.j2seimproved.thread;
import java.net.*;
import EDU.oswego.cs.dl.util.concurrent.*;

public class TestExecutor {
public static void main( String [] args) {
PooledExecutor pool =
new PooledExecutor( new BoundedBuffer(10), 20);
pool.createThreads(4);
try {
ServerSocket socket = new ServerSocket (9999);
for (; ; ) {
final Socket connection = socket.accept();
pool.execute( new Runnable () {
public void run() {
new Handler ().process(connection);
}
});
}
}
catch ( Exception e) {} // die
}
static class Handler {
void process( Socket s){
}
}
}

限于篇幅,这里只是蜻蜓点水式的介绍了Concurrent包,事实上还有相当多有用的接口和类没有提到,我们的配套光盘中附带了Concurrent包和源代码,感兴趣的读者可以自行分析。

Java内存模型-清晰剖析 Java 内存模型(JMM)指定了Java 虚拟机(JVM)如何与计算机内存RAM) 配合使用。由于Java虚拟机与内存交互的时候,是通过Java线程去做的,因此Java内存模型其实就是针对Java的线程模型进行研究的,因此Java内存模型其实也叫“Java线程模型。Java 内存模型(JMM) 控制Java 中的线程如何与内存交互。它保证一个线程所做的更改对其他线程可见,即Java 内存模型(JMM) 可以指定不同线程如何以及何时可以看到其他线程写入共享变量的值,从而为安全的多线程提供框架。 阅读详情

相关推荐

【STM32】CubeMx系列教程之移植FreeRTOS

本文详细介绍了在STM32F429上通过CubeMX工具移植FreeRTOS的方法。主要内容包括:1)配置系统时钟源,将TIM1替换SysTick作为时钟基准;2)FreeRTOS的创建与配置,包括任务创建、堆栈大小设置等;3)关键配置参数说明,如堆大小建议设为RAM的50%-70%;4)常用函数如堆栈监控、任务挂起/恢复等。文章提供了从基础配置到深入优化的完整移植指南,适合STM32开发者参考使用。

JackieJin123的博客 788

Java内存模型

从Java代码到CPU指令的变化过程是怎样的?最开始,我们编写的Java代码,即*.Java文件在执行编译Javac命令后,从刚才的*.Java文件会变出一个新的Java字节码文件,即*.class文件JVM会执行刚才生成的*.class字节码文件,并把字节码文件转化为机器指令机器指令可以直接在CPU上执运行,也就是最终的程序执行。

Qynwang的博客 3953

解决Android UVCCamera stopPreview 报错 崩溃 闪退 问题

在使用libuvccamera库的UVCCamera时,经常在调用stopPreview关闭预览的时候,app直接崩溃闪退了,经过综合各种修改源码的方案,已解决这个问题,以下请参考: 首先找到android_usbfs.c,linux_usbfs.c两个原文件,在两个文件中都可以找到这段代码,按如下示例修改: 修改前: for (i = 0; i < num_urbs; i++) { if (urb == tpriv->iso_urbs[i]) { urb_idx = i + 1;

hanye2020的博客 3577

Java的内存模型

Java的内存模型定义了Java程序在计算机内存中的组织方式,包括线程之间的共享变量、内存的可见性、原子性等规则。Java内存模型旨在确保多线程环境下的可靠性和一致性。

QYgujingjing的专栏 1558

TMC5160电机驱动芯片实战:从模式选择到参数配置全解析

本文深入解析TMC5160电机驱动芯片的实战应用,涵盖三种核心工作模式的选择策略与参数配置。详细介绍了全功能运动控制、外部脉冲驱动及独立驱动模式的应用场景与配置要点,并提供了电流控制、静音优化、SPI通信及高级功能(如StallGuard2)的调优指南,帮助开发者充分发挥其高精度、低噪音性能。

weixin_29179311的博客 323

HyperMesh实战:5种复杂载荷场景下的field映射技巧(附TCL脚本)

本文详细介绍了HyperMesh在5种复杂载荷场景下的field映射技巧,包括轴承载荷的圆柱坐标系映射、粉末压力中的切向力处理、实验数据到有限元模型的场映射、多工况载荷的组合映射以及局部细化网格的边界条件传递。通过实战案例和TCL脚本,帮助工程师高效解决有限元分析中的载荷映射难题,提升工作效率。

fanta的博客 295

【Java】Java内存模型

​JMM是Java内存模型(),简称JMM。它本身只是一个抽象的概念,并不真实存在,它描述的是一种规则或规范,是和多线程相关的一组规范。通过这组规范,定义了程序中对各个变量(包括实例字段,静态字段和构成数组对象的元素)的访问方式。需要每个JVM的实现都要遵守这样的规范,有了JMM规范的保障,并发程序运行在不同的虚拟机上时,得到的程序结果才是安全可靠可信赖的。如果没有JMM内存模型来规范,就可能会出现,经过不同JVM翻译之后,运行的结果不相同也不正确的情况。​ 计算机在执行程序时,每条指令都是在CPU。

qq_44544908的博客 918

保姆级教程:用Seurat 5.0.1搞定单细胞测序数据从质控到细胞注释的全流程

本文提供了一份详细的Seurat 5.0.1单细胞测序数据分析教程,涵盖从数据质控到细胞注释的全流程。通过R代码示例和可视化分析,帮助用户掌握单细胞RNA测序数据处理的核心技术,包括数据标准化、降维聚类和细胞类型注释,适用于生物信息学研究和临床数据分析。

weixin_29067143的博客 200

java 线程缺陷_Java 线程/内存模型的缺陷和增强

shyguy原创Java在语言层次上实现了对线程的支持。它提供了Thread/Runnable/ThreadGroup等一系列封装的类和接口,让程序员可以高效的开发Java多线程应用。为了实现同步,Java提供了synchronize关键字以及object的wait()/notify()机制,可是在简单易用的背后,应藏着更为复杂的玄机,很多问题就是由此而起。一、Java内存模型在了解Java的同步...

weixin_35275671的博客 197

SAP 固定资产采购简介

SAP 固定资产采购是指企业通过采购流程获取长期使用并记录为固定资产的物品。固定资产是企业为长期使用而购置的资产,如机器设备、办公设备、建筑物、车辆等,通常这些资产的使用期限较长,并且在资产负债表上记录。在SAP系统中,固定资产采购流程通常与物料管理(MM)模块和固定资产管理(FI-AA)模块相结合。这个过程涉及从需求的提出、采购订单的创建,到货物接收、发票校验和资产激活的多个步骤。

我小时候很黑的博客 1731

紫光展锐面试——FPGA/IC设计面试面经案例总结

自我介绍;研究生的主要研究方向是什么?为什么想要应聘数字芯片相关的工作?简单介绍下数字IC设计流程。逻辑综合主要做什么?你有实际做过逻辑综合工作吗?你了解SS corner和FF corner吗?综合时应该使用哪个corner?综合时需要考虑hold吗?为什么?提高时钟频率对setup和hold有什么影响?为什么?hvt、rvt/svt、lvt有什么区别?c40、c50是指什么概念?有什么区别?你对EDA工具有过了解吗?

FPGA/MATLAB学习教程/源码/项目合作开发 1565

java 线程缺陷_Java线程/内存模型的缺陷和增强 - 编程入门网

Java线程/内存模型的缺陷和增强时间:2007-11-05Java在语言层次上实现了对线程的支持。它提供了Thread/Runnable/ThreadGroup等一系列封装的类和接口,让程序员可以高效的开发Java多线程应用。为了实现同步,Java提供了synchronize关键字以及object的wait()/notify()机制,可是在简单易用的背后,应藏着更为复杂的玄机,很多问题就是由此而...

weixin_42098251的博客 194

ZYNQ Petalinux编译系统启动文件实战指南

本文提出的混合编译加速方案实测可将编译时间从45分钟缩短至18分钟,QSPI双启动方案已在工业控制领域成功应用。Xilinx官方UG1144文档(最新v2023.1版)正点原子《ZYNQ嵌入式开发实战》开源项目Meta-Petalinux(GitHub趋势项目)未来展望:随着Versal平台的普及,Petalinux将支持更多AI加速器原生驱动,值得持续关注。

QQ_778132974的博客 813

Java 线程/内存模型的缺陷和增强

Java 线程/内存模型的缺陷和增强 转载自UML软件工程组织 本文是由JR主持写作的《J2SE进阶》一书的部分章节整理而成,《J2SE进阶》正在写作、完善阶段。您阅读后,有任何建议、批评,请和我联系,或在这儿留言。《J2SE进阶》写作项目组感谢您阅读本文。 Java在语言层次上实现了对线程的支持。它提供了Thread/Runnab

Dony的专栏 1165

LinePhone3.2.7(android端最新版本) sip客户端

SIP客户端For android:LinePhone3.2.7(最新版本),官网地址:http://www.linphone.org/

jre9下载 jdk9

(Java SE Runtime Environment 9.0) 9.0u175 官方最新版 【x86|x64】

学习日志:JMM(Java 内存模型)

JMM(Java 内存模型)主要定义了对于一个共享变量,当另一个线程对这个共享变量执行写操作后,这个线程对这个共享变量的可见性。要想理解透彻 JMM(Java 内存模型),我们先要从CPU 缓存模型和指令重排序说起!ava 是最早尝试提供内存模型的语言,其主要目的是为了简化多线程编程,增强程序可移植性的。CPU 可以通过制定缓存一致协议(比如 MESI协议open in new window)来解决内存缓存不一致性问题。为了提升执行速度/性能,计算机在执行程序代码的时候,会对指令进行重排序。

weixin_46117680的博客 1139

SAP B1中文培訓教材_SDK

公司内部培训资料,对开发工具 ,DIAPI, UIAPI,Add-ON有详细解说

Java 线程/内存模型的缺陷和增强(转)

Java在语言层次上实现了对线程的支持。它提供了Thread/Runnable/ThreadGroup等一系列封装的类和接口,让程序员可以高效的开发Java多线程应用。为了实现同步,Java提供了synchronize关键字以及object的wait()/notify()机制,可是在简单易用的背后,应藏着更为复杂的玄机,很多问题就是由此而起。   一、Java内存模型   在了解Java的...

googitest的博客 134

efm32 入门.pdf

本PDF共计22页,介绍的是操作驱动API,下载与否自己斟酌。

java高并发编程(多线程详解)-多线程基础

java高并发编程(多线程详解)-多线程基础什么是线程创建线程的两种方式线程的生命周期Runable接口的引入以及策略模式 什么是线程 创建线程的两种方式 线程的生命周期 Runable接口的引入以及策略模式 ...

qq_35866165的博客 1177
上一篇: 很不错的JAVA学习笔记-Java基础-Java-编程开发
下一篇: PKI、SET、SSL介绍-Java基础-Java-编程开发
java169
博客等级 码龄19年 27粉丝 2568原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

个

红包个数最小为10个

元

红包金额最低5元

当前余额3.43元 前往充值 >
需支付:10.00元
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付元
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值