Java内存模型是定义程序中各个变量的访问规则,包括了实例字段,静态字段,数组对象等。不含有局部变量及方法参数。
Java内存模型规定了全部变量都记录在主内存,每条线程还有自己的工作内存。线程对变量的读写只能在工作内存中。
对于不同内存之间的操作,Java模型仅规定了read,load,store,write的顺序,而这些指令中可以插入别的指令。
volatile在各个线程的工作内存中不存在一致性问题,因为读写时均会刷新,但是Java的运算并非是原子操作,以及有指令重排等,故在并发情况时不是安全的。volatile不能同步的本质就是指令重排。
在并发时,volatile的同步性能确实优先于synchronized以及concurrent,但是由于虚机会对所做许多消除及优化,且volatile非常难于控制,故选择volatile的情况仅是一些语义的需求。
Java的read,load,store,write均是原子性的,故基本数据类型也是原子性的,当然有long,double的非原子协定。
Java提供了volatile与synchronized保证线程之间操作的有序性。volatile本身就有禁止指令重排的语义。
Java1.5以后内存模型被修正,Immutable的对象一定是线程安全的。(String,被final修饰的对象)
Java中如Vector等线程安全的容器,它的add,get等方法均是被synchronized修饰的。但是它不是绝对线程安全的,因为可以在getSize以后去减少当中某一个元素,再去读这个元素,就会发生异常。因此Vector,hashTable等时相对线程安全的。
互斥同步:
同步时指多个线程在并发访问共享数据时,保证共享数据在同一个时刻只能被一个(一些,若是信号量)线程使用。互斥是同步的手段,临界区(Critical Section),互斥量(Mutex),信号量(Semaphore)都是互斥的实现方式。故互斥是因,同步是果。
Java的线程是映射到操作系统原生线程之上的,当阻塞或唤醒一个线程时,均是操作系统介入,故需从用户态转换到核心态中。因为该转换需耗费较多处理机时间,故synchronized非常耗时,而虚机优化时会做自旋操作,避免频繁切入核心态。
JIT(Just In TimeCompiler)即时编译器,当虚拟机发现部分代码频繁时,会将之认为是hot spot code,并编译为本地平台相关的机器码。
编译器优化技术:公共子表达式消除;数组范围检查消除;方法内联;逃逸分析;表数内逃
逃逸分析:分析对象的动态作用域;当一个对象在方法中被定义以后,它有可能被外部方法引用,称为方法逃逸;被外部线程访问,称为线程逃逸。
若能证明一个对象不会逃逸到方法及线程之外,可以为这个对象做 栈上分配以减少回收时间。同时若该对象不会逃逸出线程,就不会出现同步问题,可以做 同步消除。
Java较C慢在:JIT不能使用大规模的优化以避免代码跑的慢,Java的动态性决定了Java必须做安全性检查,同时编译器不能窥见代码全貌,不能有效调优。Java有GC回收等。
通过Java提供的"Unreachable code"可以实现C语言中的条件编译(指定Ture,False)。
编译器会对代码进行优化,对于条件永远为false的语句,JAVA编译器将不会对其生成字节码。
Java的reference可能是32也可能是64,64位的数据类型只有long与double,在32位系统中,long与double的赋值不能保证线程同步。故二者需以volatile修饰。
volatile本身不保证获取和设置的原子操作性,仅保持修改的可见性。
Java语言规范规定了JVM线程内部维持顺序化语义,也就是说只要程序的最终结果等同于它在严格的顺序化环境下的结果,那么指令的执行顺序就可能与代码的顺序不一致。这个过程通过叫做指令的重排序。
JMM会被归纳为有三个特性:原子性、可见性、有序性。JMM中的操作有如下8种,分别是read、load、use、assign、store、write以及lock和unlock。
Java内存模型具备一些先天的“有序性”,即不需要通过任何手段就能够得到保证的有序性,这个通常也称为 happens-before 原则。如果两个操作的执行次序无法从happens-before原则推导出来,那么它们就不能保证它们的有序性,虚拟机可以随意地对它们进行重排序。
happens-before就是JMM的先天有序性。(lock,start,interrupt,finalizer)