23种设计模式——单例模式

定义

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

 

代码实现

饿汉式(静态常量)-可以使用

(1) 步骤

  • 构造器私有化,防止其他地方使用new进行创建实例。
  • 在类的内部创建该类的对象实例。
  • 向外暴露一个静态的公共方法getInstance(),通过该方法返回一个该类的对象实例。

(2) 代码实现

/** 
* 饿汉式(静态常量)实现单例模式 
**/
public class Singleton{
	// 1. 构造器私有化
	private Singleton(){
	}
	
	// 2. 在类的内部创建该类的对象实例
	private final static Singleton instance = new Singleton();

	// 3. 向外暴露一个静态的公共方法,返回实例对象
	public static Singleton getInstance(){
		return instance;
	}
}

/** 使用 **/
public class Do{
	psvm(){
		// 获取对象实例,test1和test2是一样的
		Singleton test1 = Singleton.getInstance();
		Singleton test2 = Singleton.getInstance();
	}
}

(3) 优缺点

  • 优点:写法简单,在类装载的时候就完成实例化。避免了线程同步问题。
  • 缺点:在类装载的时候就完成实例化,没有达到懒加载(Lazy Loading)的效果。如果从始至终没有用过这个实例,则会造成内存浪费。
  • 如果是调用getInstance()方法导致类装载是没有问题的,但是如果还有其他方式导致类装载,这时候初始化的instance对象实例就没有使用,也就是没有达到lazy loading效果。

(4) 结论

这种单例模式可用,可能造成内存浪费。

 

饿汉式(静态代码块)-可以使用

(1) 步骤

  • 构造器私有化,防止其他地方使用new进行创建实例。
  • 在类的内部声明该类的实例对象。
  • 在静态代码块中,创建该类的实例对象。
  • 向外暴露一个静态的公共方法getInstance(),通过该方法返回一个该类的对象实例。

(2) 代码实现

/** 
* 饿汉式(静态代码块)实现单例模式 
**/
public class Singleton{
	// 1. 构造器私有化
	private Singleton(){
	}

	// 2. 在类的内部声明该类的实例对象
	private static Singleton instance;
	
	// 3. 在静态代码块中,创建该类的实例对象
	static {
		instance = new Singleton();
	}
	
	// 4. 向外暴露一个静态的公共方法,返回实例对象
	public static Singleton getInstance(){
		return instance;
	}
}

/** 使用 **/
public class Do{
	psvm(){
		// 获取对象实例,test1和test2是一样的
		Singleton test1 = Singleton.getInstance();
		Singleton test2 = Singleton.getInstance();
	}
}

(3) 优缺点

  • 优缺点和饿汉式(静态常量)相同。

(4) 结论

这种单例模式可用,可能造成内存浪费。

 

懒汉式(线程不安全)-不可使用

(1) 步骤

  • 构造器私有化,防止其他地方使用new进行创建实例。
  • 在类的内部声明该类的实例对象。
  • 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。

(2) 代码实现

/** 
* 懒汉式(线程不安全)实现单例模式 
**/
public class Singleton{
	// 1. 构造器私有化
	private Singleton(){}
	
	// 2. 在类的内部声明该类的实例对象
	private static Singleton instance;

	// 3. 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。懒汉:即用到的时候,才去创建
	public static Singleton getInstance(){
		if(null == instance){
			instance = new Singleton();
		}
		return instance;
	}
}

/** 使用 **/
public class Do{
	psvm(){
		// 获取对象实例,test1和test2是一样的
		Singleton test1 = Singleton.getInstance();
		Singleton test2 = Singleton.getInstance();
	}
}

(3) 优缺点

  • 优点:用到的时候才去创建,起到了Lazy Loading效果
  • 缺点:线程不安全,在多线程的情况下,一个线程进入了if(null == instance),还没来的及往下执行,另一个线程也通过这个判断语句,这时会产生多个实例。

(4) 结论

多线程情况下,产生了多个实例,即破坏了单例模式的设计初衷。在实际开发过程中,不要使用这种方式。

 

懒汉式(线程安全,同步方法)-不建议使用

(1) 步骤

  • 构造器私有化,防止其他地方使用new进行创建实例。
  • 在类的内部声明该类的实例对象。
  • 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。该方法是同步方法,只能一个线程执行。

(2) 代码实现

/** 
* 懒汉式(线程安全)实现单例模式 
**/
public class Singleton{
	private Singleton(){}
	
	private static Singleton instance;

	// 3. 将该方法声明为同步方法,当一个线程在执行这个方法是,其他线程等待。
	public static synchronized Singleton getInstance(){
		if(null == instance){
			instance = new Singleton();
		}
		return instance;
	}
}

(3) 优缺点

  • 优点: 解决了线程安全问题。
  • 缺点: 效率低,每个线程在想获取该类的实例的时候,执行getInstance()方法,都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获取该类的实例,直接return就行了。

(4) 结论

在实际开发中,不推荐使用这种方法。

 

懒汉式(线程安全,同步代码块)-不可使用

(1) 步骤

  • 构造器私有化,防止其他地方使用new进行创建实例。
  • 在类的内部声明该类的实例对象。
  • 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。该方法中创建实例对象的代码是同步的,只能一个线程执行。

(2) 代码实现

/** 
* 懒汉式(线程安全)实现单例模式 
**/
public class Singleton{
	private Singleton(){}
	
	private static Singleton instance;

	public static synchronized Singleton getInstance(){
		if(null == instance){
			// 同步代码块,如果一个线程在执行,其他线程等待
			synchronized(Singleton.class){
				instance = new Singleton();
			}
		}
		return instance;
	}
}

(3) 优缺点

  • 缺点:本意是对同步方法效率低问题的改进,但是,这种写法并没有实现线程安全,因为多线程一旦进入if(null == instance)就会往下执行,即使等待上一个线程执行完new,也会创建多个实例。

(4) 结论

这种写法是错误的。

 

双重检查-推荐使用

(1) 步骤

  • 构造器私有化,防止其他地方使用new进行创建实例。
  • 在类的内部声明该类的实例对象,该变量声明为共享变量。
  • 提供一个静态的公有方法,当使用到该方法时,才去创建该类的实例对象。该方法中创建实例对象的代码是同步的,只能一个线程执行。新增双重检查

(2) 代码实现

/** 
* 双重检查实现单例模式 
**/
public class Singleton{
	private Singleton(){}
	
	// volatile修饰后,该变量变为共享变量,一个线程更改了它的值后,会立刻刷新到主存(内存)中,其他线程再读取会读取到最新的值
	private static volatile Singleton instance;

	public static Singleton getInstance(){
		if(null == instance){	// 第一重检查
			// 只有一个线程进入执行,其他线程等待
			synchronized(Singleton.class){
				if(null == instance){	// 第二重检查
					// 该线程一旦创建该类的实例对象,其他线程过不了第二重检查
					instance = new Singleton();	
				}
			}
		}
		return instance;
	}
}

(3) 优缺点

  • 优点: 双重检查(Double-Check)概念是多线程开发中经常使用到的。保证了线程安全,避免了反复进行方法同步效率高,实现了Lazy Loading。
  • volatitle关键字是为了禁止指令重排,否则会导致多线程访问到的是一个还未初始化的对象。

(4) 结论

在实际开发中,推荐使用这种方法实现单例模式

(5)应用实例

package springbootDemo.util;
 
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
 
/**
 * @author jiangfeng 2018/8/23 11:13
 * redis线程池
 */
public class JedisPoolUtil {
    private JedisPoolUtil() {
    }
 
    // double check实现单例,加volatile防止指令重排,保证new操作的有序性
    private volatile static JedisPool jedisPool;
    public static JedisPool jedisPool(){
        if(jedisPool == null){
            synchronized (JedisPoolUtil.class){
                if(jedisPool == null){
                    jedisPool= new JedisPool(new JedisPoolConfig(), "10.20.69.146", 6379);
                }
            }
        }
        return jedisPool;
    }
 
}

 

静态内部类-推荐使用

(1) 步骤

  • 构造器私有化
  • 创建一个静态内部类,该内部类中有一个静态属性Singleton
  • 提供静态公有方法,直接返回该静态内部类的静态属性

(2) 代码实现

/** 
* 静态内部类实现单例模式 
**/
public class Singleton{
	// 1. 构造器私有化
	private Singleton(){}

	// 2. 创建一个静态内部类,该内部类中有一个静态属性Singleton
	private static class SingletonInstance{
		private static final Singleton INSTANCE = new Singleton();
	}

	// 3. 提供静态公有方法,直接返回该静态内部类的静态属性
	public static Singleton getInstance(){
		return SingletonInstance.INSTANCE;
	}
}

(3) 分析

  • 在Singleton类装载的时候,内部类SingletonInstance是不装载的,在调用getInstance()的时候才装载内部类,因此实现了懒加载,用到的时候才创建实例对象。
  • 类的静态属性只会在第一次加载类的时候初始化,在类的初始化时,是线程安全的。JVM帮助我们实现了线程安全。
  • JVM在类装载的时候,是线程安全的,因此使用了JVM底层的机制实现线程安全。

(4) 优缺点

  • 优点:线程安全、懒加载、效率高。

(5) 结论

推荐使用

(6)应用实例

package springbootDemo.util;
 
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
 
/**
 * @author jiangfeng 2018/8/23 11:13
 * redis线程池
 */
public class JedisPoolUtil {
    private JedisPoolUtil() {
    }
 
    // 内部类实现单例
    public static JedisPool getJedisPool() {
        return JedisPoolUtil_.jedisPool;
    }
    private static class JedisPoolUtil_ {
        static JedisPool jedisPool = new JedisPool(new JedisPoolConfig(), "10.20.69.146", 6379);
    }
}

 

枚举-推荐使用

(1) 步骤:

把想要写成单例的类写成Enum就行了,因为Enum也支持方法定义,可以满足正常需求。

(2) 代码实现

/** 
* 枚举实现单例模式 
**/
public enum Singleton{
	INSTANCE;	// 属性
}

/** 使用 **/
public class Do{
	psvm(){
		// 获取对象实例,test1和test2是一样的
		Singleton test1 = Singleton.INSTANCE;
		Singleton test2 = Singleton.INSTANCE;
	}
}

(3) 优缺点

  • 优点:借助JDK1.5中添加的枚举来实现单例模式。不仅能避免多线程同步问题,还能防止反序列化重新创建新的对象。

(4) 结论

推荐使用

(5)应用实例

package springbootDemo.util;
 
import redis.clients.jedis.JedisPool;
import redis.clients.jedis.JedisPoolConfig;
 
/**
 * @author jiangfeng 2018/8/23 14:44
 */
public enum JedisPoolEnum {
    JEDIS_POOL;
    private JedisPool jedisPool = null;
 
    private JedisPoolEnum() {
        jedisPool = new JedisPool(new JedisPoolConfig(),"10.20.69.146",6397);
    }
 
    public JedisPool getJedisPool() {
        return jedisPool;
    }
}




@Test
public void testJedis(){
    JedisPool jedisPool5 = JedisPoolEnum.JEDIS_POOL.getJedisPool();
    JedisPool jedisPool6 = JedisPoolEnum.JEDIS_POOL.getJedisPool();
 
    System.out.println(jedisPool5 == jedisPool6);
}

 

转载:https://blog.csdn.net/qq_29668759/article/details/105343943?utm_medium=distribute.pc_relevant.none-task-blog-baidujs_title-6&spm=1001.2101.3001.4242    仅供学习

          https://blog.csdn.net/u012175512/article/details/81980300

 

 

 

posted @ 2021-01-11 17:00  彼岸-花已开  阅读(33)  评论(0编辑  收藏  举报