java中单例模式是一种常见的设计模式,单例模式的写法有好几种,这里主要介绍两种:懒汉式单例、饿汉式单例。
单例模式有以下特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式四大原则:
1.构造私有。
2.以静态方法或者枚举返回实例。
3.确保实例只有一个,尤其是多线程环境。
4.确保反序列换时不会重新构建对象。
线程安全的饿汉模式
public class Hungry {
/**
* @param args
*/
//内部实例化
private final static Hungry hungry=new Hungry();
//私有构造方法
private Hungry(){
}
//对外提供调用实例
public Hungry getHungry(){
return hungry;
}
}
饿汉模式是线程安全的。饿汉模式的对象在类产生时候就创建了,一直到程序结束才会去释放。即作为一个单例类实例,它的生存周期和我们的程序一样长。因此该实例对象需要存储在全局数据区,所以肯定需要使用static来修饰,因为类内部的static成员是不属于每个对象的,而是属于整个类的。在加载类的时候,我们的实例对象就产生了。但是他是在加载类的时候创建实例,如果缓存了很多实例,就会影响效率问题,所以说这种加载无法实现懒加载。
懒汉模式
为了缓解在类加载是即完成实例,实现懒加载,便有了懒汉模式
public class Lazy {
private static Lazy lazy=null;
//私有构造方法
private Lazy(){
}
//需要时完成加载
public static Lazy getLazy(){
if(lazy==null){
lazy=new Lazy();
}
return lazy;
}
}
但是如果是在多线程并发的 情况下,这种方法是线程不安全的无法保证一个实例。所以我们加synchronized
public class Lazy {
private static Lazy lazy=null;
//私有构造方法
private Lazy(){
}
//需要时完成加载是线程安全的
public static synchronized Lazy getLazy(){
if(lazy==null){
lazy=new Lazy();
}
return lazy;
}
}
然而并发其实是一种特殊情况,大多时候这个锁占用的额外资源都浪费了,这种方式写出来的结构效率很低。
所以改进成为了双重校验锁法
双重锁懒汉模式(Double Check Lock)
public class Lazy {
private static Lazy lazy=null;
//私有构造方法
private Lazy(){
}
//双重校验锁法
public static Lazy getLazy(){
if(lazy==null){
synchronized(Lazy.class){
if(lazy==null){
lazy=new Lazy();
}
}
}
return lazy;
}
}
DCL模式的优点就是,只有在对象需要被使用时才创建,第一次判断 INSTANCE == null为了避免非必要加锁,当第一次加载时才对实例进行加锁再实例化。这样既可以节约内存空间,又可以保证线程安全。但是,由于jvm存在乱序执行功能,DCL也会出现线程不安全的情况。具体分析如下:
INSTANCE = new SingleTon();
这个步骤,其实在jvm里面的执行分为三步:
1.在堆内存开辟内存空间。
2.在堆内存中实例化SingleTon里面的各个参数。
3.把对象指向堆内存空间。
由于jvm存在乱序执行功能,所以可能在2还没执行时就先执行了3,如果此时再被切换到线程B上,由于执行了3,INSTANCE 已经非空了,会被直接拿出来用,这样的话,就会出现异常。这个就是著名的DCL失效问题。
不过在JDK1.5之后,官方也发现了这个问题,故而具体化了volatile,即在JDK1.6及以后,只要定义为private volatile static SingleTon INSTANCE = null;就可解决DCL失效问题。volatile确保INSTANCE每次均在主内存中读取,这样虽然会牺牲一点效率,但也无伤大雅
登记式/静态内部类
这种方式能达到双检锁方式一样的功效,但实现更简单。对静态域使用延迟初始化,应使用这种方式而不是双检锁方式。这种方式只适用于静态域的情况,双检锁方式可在实例域需要延迟初始化时使用。饿汉模式类加载便一定会实例化。但是使用静态内部类类加载是不一定会实例化。因为 SingletonHolder 类没有被主动使用,只有显示通过调用 getInstance 方法时,才会显示装载 SingletonHolder 类,从而实例化 instance。
public class Singleton {
private static class SingletonHolder {
private static final Singleton INSTANCE = new Singleton();
}
private Singleton (){}
public static final Singleton getInstance() {
return SingletonHolder.INSTANCE;
}
}
枚举
这种实现方式还没有被广泛采用,但这是实现单例模式的最佳方法。它更简洁,自动支持序列化机制,绝对防止多次实例化。但它无法实现懒加载。
public enum Singleton {
INSTANCE;
public void whateverMethod() {
}
}
public static void main(String[] args) {
Singleton.INSTANCE.whateverMethod() ;
}