Java单例模式

单例模式之饿汉模式:

/**
 * 
 * @author Taowd
 * 功        能:单例模式,饿汉模式,线程安全,效率比较低
 * 优点:写法简单,线程安全
 * 缺点:加载速度比较慢,某些特定情况下会耗费内存
 * 编写时间:2017-5-11-上午8:37:37
 */
public class Singleton2 {

    // 私有化构造参数
    private Singleton2() {

    }

    // 将自身实力对象设置一个属性,并加上static和final修饰符
    private static final Singleton2 singleton2 = new Singleton2();

    // 静态方法返回该类的示例
    public static Singleton2 getInstance() {
        return singleton2;
    }
}

单例模式之懒汉模式:

package com.taowd.singleton;

/**
 * 
 * @author Taowd
 * 功能:单例模式,懒汉模式(饱汉模式),非线程安全的
 * 优点:编写简单,类加载速度快,使用速度慢
 * 缺点:并发环境下可能出现多个Singleton实例
 * 编写时间:2017-5-11-上午8:27:25
 */
public class Singleton1 {
    // 把构造方法私有化,防止通过new Singleton() 去实例化
    private Singleton1() {

    }

    // 定义一个Singleton类型的示例,不进行初始化,注意这里没有使用final关键字
    private static Singleton1 singleton;

    // 定义一个静态方法,外部调用时再初始化Singleton,但是多线程访问时可能造成重复初始化的问题
    public static Singleton1 getInstance() {
        if (singleton == null) {
            singleton = new Singleton1();
        }
        return singleton;
    }
}

单例模式之终极模式:

package com.taowd.singleton;

/**
 * 
 * @author Taowd
 * 功能:最终版,最优方案
 * 优点:内存占用低,效率高,线程安全,多线程操作原子性
 * 编写时间:2017-5-11-上午8:43:08
 */
public class Singleton {
    // 把构造方法私有化,防止通过new Singleton() 去实例化
    private Singleton() {

    }

    // 定义一个Singleton类型的示例,不进行初始化,注意这里没有使用final关键字
    // volatile 保证了多线程访问instance变量的可见性,避免了instance初始化时,其他变量属性还没赋值完就被另外线程调用
    // volatile:
    private static volatile Singleton instance;

    // 定义一个静态方法,外部调用时再初始化Singleton,但是多线程访问时可能造成重复初始化的问题
    public static Singleton getInstance() {
        // 对象实例化与否判断(不适用同步代码,instance不等于null时,直接返回对象,提高执行效率)
        if (instance == null) {
            // 同步代码块(对象未初始化时,使用同步代码,保证多线程访问对象在第一次被创建后,不再重复被创建)
            // synchronized:
            synchronized (Singleton.class) {
                // 未初始化,则初始化instance变量
                if (instance == null) {
                    instance = new Singleton();
                }
            }
        }
        return instance;
    }
}

单例模式的测试程序:

package com.taowd.test;

import org.junit.Test;

import com.taowd.singleton.Singleton1;

public class SingletonTest {

    @Test
    public void TestSingleton() {
        Singleton1 singleton1 = Singleton1.getInstance();
        Singleton1 singleton2 = Singleton1.getInstance();
        System.out.println(singleton1 == singleton2 ? "同一对象" : "不是同一对象");
    }
}

  以前对单例模式不是很清楚,通过这几个例子也学习了一下单例模式,其中对volatile关键字的含义的不是很明白,因为很少用到这个关键字,查阅了一下资料下面将我的查阅的信息简单说明一下。

  volatile关键字可以保证可见性和执行顺序,可见性既是先发生的原子修改操作一定会在读操作之前执行完成(同一时间对volatile变量只能有一个操作);执行顺序的含义既是volatile关键字会阻止编译器或JVM对代码重排序,或把volatile变量从同步区域(synchronized方法,synchronized代码块..)中移除出来。

Java的内存模型

Java的内存模型(JMM)可以保证一个线程修改了某个变量,该变量的改变对另一个线程是可见的,也就是说另一个线程取得的这个变量的值,一定是前一个线程修改之后的值。这个也被称作"happens-before" 。

volatile关键字使用场所

volatile关键字只能修饰变量,不能修饰类,也不能修饰方法。

想要把某个变量共享,该变量的读写操作必须是原子性的,并用volatile关键字修饰。

  • volatile修饰的long和double类型的变量读写操作是原子性的。long和double都是64位的,给long和double类型的变量赋值跟平台相关,在有些平台上不是原子操作。很多平台给long和double变量赋值需要2步操作,每一步只写32位,在这2个步骤之间,其他线程获取的long或double类型的变量的值的状态是不正确的。

  • volatile变量有类似synchronized同步代码的可见性,即每个线程读取到的都是最新更新的值。volatile的局限性很大,你只能获取volatile变量的值,直接设置volatile变量的值,不能比较之后再设置volatile的值,因为在你做比较操作的区间很有可能有其他线程修改了该volatile变量的值。

  • 被volatile关键字修饰,表明该变量是要被多个线程访问的,编译器不会对与volatile变量相关的代码做重排或其他多线程下不允许的优化。没有被volatile关键字修饰,编译器会重排代码,缓存变量的值,减少从主存中直接获取变量的值。

Java单例模式_第1张图片

不用volatile关键字修饰,有可能某个线程可以获取到另一个线程设置的isActive的值,编译器有可能缓存了isActive的值等等,使用volatile关键字可以避免这些情况。

  • 双重检查单例模式在JDK4是有问题的,volatile可以修复这个问题。本文的《第一个例子说明volatile变量的含义》中举的例子就是说这个问题。

volatile关键字的总结

1. volatile关键字只用修饰变量,不能修饰方法和类。

2. volatile变量的值都是从主存中获取的,而不是从线程的本地内存。

3.long和double变量被volatile关键字修饰之后,读写(赋值操作,读取操作)都是原子操作.

4. 使用volatile关键字可以避免内存不一致的错误;写入volatile变量一定会比接下来的读操作先发生。5. 从jdk5开始对volatile变量的修改对其他的线程都是可见的;当线程读取volatile变量的时候,会先把其他线程中缓存着的volatile变量(如果还没有更新到主存中的时候)强制写入到主存。

6. 除了long和double其他的基本类型读写操作都是原子性的;引用类型的读写操作也是原子性的。

7. volatile变量只能做简单的读写,没有锁,没有阻塞。

8.volatile变量可以是空的.

9. volatile不能保证原子性,比如volatile修饰的int变量++操作还是非原子的。

10. 变量没有在多个线程之间共享,没有必要做任何同步的操作,比如使用volatile关键字修饰。

synchronized和volatile关键字的比较

volatile关键字代替不了synchronized关键字,不过在某些场合可以作为替代方案。

1. volatile关键字只能修饰字段,而synchronized只能修饰代码块和方法。

2. synchronized关键字需要获得锁释放锁,volatile关键字不需要。

3.synchronized代码块或方法在等待锁的时候会被阻塞;volatile不是这样的。

4. synchronized代码块或方法会比volatile关键字更影响性能。

5. volatile关键只同步被修饰的变量,而synchronized关键字却同步代码块或方法中所有的变量,并且还会获得锁释放锁,所以synchronized的负载更大。

6. 不能同步(synchronized)null对象,而volatile变量可以是null的。

7. 读取volatile变量效果等同获取锁,写入volatile变量效果等同释放锁。

 

你可能感兴趣的:(Java单例模式)