饿汉式的两种写法:
方式一:
/*
单例:饿汉式
*/
public class Singleton {
//无参构造私有化
private Singleton() { };
private static Singleton intence = new Singleton();
public static Singleton genIntence(){
return intence;
}
}
测试:
public class Test {
public static void main(String[] args) {
Singleton singleton = Singleton.genIntence();
Singleton singleton2 = Singleton.genIntence();
System.out.println(singleton==singleton2);
}
}
结果为:true (==判断的是对象在内容中的地址值是否相同)
解释:
该方式在成员位置声明Singleton类型的静态变量,并创建Singleton类的对象 intence
对象是随着类的加载而创建的。如果该对象足够大的话,而一直没有使用就会造成内存的浪费。
方式二:
/*
单例:饿汉式,静态代码块方式
*/
public class Singleton {
private Singleton(){};
private static Singleton singleton;
static {
singleton = new Singleton();
}
public static Singleton genIntence(){
return singleton;
}
}
测试:
public class Test {
public static void main(String [] args){
Singleton singleton = Singleton.genIntence();
Singleton singleton2 = Singleton.genIntence();
System.out.println(singleton==singleton2);
}
}
结果为:true (==判断的是对象在内容中的地址值是否相同)
解释:
在成员位置声明Singleton类型的静态变量,而对象的创建是在静态代码块中,也是对着类的加载而创建。所以和饿汉式的方式一基本相同,该方式也存在内存浪费问题。
线程不安全方式一:
多线程下会产生线程安全问题
public class Singleton{
private Singleton(){};
private static Singleton singleton; //只是声明一个该类型的变量,并没有进行赋值
//对外提供访问方式
public static Singleton getInstance(){
/*
判断singleton 是否为null,如果为null,说明还没有创建Singleton类的对象,
进入if里边如果为null创建一个并返回,如果有直接返回。
*/
if(singleton ==null){
//假设线程1进入后隔着等待了,此时线程2进入后立刻获取到cpu的执行权,也会进入到该判断里边
singleton=new Singleton();
}
return instance;
}
}
测试:
public class Test {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
}
}
执行结果:true
线程安全方式二:
给对外提供的静态方法加 synchronized
保证多线程情况下每个线程执行完后释放CPU的执行权后轮流执行。
public class Singleton{
private Singleton(){};
private static Singleton singleton; //只是声明一个该类型的变量,并没有进行赋值
//对外提供访问方式
public static synchronized Singleton getInstance(){
/*
判断singleton 是否为null,如果为null,说明还没有创建Singleton类的对象,
进入if里边如果为null创建一个并返回,如果有直接返回。
*/
if(singleton ==null){
singleton=new Singleton();
}
return instance;
}
}
测试:
public class Test {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
}
}
执行结果:true
双重检查锁方式三: 又称:Double check lock
在上边的懒汉式线程安全方式中,对于getInstance()方法来说,绝大部分操作都是读操作,读操作就是第一次创建对象赋值后,在进行第二次判断就直接有对象了,singleton 不为null就直接返回了。读操作是线程安全的,所以没必要让每个线程必须持有锁才能调用该方法。此时就需要调整加锁的时机。因而由此产生了一种新得实现模式也就是所说得:双重检查锁模式
/*
双重检查锁方式
*/
public class Singleton {
private Singleton(){};
private static volatile Singleton singleton;
//对外提供静态方法获取该对象
public static Singleton getInstance(){
//第一次判断,如果singleton不为null,不进入枪锁阶段,直接返回
if (singleton==null){
singleton=new Singleton();
synchronized (Singleton.class){
//抢到锁之后再次判断是否为空
if (singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
测试:
public class Test {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1==instance2);
}
}
结果为:true
解释:
双重检查锁模式是一种非常好的单例实现模式,解决了单例、性能、线程安全问题、上面的双重检查锁模式看上去完美无缺,其实是存在一些问题的。在多线程情况下,可能会出现空指针问题,出现问题的原因是JVM在实例化对象的时候会进行优化和指令重排序操作。
要解决双重检查锁模式带来空指针遗产过的问题,只要使用Volatile 关键字,Volatile 关键字可以保证可见性和有序性。
总结:
添加Volatile 关键字之后的双重检查锁模式是一种比较好的单例设计模式,能够保证在多线程情况下线程安全也不会有性能问题。
静态内部类方式:
static
修饰,保证只被实例化一次,并且严格保证实例化顺序。
/*
单例懒汉式之静态内部类方式
*/
public class Singleton {
//私有构造
private Singleton singleton;
//静态内部类
private static class SingletonHolder{
private static final Singleton SINGLETON =new Singleton();
}
public static Singleton getInstance(){
return SingletonHolder.SINGLETON;
}
}
测试:
public class Test {
public static void main(String[] args) {
Singleton instance1 = Singleton.getInstance();
Singleton instance2 = Singleton.getInstance();
System.out.println(instance1 == instance2);
}
}
结果为:true
解释:
第一次加载Singleton类时不会去初始化 SINGLETON ,只有第一次调用getInstance,虚拟机加载SingletonHolder并初始化 SINGLETON,这样不仅能确保线程安全,也能保证 Singleton类的唯一性。
总结:
静态内部类单例模式是一种优秀的单例模式,是项目中比较常用的一种模式,在没有加任何锁的情况下,保证了多线程下的安全,并且没有任何性能影响和空间的浪费。