java 设计模式之Singleton(单例模式)
单例模式
单例设计模式是一种比较常用到的模式之一,但是项目中自己手动写的情况下很少,因为有spring的Bean工厂直接接手了,框架内实现了单例模式。
单例模式创建时保证了在java程序中一个类只有一个实例对象的存在。
单例模式有很多好处,它能够避免实例对象的重复创建,不仅可以减少每次创建对象的时间开销,还可以节约内存空间;能够避免由于操作多个实例导致的逻辑错误。如果一个对象有可能贯穿整个应用程序,而且起到了全局统一管理控制的作用,那么单例模式也许是一个值得考虑的选择。
饿汉模式
package com.getnextapp.scm.util;
/**
* 单例设计模式之饿汉模式
* @author Xuzs
*/
public class SingletonTest {
//初始化这个对象
private static SingletonTest singletonTest=new SingletonTest ();
//创建私有的构造方法,让其不能new
private SingletonTest(){
}
//通过方法来调用它
public static SingletonTest getInstance(){
return singletonTest;
}
public static void main(String[] args) {
SingletonTest s1= SingletonTest.getInstance ();
SingletonTest s2= SingletonTest.getInstance ();
System.out.println (s1.hashCode ()==s2.hashCode ());
//用for循环开启100线程同时访问,所有的哈希码值是一样的
for (int i=0;i<100;i++){
new Thread (()->System.out.println(SingletonTest.getInstance ().hashCode ())).start ();
}
}
}
这种饿汉式单例模式是最简单也比较常用的,既保证了只实例一次有保障了线程安全(jvm内部维护线程安全)
缺点是在类加载的同时就生成了这个对像,这个对象存在于这个类的生命周期,这样就造成的了内存浪费
从而引发了以下几种方式
懒汉式单例模式
package com.getnextapp.scm.util;
import java.util.concurrent.TimeUnit;
/**
* 单例设计模式之懒汉模式
* @author Xuzs
*/
public class LazyManSingleton {
//私有化构造方法
private LazyManSingleton(){}
//不先new出这个对象
private static LazyManSingleton lazyManSingleton;
//在调用的时候判断该对象是否被创建,被创建了就直接return出去
public static LazyManSingleton getInstance(){
if (lazyManSingleton==null){
try {
Thread.sleep (2000);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
lazyManSingleton=new LazyManSingleton();
}
return lazyManSingleton;
}
}
class Test2{
public static void main(String[] args) {
for (int i=0;i<100;i++){
//用的是java 1.8 的 lambad表达式写的
new Thread (()->{
System.out.println(SingletonTest.getInstance ( ).hashCode ());
}
).start ();
}
}
}
懒汉模式解决了饿汉模式存在的内存资源浪费的问题,但是引发了在高并发情况下的线程不安全问题。
以下是对线程不安全问题的两种解决方式
方法体上加synchronized锁
public synchronized static LazyManSingleton getInstance(){
if (lazyManSingleton==null){
try {
Thread.sleep (2000);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
lazyManSingleton=new LazyManSingleton();
}
return lazyManSingleton;
}
这种直加锁能够直接解决线程问题,但是因为整个方法体上都加锁了,每次调用都要想cup申请加锁,导致性能低下。影响效率
双重判断在代码里加synchronized代码块
//要加上volatile关键字,防止汇编时产生指令重排问题
private static volatile LazyManSingleton lazyManSingleton;
public static LazyManSingleton getInstance(){
//第一个判断:假如该对象已经创建就没有必要进入synchronized块,从而提高效率
if (lazyManSingleton==null){
synchronized (LazyManSingleton.class){
//防止多线程并发时多个线程同时涌入进来
if(lazyManSingleton==null){
try {
Thread.sleep (2000);
} catch (InterruptedException e) {
e.printStackTrace ( );
}
lazyManSingleton=new LazyManSingleton();
}
}
}
return lazyManSingleton;
}
这种方式解决了多次频繁加锁同时也防止了指令重排序的问题。这种方式相对来说比较好,但是写起来有点费劲熟练度不是很高的话是很难把握住的(对于我这种菜鸟来说~)
静态内部类方式
package com.getnextapp.scm.util;
/**
* 单例模式之静态内部类
* @author Xuzs
*/
public class StaticSingleton {
//构造方法私有化
private StaticSingleton(){}
//声明一个私有的静态的内部类,内部类能够拿到外部类的属性
private static class Singleton{
private static StaticSingleton staticSingleton=new StaticSingleton();
}
public static StaticSingleton getInstance(){
//只有外部类能拿到内部类的值
return Singleton.staticSingleton;
}
}
class Test3{
public static void main(String[] args) {
for (int i=0;i<100;i++){
new Thread (()->
System.out.println(SingletonTest.getInstance ( ).hashCode ())
).start ();
}
}
}
这种方式跟饿汉模式很相识,也是利用类加载的方式,既保证了只创建了一个实例对象又通过jvm保证了线程安全性。通过静态内部类来创建对象,这样就解决了外部类通过类加载的方式而导致了内存资源浪费的问题。
静态内部类方式
package com.getnextapp.scm.util;
/**
* 单例模式之枚举类
* @author Xuzs
*/
public enum EnumSingleton {
instance
}
class Test4{
public static void main(String[] args) {
for (int i=0;i<100;i++){
new Thread (()->
System.out.println(EnumSingleton.instance.hashCode ())
).start ();
}
}
}
上面提到的四种实现单例的方式都有共同的缺点:
1)需要额外的工作来实现序列化,否则每次反序列化一个序列化的对象时都会创建一个新的实例。
2)可以使用反射强行调用私有构造器(如果要避免这种情况,可以修改构造器,让它在创建第二个实例的时候抛异常)。
而枚举类很好的解决了这两个问题,使用枚举除了线程安全和防止反射调用构造器之外,还提供了自动序列化机制,防止反序列化的时候创建新的对象。因此,《Effective Java》作者推荐使用的方法。不过,在实际工作中,很少看见有人这么写。