01_单例模式

【单例模式定义】

确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例。

【关键点】

1.定义一个private访问权限的构造方法,避免被其它类new出一个对象。

2.自己可以new出一个对象,其他类可以通过getInstance()方法获得同一个对象。

【优点】

1.由于单例模式在内存中只有一个实例,减少了内存开支,特别是一个对象需要频繁地创建、销毁时,而且创建或销毁时性能又无法优化,单例模式的优势就非常明显。

2.单例模式只生成一个实例,减少了系统的性能开销,当一个对象产生需要较多资源时,如读取配置、产生其他依赖对象时,则可以通过在应用启动的时候直接产生一个单例对象,然后永久驻留内存的方式来解决。

3.单例模式可以避免对资源的多重占用,例如一个写文件动作,由于只有一个实例存在内存中,避免对同一个文件同时写操作。

4.单例模式可以在系统设置全局的访问点,优化和共享资源访问,例如一个单例类来负责所有的数据表的映射处理。

【使用场景】

(在一个系统中,要求类有且只有一个对象的场景)

1.要求生成唯一序列号的环境。

2.在整个项目中需要一个共享访问点或共享数据。

3.创建一个对象需要消耗的资源资源过多,如要访问IO和数据库等资源。

4.需要定义大量的静态常量和静态方法(常见于工具类)的环境。(当然也可以直接声明为static的方式)

【饿汉式】

package com.Higgin.Single;

public class HungrySingle {
    //
    private static final HungrySingle hungrySingle=new HungrySingle();
    //限制产生多个对象
    private HungrySingle(){};
  //通过该方法获得实例对象
public static HungrySingle getInstance(){ return hungrySingle; } //类中的其它方法,尽量使static public static void doSomething(){ } }

【懒汉式:线程不安全】

package com.Higgin.Single;
/**
 * 线程不安全的 懒汉式 单例模式
 */
public class LazySingle {
    private LazySingle(){};
    
    public static LazySingle lazySingle=null;
    
    public static LazySingle getInstance(){
        if(lazySingle==null){
            lazySingle=new LazySingle();
        }
        return lazySingle;
    }
}

[ 普通懒汉式:线程不安全的例子 ]

class LazySingle {
    private LazySingle(){};
    
    public static LazySingle lazySingle=null;
    
    public static LazySingle getInstance() throws InterruptedException{
        if(lazySingle==null){
            Thread.sleep(1000);   //这里刻意延迟1秒,增加多个线程进入该判断的几率
            lazySingle=new LazySingle();
        }
        return lazySingle;
    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
        try {
            for(int i=0;i<10;i++){
                LazySingle  l = LazySingle.getInstance();//每个线程都去获取10次单例模式的实例
                System.out.println(Thread.currentThread().getName()+"======"+l.hashCode());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }   
    }
}

public class Test{
    public static void main(String[] args) {
        MyThread t= new  MyThread();
        //开启四个线程
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
        new Thread(t).start();
    }
}

[ 运行结果 ]

其hashcode值有多处不同,说明发生了多线程的处理异常

【懒汉式:双重加锁(线程安全)】

/**
 * 懒汉式:双重加锁 
 */
public class LazySingle {
    private LazySingle(){}
    
    private static LazySingle singleton=null;
    
    public static LazySingle getInstance(){
        if(singleton==null){                  //第一个null判断
            synchronized(LazySingle.class){   //同步代码块,确保这里只有一个进程进入代码块
                if(singleton==null){          //第二个null判断
                    singleton=new LazySingle();
                }
            }
        }
        return singleton;
    }
}

[ 为什么要加第一个null判断 ]

加第二个null判断很容易理解,为什么要加第一个null判断呢?

对于单例设计模式,本质上要求我们只能执行一次" singleton=new LazySingle(); ",

如果没有第一个null判断,所有线程都会来抢占锁,抢占成功的正常执行,未成功的要进行等待,每次只能有一个线程执行。

如果加上第一个null判断,只要执行了一次" singleton=new LazySingle(); ",后续进入该方法的线程,无需等待同步代码块的锁,只要在第一个null判断执行结束后就可以往下走了,极大节约了性能。

[ 验证加第一个null判断的性能 ]

/**
 * 懒汉式:双重加锁 
 */
class LazySingle {
    private LazySingle(){}
    
    private static LazySingle singleton=null;
    
    public static  LazySingle  getInstance() throws InterruptedException{
        if(singleton==null){                 //第一个null判断
            synchronized(LazySingle.class){
                Thread.sleep(1);  //延迟1ms使性能的效果更明显
                System.out.println("进入同步...");
                if(singleton==null){          //第二个null判断
                    Thread.sleep(1000);
                    singleton=new LazySingle();
                }
            }
        }
        return singleton;
    }
}
class MyThread implements Runnable{
    @Override
    public void run() {
        try {
            for(int i=0;i<10;i++){
                LazySingle    l = LazySingle.getInstance();//每个线程都去获取10次单例模式的实例
                System.out.println(Thread.currentThread().getName()+"======"+l.hashCode());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }   
    }
}
public class Test {
    //线程 启动 并且 插队
    public static void startJoin(Thread t){
        try {
            t.start();  //启动线程
            t.join();   //线程插队
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
   
    public static void main(String[] args) throws InterruptedException {
        MyThread t= new  MyThread();
        
        long begin=System.currentTimeMillis();
        
        //创建多个插队的线程
        for(int j=0;j<100;j++){
            startJoin(new Thread(t));
        }
        
        LazySingle l=LazySingle.getInstance();   //获取单例模式的
        System.out.println("main方法的线程======"+l.hashCode());
        
        long end=System.currentTimeMillis();
        
        System.out.println("【耗时:"+(end-begin)+" 毫秒】");
    }
}

[ 运行结果 ]

[ 注释了第一个null之后的运行结果(性能下降) ]

 

【静态内部类方式:线程安全】

/**
 * 懒汉式:静态内部类 线程安全
 */
public class LazySingle {
    private LazySingle(){}
    
    private static class LazySingleBuilder{
        private static LazySingle singleton=new LazySingle();
    }
    
    public static LazySingle getInstance(){
        return LazySingleBuilder.singleton;
    }
}

 [ 分析 ]

当getInstance()方法第一次被调用的时候,第一次读取LazySingleBuilder.singleton,静态内部类LazySingleBuilder类得到初始化,在这个类装载并初始化的时候,会初始化它的静态域,从而创建LazySingle的实例,由于是静态的域,因此只会在虚拟机装载类的时候初始化一次,并且由虚拟机来保证它的线程安全性。

原文地址:https://www.cnblogs.com/HigginCui/p/6196501.html