一 JAVA内存模型JMM
Java的内存模型,也就是JVM所设置的内存模型。Java内存模型分为主存储器(主内存)和工作存储器(工作内存),这里的存储器与计算机硬件所讲的不一样。
主存储器,就是实例位置所在的区域,所有的实例都存在主存储器内,并且实例的字段也位于这里。主存储器为所有的线程所共享,主内存主要对应于Java堆中对象的实例数据部分。
工作存储器,它是各个线程所拥有的独立专门的作业区。在工作存储器中,存在有主存储器中必要的拷贝,称为工作拷贝或者变量副本。工作内存对应于虚拟机中的部分区域。
每个线程都位于各自的工作存储器中,每个线程都不能直接的对存储器中字段进行引用或者赋值操作。
当线程欲引用字段的值时候,会一次将值从主存储器拷贝到工作存储器中,然后再引用该工作拷贝的字段。当同一个线程再次引用同一个字段的值时候,可能会引用刚才的工作拷贝,也可能会重新从主存储器拷贝到工作存储器。
当线程欲将值指定给字段的时候,会一次将值指定给位于工作存储器上的工作拷贝。指定完后,工作拷贝的内容则会映射到主存储器中。至于什么时候映射,是都JVM决定的。当同一个线程多次对于同一个字段指定的时候,线程可能只会对工作拷贝进行指定,也有可能会每次指定后,马上拷贝到主存储器中。
二 内存间的交互操作
主内存与工作内存之间的交互操作定义了8种原子性操作。具体如下:
1 lock(锁定):作用于主内存的变量,将一个变量标识为一条线程独占状态
2 unlock(解锁):作用于主内存的变量,将一个处于锁定状态的变量释放出来
3 read(读取):作用于主内存的变量,把一个变量的值从主内存传输到线程的工作内存中
4 load(载入):作用于工作内存的变量,把read传输的变量值放入或者拷贝到工作内存的变量副本
5 use(使用):作用于工作内存的变量,表示线程引用工作内存中的变量值,将工作内存中的一个变量的值传递给执行引擎
6 assign(赋值):作用于工作内存的变量,表示线程将指定的值赋值给工作内存中的某个变量。
7 store(存储):作用于工作内存的变量,把工作内存中的一个变量的值传送给主内存中
8 write(写入):作用于主内存的变量,将store传递的变量值放入到主内存中对应的变量里
三 Java同步机制
Java中同步包括:线程同步和内存同步。
synchronized:线程同步和内存同步
线程的同步指的就是利用synchronized设置一个临界区,使得只有同时一个线程在该临界区执行。由synchronized所指定的临界区,来控制线程的操作。
欲进入synchronized时候,线程的工作存储器如果有未映射到主存储器的工作拷贝,该内容就会被强制写入主存储器,并且会将工作存储器的工作拷贝全部丢弃清除掉。
欲退出synchronized时候,线程会将工作存储器中未映射到主存储器的工作拷贝强制写入主存储器中。但是并不会清除或丢弃自己的工作存储器。
在synchronized中,不管是方法还是代码块,内存同步仅仅会在线程“欲进入”与“欲退出”synchronized时候进行内存同步。如果是“在synchronized内部”或“正在synchronized外部”,不一定会有内存的同步。
Volatile:内存同步
对于关键字Volatile,它仅仅是进行内存的同步,并不会涉及线程的同步,利用Volatile修饰的字段可以允许多个线程同时访问。当线程欲引用volatile字段的值,就会从主存储器中拷贝到工作存储器里。对于指定给volatile字段值后,工作存储器的内容都会立刻马上映射到主存储器中。
对于Volatile修饰的变量,在读取的过程与非Volatile变量差不多,只是会在写入操作上慢一点
一个变量为Volatile类型具备的两种特性:
1 保证此变量对所有线程的可见性。指的就是当一个线程修改了这个变量的值后,新值对于其他线程来讲师马上可以看到的。
2 禁止指令重排序优化。由于指令会在执行中进行重排序进行优化,利用Volatile后,可以保证该指定不会被重新排序,也就是在程序中位于Volatile之前的先执行,位于Volatile之后的在它之后执行,不会混合到前面或者后面指令的重排序中。
一般仅仅在以下场合中选择使用volatiole:
1 对变量的写入操作不依赖于变量的当前值
2 变量不会与其它状态变量一起纳入不变性条件中
3 在访问变量的时候不需要进行加锁
由于long和double是64位的,JVM在处理这种类型的读写操作可以划分为两次的32位操作,所以必须要注意这两种类型的共享操作
四 原子性、可见性和有序性
Java内存模型对于并发处理都是基于原子性、可见性和有序性进行设计的。
原子性 Java内部6种基本类型都是采用原子性操作的,当需要扩大原子性操作,就可以利用Java内存模型中的lock和unlock来实现,这两种方法对应高层次的字节码指令是monitorenter和monitorexit,而这两个字节码指令反映到Java代码中就是同步synchronized
可见性 指的就是当一个线程修改了共享变量后,其他线程都马上看到这个变量值。在Java是利用volatile、synchronized以及final,前两种已经在前面说明了。对于final指的就是,凡是被final修饰的字段在构造器一旦被初始化完成后,那么其他线程就能看到这个final字段的值
有序性 Java运用的就是volatile和synchronized实现的,volatile保证了禁止指令重排序,保证了该指令在程序中原来的顺序。而synchronized保证了一个时刻仅允许一个线程对其进行lock操作。
五 happens-before先行发生原则
“先行发生”,可以主要用来判断在并发中数据是否存在竞争,线程是否安全。
“先行发生”,是Java内存模型中定义的两个操作之间的偏序关系。即操作A先行发生于操作B,那么就是在发生操作B之前,操作A产生的影响能够被操作B观察到。Java内存模型有几个先行发生的关系。在并发测试中,如果两个操作不再以下类别中,那么其实际执行就没有顺序保障,即线程是不安全的。
程序顺序规则:如果程序中操作A在操作B之前,那么在线程的内部中A操作将在B操作之前。
监视器规则:在监视器锁上的解锁操作先行发生于后面对于同一个锁的枷锁操作。
volatile变量规则:对于一个volatile变量的写操作先行发生于对该变量的读操作之前。
线程启动规则:Thread对象的start()方法先行发生于此线程的每一个动作。
线程结束规则:线程中所有的操作都先行发生于对此线程的终止测试。
线程中断规则:当一个线程在另一个线程上被调用interrupt时,必须在被中断线程检测到interrupt调用之前执行。
终结器规则:对象的构造器函数必须在启动该对象的终结器finalize()方法之前完成。
传递性:如果操作A在操作B之前执行,操作B在操作C之前执行,那么操作A必须在操作C之前执行
注意:凡是多个线程所共享的对象,会对对象的状态进行修改等操作的时候,一般由synchronized或volatile来保护。