java内存模型,java memory model(JMM)

线程间的通信机制,蕴含两种形式:共享内存和消息传递

共享内存:线程之间通过共享程序/过程内存中的公共状态,从而进行通信.例如共享对象.

消息传递:通过明确的发送音讯来进行显示的通信.例如java中的wait()和notify()

*

线程间的同步


同步是指程序用于管制不同线程之间操作产生绝对程序的机制.

共享内存时,同步必须显示进行.即程序员必须指定某段代码在过程间互斥执行.

消息传递时,音讯的发送必然在接管之前,因而同步是隐式的.

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


java内存模型


从形象的角度来看,JMM定义了线程和主内存之间的形象关系;线程之间的共享变量存储在主内存(main memory)中,每个线程都有一个公有的本地内存(local memory),本地内存中存储了该线程以读/写共享变量的正本.本地内存是JMM的一个抽象概念,并不实在存在.

线程A与线程B之间如要通信的话,必须要经验上面2个步骤:

1.首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。

2.而后,线程B到主内存中去读取线程A之前已更新过的共享变量。

*

JVM对内存模型的实现


JVM在内存中创立两个区域,线程栈区和堆区

每个线程领有独立的线程栈(也称为Stack,虚拟机栈,栈),其中寄存着栈帧(也成为Stack Frame,办法栈).线程每调用一个办法就对应一个栈帧入栈,办法执行结束或者异样终止就意味着该栈帧出栈(销毁).栈帧中寄存以后办法的局部变量表(用于寄存局布变量)和操作数栈(用于算数运算中操作数的入栈和出栈).

栈帧的内存大小在编译时就曾经确定,不随代码执行而扭转.然而线程栈能够动静扩大,如果线程申请的栈深度大于虚拟机所容许的深度,将抛出StackOverflowError异样(即内存溢出);如果虚拟机栈能够动静扩大,如果扩大时无奈申请到足够的内存,就会抛出OutOfMemoryError异样(即内存溢出)

只有位于栈顶的栈帧才是以后执行栈帧,成为以后栈帧,对应的办法成为以后办法.

线程栈中的局部变量(具体来说是栈帧中的局部变量),对于其余线程不可见.在传递时也只是传递对应的正本值.如果局部变量时根本类型,那么间接存储其值,如果是援用类型,那么在栈(栈帧)中存储其堆地址,其具体内容寄存在堆中.

堆中的对象能够被多线程所共享,只有对应线程有其地址即可.

*

硬件内存架构


CPU从寄存器中读取操作数,寄存器从CPU缓存(可能有多级)中读取数据,CPU缓存从内存/主存中读取数据.

当CPU须要拜访内存时,理论过程是:内存将数据传递给CPU缓存,CPU缓存将数据传递给寄存器.当CPU须要写入数据到内存时,也是依照这个逆向流程,然而,CPU缓存只会在特定的工夫节点上将数据刷新到内存中.

所以会呈现上面代码中的问题:

public static void main(String\[\] args) throws InterruptedException { MyRunnable r = new MyRunnable();     new Thread(r).start();     new Thread(r).start();     new Thread(r).start();     new Thread(r).start();     new Thread(r).start();     new Thread(r).start();     new Thread(r).start();     new Thread(r).start();     new Thread(r).start();     new Thread(r).start();     new Thread(r).start(); } static class MyRunnable implements Runnable {     static int count = 0;        @Override public void run() {         while (true) if (count < 100) {             System.out.println(Thread.currentThread()                     + ":" + count++);             } else{                     System.out.println(Thread.currentThread()                     + " break :" + count);                 break;             }         } }

局部执行后果:

    Thread[Thread-3,5,main]:84    Thread[Thread-10,5,main]:99    Thread[Thread-5,5,main] break :100    Thread[Thread-0,5,main] break :100    Thread[Thread-1,5,main]:98    Thread[Thread-1,5,main] break :100    Thread[Thread-4,5,main]:97    Thread[Thread-4,5,main] break :100    Thread[Thread-2,5,main]:96    Thread[Thread-9,5,main]:95    Thread[Thread-6,5,main]:94    Thread[Thread-9,5,main] break :100    Thread[Thread-2,5,main] break :100

能够看到,局部线程曾经将count值加到100,后续仍有线程输入的值有余100,起因在于这部分线程在计算时,cpu是从cpu缓存中读取的count备份,而且此备份并非最新值.

执行时刻靠后的线程读取到"旧值"的线程称为脏读.

应用volatile关键字能够保障没有脏读,即保障变量的可见性.每个线程批改的后果能够第一工夫被其余线程看到.因为cpu不再从cpu缓存中读取数据而是间接从主存中读取.

然而依然会存在线程不平安的问题,起因在volatile仅保证数据可见性,然而不保障操作原子性(即一个操作或者多个操作要么全副执行并且执行的过程不会被任何因素打断,要么就都不执行).

上述程序的第25行,理论执行过程中须要读取count值,控制台输入,count值加1,返回.volatile仅保障后执行的线程读取到的值不会比先执行的线程读的值更"旧".然而可能存在一种状况:线程A读取count值(如果是50)后,工夫片失落,线程B拿到工夫片,读取count值(也为50)并残缺执行该办法(count值为51),而后线程A复原执行,因为曾经读取过count值所以不再执行,执行后果与线程B雷同(也为51),从而仍有线程不平安.

为了保障线程平安,仍须要应用锁或者同步代码块,因为在解锁之前,必然将相应的变量值刷新到内存中.

至于volatile,其更大的作用是避免指令重排序.