java, volatile与单例模式

778 阅读2分钟

volatile是java中的一种轻量级同步机制,主要应用于原子级操作, 相比于synchronized关键字, 不会引起线程上下文的切换和调度。

  • java中的原子性操作
    1. 基本类型的读取和赋值
    2. 所有引用的赋值
    3. jaca.concurrent.Atomic.*下的所有操作
  • volatile变量的特性
    1. 当写一个volatile变量时,JMM会把该线程本地内存中的变量强制刷新到主内存中
    2. 导致其他线程中的volatile变量无效
    3. 禁止指令重排(在并发环境下,指令重排会导致结果不一致)

volatile不适用非原子性操作示例:

class Test{
    public int a = 0;
    public void increase(){
        a++;
    }
    public static void main(String[] args) throws InterruptedException {
        System.out.println();
        final Test test = new Test();
        for(int i = 0; i < 10; i++){
            new Thread(){
                @Override
                public void run() {
                    for(int j = 0 ; j < 1000; j++){
                        test.increase();
                    }
                };
            }.start();
        }
        while (Thread.activeCount() > 2){
            Thread.yield(); // 当前线程放弃对处理器的使用
        }
        System.out.println(test.a);
    }
}

代码输出结果小于10*1000, 因为a++不是一个原子性操作,由读取、(运算)a+1, (赋值)a = a + 1三个步骤组成。

  • 解决方法
    1. synchronized关键字
    2. lock
    3. CAS

单例模式

适用于全局频繁使用创建与销毁。例如全局计数器、生产唯一序列号、I/O与数据库的连接。

  1. 饿汉式

    class SingleTon{
       private static SingleTon singleTon = new SingleTon();
       private SingleTon(){}
    
       public static SingleTon getSingleTon() {
           return singleTon;
       }
    
       public static void main(String[] args) {
           SingleTon singleTon1 = SingleTon.getInstance();
       }
    }
    

    将构造函数设为private,这样这个实例就唯一了, 缺点是有可能由于其他的静态方法导致类加载,无法达到lazy loading的效果。

  2. 懒汉式

    class SingleTon{
        private static SingleTon instance = null;
        private SingleTon() {
        }
    
    
        public static SingleTon getInstance() {
            if(instance == null){
                instance = new SingleTon();
            }
            return instance;
        }
    
        public static void main(String[] args) {
            SingleTon singleTon2 = SingleTon.getInstance();
        }
    }
    

    这种懒汉式的好处是按需加载,只会通过getInstance方法创建这个唯一示例,但是很明显的是线程不安全的。

    解决方法:加同步锁

  3. 单锁的懒汉式

    class SingleTon{
        private static SingleTon instance = null;
        private SingleTon() {
        }
    
    
        public static synchronized SingleTon getInstance() {
            if(instance == null){
                instance = new SingleTon();
            }
            return instance;
        }
    
        public static void main(String[] args) {
            SingleTon singleTon3 = SingleTon.getInstance();
        }
    }
    

    这种加锁的方式十分耗费资源

  4. 双重锁

    class SingleTon{
        private volatile static SingleTon instance = null;
        private SingleTon() {
        }
    
    
        public static synchronized SingleTon getInstance() {
            if(instance == null){
                synchronized (SingleTon.class){
                    if(instance == null){
                        instance = new SingleTon(); // 不是原子性操作
                    }
                }
            }
            return instance;
        }
    
        public static void main(String[] args) {
            SingleTon singleTon4 = SingleTon.getInstance();
        }
    }
    

    这种方式是线程安全的,且在并发情况下效率较高,但是实现复杂

  5. 静态内部类

    class SingleTon{
        private static class SingleTonHolder{
            private static final SingleTon singleTon = new SingleTon();
        }
        private SingleTon() {
        }
    
    
        public static final synchronized SingleTon getInstance() {
            return SingleTonHolder.singleTon;
        }
    
        public static void main(String[] args) {
            SingleTon singleTon5 = SingleTon.getInstance();
        }
    }
    

    首先,只能能过getInstance实例化,且不会被改变,是线程安全的。