java内存模型

共享内存的方式,存储在堆中的实例域,静态域以及数组元素都可以在线程间通信。java内存模型控制一个线程对共享变量的改变何时对另一个线程可见。

线程间的共享变量存在主内存中,而对于每一个线程,都有一个私有的工作内存。工作内存是个虚拟的概念,涵盖了缓存,写缓冲区,寄存器以及其他的硬件和编译器优化,总之就是指线程的本地内存。存在线程本地内存中的变量值对其他线程是不可见的。

如果线程A与线程B之间如要通信的话,必须要经历下面2个步骤,如图所示: 1. 首先,线程A把本地内存A中更新过的共享变量刷新到主内存中去。 2. 然后,线程B到主内存中去读取线程A之前已更新过的共享变量。 

 

java跨线程获取实例_赋值

关于volatile变量

由于java的内存模型中有工作内存和主内存之分,所以可能会有两种问题:

(1)线程可能在工作内存中更改变量的值,而没有及时写回到主内存,其他线程从主内存读取的数据仍然是老数据

(2)线程在工作内存中更改了变量的值,写回主内存了,但是其他线程之前也读取了这个变量的值,这样其他线程的工作内存中,此变量的值没有被及时更新。

为了解决这个问题,可以使用同步机制,也可以把变量声明为volatile,volatile修饰的成员变量有以下特点:

(1)每次对变量的修改,都会引起处理器缓存(工作内存)写回到主内存。

(2)一个工作内存回写到主内存会导致其他线程的处理器缓存(工作内存)无效。

基于以上两点,如果一个字段被声明成volatile,java线程内存模型确保所有线程看到这个变量的值是一致的。

java虚拟机规范(jvm spec)中,规定了声明为volatile的long和double变量的get和set操作是原子的。这也说明了为什么将long和double类型的变量用volatile修饰,就可以保证对他们的赋值操作的原子性了

 

关于volatile变量的使用建议:多线程环境下需要共享的变量采用volatile声明;如果使用了同步块或者是常量,则没有必要使用volatile。

java内存模型与synchronized关键字

在线程进入synchronized块之前,会把工作存内存中的所有内容映射到主内存上,然后把工作内存清空再从主存储器上拷贝最新的值。而 在线程退出synchronized块时,同样会把工作内存中的值映射到主内存,但此时并不会清空工作内存。这样一来就可以强制其按照上面的顺序运行,以 保证线程在执行完代码块后,工作内存中的值和主内存中的值是一致的,保证了数据的一致性!  

所以由synchronized修饰的set与get方法都是相当于直接对主内存进行操作,不会出现数据一致性方面的问题。

关于CAS

CAS 操作包含三个操作数 —— 内存位置(V)、预期原值(A)和新值(B)。 如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值 。否则,处理器不做任何操作。无论哪种情况,它都会在 CAS 指令之前返回该 位置的值。(在 CAS 的一些特殊情况下将仅返回 CAS 是否成功,而不提取当前 值。)CAS 有效地说明了“我认为位置 V 应该包含值 A;如果包含该值,则将 B 放到这个位置;否则,不要更改该位置,只告诉我这个位置现在的值即可。”

为什么CAS可以用于同步?

例如,有一个变量i=0,Thread-1和Thread-2都对这个变量执行自增操作。 可能会出现Thread-1与Thread-2同时读取i=0到各自的工作内存中,然后各自执行+1,最后将结果赋予i。这样,虽然两个线程都对i执行了自增操作,但是最后i的值为1,而不是2。

解决这个问题使用互斥锁自然可以。但是也可以使用CAS来实现,思路如下:

自增操作可以分为两步:(1)从内存中读取这个变量的当前值(2)执行(变量=上一步取到的当前值+1)的赋值操作。

“比较并交换(CAS)”操作是原子操作,它使用平台提供的用于并发操作的硬件原语。

通过下面代码可以加深理解:

package com.jyq.multithread;

import java.util.List;
import java.util.ArrayList;
import java.util.concurrent.atomic.AtomicInteger;
import java.lang.Thread;

public class Counter {
	private AtomicInteger atomicInteger = new AtomicInteger(0);
	private int i = 0;

	// 使用CAS实现线程安全的计数器
	public void safeCount() {
		//用一个for循环,如果没有计数成功的话,会一直执行这段代码,知道计数成功break为止
		for (;;) {
			int i = atomicInteger.get(); //读取value值,赋给i,i在线程的工作内存中
			//将主内存中的值(current)与工作内存中的值i相比较,如果相等的话,说明工作内存中的i值仍然是value的最新值
			//计数运算对当前i操作没有问题,将value值设为i+1,因为value是violent的,所以写的时候也就写到了主内存
			boolean suc = atomicInteger.compareAndSet(i, i + 1); 
			if (suc) {
				break;
			}
		}
	}

	// 非安全的线程计数器
	public void count() {
		i++;
	}

	public static void main(String[] args) {
		final Counter cas = new Counter();
		List<Thread> ts = new ArrayList<Thread>();
		for (int j = 0; j < 100; j++) {
			Thread t = new Thread(new Runnable() {

				@Override
				public void run() {
					for (int i = 0; i < 10000; i++) {
						cas.safeCount();
						cas.count();
					}

				}
			});
			ts.add(t);
		}
		for (Thread t : ts) {
			t.start();
		}
		// 等待所有线程执行完成
		for (Thread t : ts) {
			try {
				t.join();
			} catch (InterruptedException e) {
				e.printStackTrace();
			}
		}
		System.out.println(cas.atomicInteger.get());
		System.out.println(cas.i);
	}
}