首页 > 编程知识 正文

java转php容易吗,java类型转换

时间:2023-05-03 08:29:37 阅读:48141 作者:4278

转自: http://blog.csdn.net/suifeng 3051/article/details/52611310

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

要深入了解Java并发编程,必须了解Java内存模型。 Java内存模型定义了多线程之间共享变量的可见性以及如何根据需要同步共享变量。 由于原始Java内存模型效率不高,因此Java 1.5版进行了重构,而当前的Java8继承了Java 1.5版。

关于并发编程

在并发编程领域,存在两个重要问题:线程之间的通信和同步。

线程之间的通信

线程通信是指线程之间通过什么机制交换信息。 在命令型编程中,线程间的通信机构有两种共享存储器和消息传递。

在共享内存的并发模型中,线程之间共享程序的共同状态,线程之间通过读写内存内的共同状态进行隐式通信。 典型的共享存储器通信方式通过共享对象进行通信。

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

线程之间的同步

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

在共享内存并行模型中,同步是显式进行的。 程序员必须明确指定方法或代码必须在线程之间互斥地执行。

在消息传递并发模型中,同步是隐式的,因为消息的发送必须早于消息的接收。

Java的同时执行采用了共享内存模型

Java线程之间的通信始终是隐式的,整个通信过程对程序员完全透明。 如果编写多线程程序的Java程序员不了解隐式执行的线程间通信的工作方式,他们很可能会遇到各种奇怪的内存可视性问题。

Java内存模型

在上述内容中,Java线程之间的通信采用了过共享存储器模型,但这里所说的共享存储器模型是Java存储器模型(简称为JMM ),JMM决定一个线程对共享变量的写入在另一个线程中何时可见。 抽象地看,JMM定义了线程和主内存之间的抽象关系。 线程之间的共享变量存储在主内存(main memory )中,每个线程都有专用的本地内存,而本地内存存储该线程以读写共享变量的副本。 本地内存是JMM的抽象概念,不实际存在。 它涵盖了缓存、写缓冲区、寄存器以及其他硬件和编译器的优化。

从上图可见,线程a和线程b之间的通信需要经过以下两个步骤:

1 .首先,线程a将由本地存储器a更新的共享变量刷新为主存储器。

2 .然后,线程b将在线程a之前更新的共享变量读取到主存储器中。

1

2

3

用示意图说明这两个步骤。

如上图所示,本地存储器a和b有主存储器中共享变量x的副本。 初始情况下,假设这三个内存的x值都为0。 线程a在执行时,将更新后的x的值(即使假设值为1 )暂时存储在其本地存储器a中。 如果线程a和线程b需要通信,则线程a首先将其本地内存中的修改后的x值刷新为主内存,此时主内存中的x值为1。 然后,线程b去主存储器读取线程a更新后的x值,线程b的本地存储器的x值也变为1。

总的来说,这两个步骤实际上是线程a向线程b发送消息,该通信过程必须通过主存储器。 JMM通过控制主内存与每个线程的本地内存之间的交互,使java程序员能够看到内存。

如上所述,Java内存模型只是一个抽象的概念。 那么,在Java中具体是如何工作的呢? 为了更好地理解上Java内存模型的工作原理,下面详细介绍JVM实现Java内存模型、硬件内存模型及其之间的桥接。

JVM在Java内存模型中的实现

在JVM内部,Java内存模型将内存分为两部分:线程堆栈区域和堆区域。 下图显示了JVM中Java内存模型的逻辑视图。

在JVM中执行的每个线程都有自己的线程栈,线程栈包含有关当前线程正在执行的方法调用的信息,也称为调用栈。 随着代码的执行,调用堆栈会不断变化。

线程还包含当前方法的所有局部变量信息。 一个线程只能读取自己的线程堆栈。 也就是说,线程中的局部变量不会显示在其他线程中。 每个线程中的局部变量都有自己的版本,因为即使两个线程运行相同的代码,也会在各自的线程栈中创建局部变量。

所有原始类型(布尔、字节、短整型、字符、整型、长整型、浮点型和双精度)的局部变量直接存储在线程堆栈中,这些值在线程之间是独立的。 对于原始类型的局部变量,一个线程可以将副本传递给另一个线程,但不能在它们之间共享。

堆区域包含由Java APP应用程序创建的所有对象信息。 无论在其中创建对象的线程如何,对象都包含原始类型的封装

类(如Byte、Integer、Long等等)。不管对象是属于一个成员变量还是方法中的本地变量,它都会被存储在堆区。

下图展示了调用栈和本地变量都存储在栈区,对象都存储在堆区:

 

一个本地变量如果是原始类型,那么它会被完全存储到栈区。

一个本地变量也有可能是一个对象的引用,这种情况下,这个本地引用会被存储到栈中,但是对象本身仍然存储在堆区。

对于一个对象的成员方法,这些方法中包含本地变量,仍需要存储在栈区,即使它们所属的对象在堆区。

对于一个对象的成员变量,不管它是原始类型还是包装类型,都会被存储到堆区。

Static类型的变量以及类本身相关信息都会随着类本身存储在堆区。

堆中的对象可以被多线程共享。如果一个线程获得一个对象的应用,它便可访问这个对象的成员变量。如果两个线程同时调用了同一个对象的同一个方法,那么这两个线程便可同时访问这个对象的成员变量,但是对于本地变量,每个线程都会拷贝一份到自己的线程栈中。

下图展示了上面描述的过程:

硬件内存架构

不管是什么内存模型,最终还是运行在计算机硬件上的,所以我们有必要了解计算机硬件内存架构,下图就简单描述了当代计算机硬件内存架构:

现代计算机一般都有2个以上CPU,而且每个CPU还有可能包含多个核心。因此,如果我们的应用是多线程的话,这些线程可能会在各个CPU核心中并行运行。

在CPU内部有一组CPU寄存器,也就是CPU的储存器。CPU操作寄存器的速度要比操作计算机主存快的多。在主存和CPU寄存器之间还存在一个CPU缓存,CPU操作CPU缓存的速度快于主存但慢于CPU寄存器。某些CPU可能有多个缓存层(一级缓存和二级缓存)。计算机的主存也称作RAM,所有的CPU都能够访问主存,而且主存比上面提到的缓存和寄存器大很多。

当一个CPU需要访问主存时,会先读取一部分主存数据到CPU缓存,进而在读取CPU缓存到寄存器。当CPU需要写数据到主存时,同样会先flush寄存器到CPU缓存,然后再在某些节点把缓存数据flush到主存。

Java内存模型和硬件架构之间的桥接

正如上面讲到的,Java内存模型和硬件内存架构并不一致。硬件内存架构中并没有区分栈和堆,从硬件上看,不管是栈还是堆,大部分数据都会存到主存中,当然一部分栈和堆的数据也有可能会存到CPU寄存器中,如下图所示,Java内存模型和计算机硬件内存架构是一个交叉关系:

 

当对象和变量存储到计算机的各个内存区域时,必然会面临一些问题,其中最主要的两个问题是:

1. 共享对象对各个线程的可见性

2. 共享对象的竞争现象

1

2

3

共享对象的可见性

当多个线程同时操作同一个共享对象时,如果没有合理的使用volatile和synchronization关键字,一个线程对共享对象的更新有可能导致其它线程不可见。

想象一下我们的共享对象存储在主存,一个CPU中的线程读取主存数据到CPU缓存,然后对共享对象做了更改,但CPU缓存中的更改后的对象还没有flush到主存,此时线程对共享对象的更改对其它CPU中的线程是不可见的。最终就是每个线程最终都会拷贝共享对象,而且拷贝的对象位于不同的CPU缓存中。

下图展示了上面描述的过程。左边CPU中运行的线程从主存中拷贝共享对象obj到它的CPU缓存,把对象obj的count变量改为2。但这个变更对运行在右边CPU中的线程不可见,因为这个更改还没有flush到主存中:

 

要解决共享对象可见性这个问题,我们可以使用java volatile关键字。 Java’s volatile keyword. volatile 关键字可以保证变量会直接从主存读取,而对变量的更新也会直接写到主存。volatile原理是基于CPU内存屏障指令实现的,后面会讲到。

竞争现象

如果多个线程共享一个对象,如果它们同时修改这个共享对象,这就产生了竞争现象。

如下图所示,线程A和线程B共享一个对象obj。假设线程A从主存读取Obj.count变量到自己的CPU缓存,同时,线程B也读取了Obj.count变量到它的CPU缓存,并且这两个线程都对Obj.count做了加1操作。此时,Obj.count加1操作被执行了两次,不过都在不同的CPU缓存中。

如果这两个加1操作是串行执行的,那么Obj.count变量便会在原始值上加2,最终主存中的Obj.count的值会是3。然而下图中两个加1操作是并行的,不管是线程A还是线程B先flush计算结果到主存,最终主存中的Obj.count只会增加1次变成2,尽管一共有两次加1操作。

要解决上面的问题我们可以使用java synchronized代码块。synchronized代码块可以保证同一个时刻只能有一个线程进入代码竞争区,synchronized代码块也能保证代码块中所有变量都将会从主存中读,当线程退出代码块时,对所有变量的更新将会flush到主存,不管这些变量是不是volatile类型的。

volatile和 synchronized区别

支撑Java内存模型的基础原理

指令重排序

在执行程序时,为了提高性能,编译器和处理器会对指令做重排序。但是,JMM确保在不同的编译器和不同的处理器平台之上,通过插入特定类型的Memory Barrier来禁止特定类型的编译器重排序和处理器重排序,为上层提供一致的内存可见性保证。

编译器优化重排序:编译器在不改变单线程程序语义的前提下,可以重新安排语句的执行顺序。

指令级并行的重排序:如果不存l在数据依赖性,处理器可以改变语句对应机器指令的执行顺序。

内存系统的重排序:处理器使用缓存和读写缓冲区,这使得加载和存储操作看上去可能是在乱序执行。

数据依赖性

如果两个操作访问同一个变量,其中一个为写操作,此时这两个操作之间存在数据依赖性。

编译器和处理器不会改变存在数据依赖性关系的两个操作的执行顺序,即不会重排序。

as-if-serial

不管怎么重排序,单线程下的执行结果不能被改变,编译器、runtime和处理器都必须遵守as-if-serial语义。

内存屏障(Memory Barrier )

上面讲到了,通过内存屏障可以禁止特定类型处理器的重排序,从而让程序按我们预想的流程去执行。内存屏障,又称内存栅栏,是一个CPU指令,基本上它是一条这样的指令:

保证特定操作的执行顺序。

影响某些数据(或则是某条指令的执行结果)的内存可见性。

编译器和CPU能够重排序指令,保证最终相同的结果,尝试优化性能。插入一条Memory Barrier会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。

Memory Barrier所做的另外一件事是强制刷出各种CPU cache,如一个Write-Barrier(写入屏障)将刷出所有在Barrier之前写入 cache 的数据,因此,任何CPU上的线程都能读取到这些数据的最新版本。

这和java有什么关系?上面java内存模型中讲到的volatile是基于Memory Barrier实现的。

如果一个变量是volatile修饰的,JMM会在写入这个字段之后插进一个Write-Barrier指令,并在读这个字段之前插入一个Read-Barrier指令。这意味着,如果写入一个volatile变量,就可以保证:

一个线程写入变量a后,任何线程访问该变量都会拿到最新值。

在写入变量a之前的写入操作,其更新的数据对于其他线程也是可见的。因为Memory Barrier会刷出cache中的所有先前的写入。

happens-before

从jdk5开始,java使用新的JSR-133内存模型,基于happens-before的概念来阐述操作之间的内存可见性。

在JMM中,如果一个操作的执行结果需要对另一个操作可见,那么这两个操作之间必须要存在happens-before关系,这个的两个操作既可以在同一个线程,也可以在不同的两个线程中。

与程序员密切相关的happens-before规则如下:

程序顺序规则:一个线程中的每个操作,happens-before于该线程中任意的后续操作。

监视器锁规则:对一个锁的解锁操作,happens-before于随后对这个锁的加锁操作。

volatile域规则:对一个volatile域的写操作,happens-before于任意线程后续对这个volatile域的读。

传递性规则:如果 A happens-before B,且 B happens-before C,那么A happens-before C。

注意:两个操作之间具有happens-before关系,并不意味前一个操作必须要在后一个操作之前执行!仅仅要求前一个操作的执行结果,对于后一个操作是可见的,且前一个操作按顺序排在后一个操作之前。

版权声明:该文观点仅代表作者本人。处理文章:请发送邮件至 三1五14八八95#扣扣.com 举报,一经查实,本站将立刻删除。