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关键字修饰,编译器会重排代码,缓存变量的值,减少从主存中直接获取变量的值。
不用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变量效果等同释放锁。