首先,先理清Java内存模型和Java内存结构的区别
Java内存结构是指JVM的内存结构,Java的运行是由Java虚拟机支撑的(Java Virtual Machine)
堆是存储类实例和数组的,通常是内存中最大的那一块,实例就是实例出来的对象,那么数组在堆区也很好理解,在Java中,数组也可以当作一个实例化的对象。
虚拟机栈保存局部变量和部分处理结果,并在方法调用和返回中起作用。
存储每个类的结构,例如运行时的常量池,字段和方法数据,构造函数的代码,类初始化和接口初始化的特殊方法
与虚拟机栈基本相似,区别在于虚拟机栈为虚拟机执行Java方法服务,而本地方法栈是Native方法服务。
是最小的一块内存区域,通常是保存当前JVM指令地址
是方法去的一部分,包含多种常量,范围从编译时已知的数字到必须在运行时间隙的方法和字段引用
JMM最重要的三点:重排序,原子性,内存可见性
int a=5;
int b=10;
a=a+10;
假如没重排序
load a
set a 5
store a
load b
set b 10
store b
load a
set a 15
store a
但是重排序后的效果
load a
set a 5
set a 15
store a
load b
set b 10
store b
可以看出,这两段执行指令节省了一次store和load,大大提高了执行效率,所以总结三种情况
编译器优化,例如有了a,把涉及到a变量的运算放到一起执行执行效率会变得更高。
CPU通过执行乱序提高整体的执行效率
内存中的重排序,但并不是真正意义上的重排序,举个例子,假如给线程A赋值给a变量1,线程B读取a变量,这时候线程A把a变量变成2,线程B并没有感知到a变量已经发生了改变,这表面上就发生了重排序一样。
除了long和double 其余变量都具有原子性操作
加了volatile后所有变量读写同样具备原子性操作
concurrent.Atomic包中的一部分类时具有原子性的
Non-Atomic Treatment of double and long
For the purposes of the Java programming language memory model, a single write to a non-volatile long or double value is treated as two separate writes: one to each 32-bit half. This can result in a situation where a thread sees the first 32 bits of a 64-bit value from one write, and the second 32 bits from another write.
Writes and reads of volatile long and double values are always atomic.
Writes to and reads of references are always atomic, regardless of whether they are implemented as 32-bit or 64-bit values.
Some implementations may find it convenient to divide a single write action on a 64-bit long or double value into two write actions on adjacent 32-bit values. For efficiency’s sake, this behavior is implementation-specific; an implementation of the Java Virtual Machine is free to perform writes to long and double values atomically or in two parts.
Implementations of the Java Virtual Machine are encouraged to avoid splitting 64-bit values where possible. Programmers are encouraged to declare shared 64-bit values as volatile or synchronize their programs correctly to avoid possible complications.
JVM在此提到,当读写64bit的字符时,long,double类型要一劈为二,变成32bit进行读写,如果发生其他线程对这个值的读操作,就可能读到一个不完整的值。但是在实际开发中,好像不对double或者long加上volatile关键字也无济于事,因为,通常来说读取半个变量这个情况还是特别少见的。
值得注意的时原子操作+原子操作!=原子操作
就拿银行转账来说,存钱和取钱一定是两个原子操作,但是中间穿插了点事务进去,他就不会变成原子操作,并且,第一次存款失败获成功也不会影响第二次的取款操作。
至于为什么多线程执行i++会产生原子性不存在的问题,会不会可以用这个证明int这个类型不具有原子性
答案:当然是否定的,int这个类型是具有原子性的,但是组合起来就不具有原子性了。
public class Visibility {
int x = 0;
public void write() {
x = 1;
}
public void read() {
int y = x;
}
}
在上述案例中,write是将x值赋成1,read方法则是将x的值读出来赋给y,假如两个线程进行这样操作,要分几种情况
read + write
y=0
x=1
执行完毕
write + read
x=1
y=1
执行完毕
再看执行顺序
线程->工作内存->主内存
由于不可见的原因,所以A和B线程是互不感知的,只有刷到主内存的时候才知道真正的值,很容易造成脏读。
public class VolatileDemo implements Runnable {
static volatile int virtualI = 0;
static AtomicInteger realI = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new VolatileDemo());
Thread thread2 = new Thread(new VolatileDemo());
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(virtualI);
System.out.println(realI.get());
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
this.virtualI++;
realI.incrementAndGet();
}
}
}
1986
2000
上面的代码片段是volatile的virtualI++和具有原子性的AtomicInteger的incr,从结果中我们可知一个结论
被volatile修饰的变量同样没有原子性
那么,加了volatile的变量到底有什么作用,前面谈到一个词,可见性,我们可以将volatile设置成一个全局变量的布尔值,这样线程都可以看到变更
public class VolatileDemo implements Runnable {
static volatile boolean flag=false;
static volatile int virtualI = 0;
static AtomicInteger realI = new AtomicInteger(0);
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new Thread(new VolatileDemo());
Thread thread2 = new Thread(new VolatileDemo());
thread1.start();
thread2.start();
thread1.join();
thread2.join();
System.out.println(virtualI);
System.out.println(realI.get());
}
@Override
public void run() {
for (int i = 0; i < 1000; i++) {
this.virtualI++;
realI.incrementAndGet();
}
if (!flag){
flag=true;
System.out.println( Thread.currentThread().getName()+"flag设置为true");
}else {
System.out.println( Thread.currentThread().getName()+"flag已设置为true,所以无法再重新设置");
}
}
}
Thread-0flag设置为true
Thread-1flag已设置为true,所以无法再重新设置
1911
2000
volatile和syncchronized的区别在于volatile处于半同步锁的状态,它并不具备原子性,但是被它修饰了的变量在线程中具有可见性,所以被它修饰的变量重量级来说比synchronized更轻,volatile的两点作用在于
- 使变量具有可见性
- 防止指令重排序