1、引入工具(Javap )
1、使用javap,主要用于帮助开发者深入了解Java编译器的机制,主要选项有:
- -c分解方法代码,即显示每个方法具体的字节码
- -public|protect|package|private 用于指定显示那种级别的类成员
- -verbose指定显示更进一步的详细信息
输入:javap -c Test.class
D:\my-workspace\mayun\JUCDemo\out\production\JUCDemo\com\pingan>javap -c Test.class
Compiled from "Test.java"
public class com.pingan.Test {
static volatile java.lang.String b;
public com.pingan.Test();
Code:
0: aload_0
1: invokespecial #1 // Method java/lang/Object."<init>":()V
4: return
public static void main(java.lang.String[]);
Code:
0: getstatic #2 // Field java/lang/System.out:Ljava/io/PrintStream;
3: getstatic #3 // Field b:Ljava/lang/String;
6: invokevirtual #4 // Method java/io/PrintStream.println:(Ljava/lang/String;)V
9: return
static {};
Code:
0: ldc #5 // String hello
2: putstatic #3 // Field b:Ljava/lang/String;
5: return
2、volatile
voliate是一个类型修饰符,volatile的作用是作为指令关键字,确保本指令不会因编译器的优化而省略。
2.1 volatile的特性
- 实现可见性:保证了不同线程对这个变量进行操作时可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。
- 实现有序性:禁止进行指令重排序。
- 原子性:volatile只能保证对单次操作的原子性,i++这种操作不能保证原子性;
2.2 volatile的实现原理 - volatile变量的内存可见性是基于内存屏障(Memory Barrier)实现。
内从屏障,又称内存栅栏,是一个CPU指令。
在程序运行是,为了提高执行性能,编译器和处理器对指令进行重排,JMM为了保证在不同的编译器和CPU上有相同的结果,通过插入特定类型的内存屏障来禁止特定类型的编译器重排序和处理器重排序,插入一条内存屏障会告诉编译器和CPU:不管什么指令都不能和这条Memory Barrier指令重排序。
2.3 volatile的应用场景
1、使用volatile必须具备的条件
- 对变量的写操作不依赖于当前值;
- 对变量没有包含在具有其他变量的不变式中;
2、只有在状态真正独立于程序内其他内容时才能使用volatile。
模式一:状态标志
也许实现volatile变量的规范使用仅仅是使用一个布尔标志,由于知识发生了一个重要的一次性事件,例如:完成初始化或请求停机
volatile boolean shutdownRequested;
......
public void shutdown() { shutdownRequested = true; }
public void doWork() {
while (!shutdownRequested) {
// do stuff
}
}
模式二:一次性安全发布
缺乏同步会导致无法实现可见性,这使得确定何时写入对象引用而不是原始值变得更加困难,在缺乏同步的情况下,可能会遇到某个对象引用的更新值(由另一线程写入)和该对象状态的旧值同时存在。
public class BackgroundFloobleLoader {
public volatile Flooble theFlooble;
public void initInBackground() {
// do lots of stuff
theFlooble = new Flooble(); // this is the only write to theFlooble
}
}
public class SomeOtherClass {
public void doWork() {
while (true) {
// do some stuff...
// use the Flooble, but only if it is ready
if (floobleLoader.theFlooble != null)
doSomething(floobleLoader.theFlooble);
}
}
}
模式三:独立观察
安全使用volatile的另一种简单模式是定期发布观察结果供程序内部使用。例如:假设有一种环境传感器能够感觉环境温度,一个后台线程可能会每隔几秒读取一次传感器,并更新包含当前文档的volatile变量,然后,其它线程可以读取变量,从而随时能后看到最新的温度值。
public class UserManager {
public volatile String lastUser;
public boolean authenticate(String user, String password) {
boolean valid = passwordIsValid(user, password);
if (valid) {
User u = new User();
activeUsers.add(u);
lastUser = user;
}
return valid;
}
}
模式五:开销较低的读-写锁策略
- volatile的功能还不足以实现计数器,以为内++X实际上是三种操作(读、添加、存储)的简单组合。如果多个线凑巧试图同时对volatile计数器执行增量操作,那么它的更新值有可能会丢失。
- 如果读操作远远超过写操作,可以结合使用内部锁,和volatile变量来减少公共代码路径的开销。
- 安全的计数器使用synchronized确保增量操作是原子的,并使用volatile保证当前结果的可见性。如果更新不频繁的情况下的话,改方法可实现更好的性能。因为读路径的开销仅仅涉及volatile读操作,这通常要优于一个无竞争的锁获取的开销。
@ThreadSafe
public class CheesyCounter {
// Employs the cheap read-write lock trick
// All mutative operations MUST be done with the 'this' lock held
@GuardedBy("this") private volatile int value;
public int getValue() { return value; }
public synchronized int increment() {
return value++;
}
}
模式六:双重检查
单例模式的一种实现方式,因为很多人会忽略volatile关键字,因为没该关键字,程序也可以很好的运行,只不过代码的稳定性总不是100%,说不定在那个时刻,隐藏的bug就暴处理了。
class Singleton {
private volatile static Singleton instance;
public static Singleton getInstance() {
if (instance == null) {
syschronized(Singleton.class) {
if (instance == null) {
instance = new Singleton();
}
}
}
return instance;
}
}
推荐懒加载优雅写法:
public class Singleton {
private static class SingletonHolder{
static Singleton instance = new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.instance;
}
}