设计模式之单例模式

一、单例模式(singleton)

是Java设计模式中最常用的模式之一,是面试过程中经常考察的点,需要手写单例。单是唯一,例是实例,即某个类在系统中只能存在唯一的一个实例对象可供使用。

二、要点

1、 构造器私有化,由于类的实例唯一,所以外界不能通过new关键字创建实例对象

2、 在类的内部创建实例,并定义一个静态成员变量保存类的实例

3、 需要向外界提供获得实例的方式

a) 直接暴露

b) 通过get方法获取

三、几种常见的单例模式,一般分为两类:饿汉式和懒汉式

饿汉式

随着类的加载不管需不需要都会直接创建实例对象,不存在线程安全问题。

a. 直接实例化 (简单直观)
/**
* 单例模式之直接实例方式
* @author lux81
*
*/
public class Singleton1 {
   //静态常量保存实例对象
public static final Singleton1 INSTANCE = new Singleton1();
//构造器私有化
private Singleton1() {
System.out.println("直接实例化");
}
}
//调用测试
public class TestSingleton1 {
public static void main(String[] args) {
       //直接通过类名.属性 调用
Singleton1 instance = Singleton1.INSTANCE;
System.out.println(instance);
}
}
b. 枚举式 (最简洁)
/**
* 单例模式之枚举方式
* 枚举类型 表示该类型的对象是有限的几个
* 我们可以限定为一个 就成了单例
* @author lux81
*
*/
public enum Singleton2 {
INSTANCE;
//添加一个测试方法
public void sayHello() {
System.out.println("hello");
}
}

//测试类
public class TestSingleton2 {
public static void main(String[] args) {
Singleton2 instance2 = Singleton2.INSTANCE;
//调用测试方法 输出 hello
instance2.sayHello();
//直接输出实例对象名
System.out.println(instance2);
}
}

 

c. 静态代码块 (适合复杂实例化)

在实例化对象的时候可能会初始化一些数据

/**
* 单例模式之静态代码块方式
* 加入在实例化对象时需要初始化一些配置信息可以使用该方式
* @author lux81
*
*/
public class Singleton3 {
public static final Singleton3 INSTANCE;
//测试从配置文件中获取初始化数据
private String info;

static {
//加载配置文件
Properties prop = new Properties();
try {
prop.load(Singleton3.class.getClassLoader().getResourceAsStream("test.properties"));
} catch (IOException e) {
throw new RuntimeException(e);
}

//实例化对象 并获取初始化数据
INSTANCE = new Singleton3(prop.getProperty("name"));
}

private Singleton3(String info) {
this.info = info;
}
//setter和 getter方法略
}

//测试
public class TestSingleton3 {
public static void main(String[] args) {
Singleton3 instance3 = Singleton3.INSTANCE;
       //调用方法获取配置文件 name属性值
System.out.println(instance3.getInfo());
}
}

 

懒汉式

延迟创建或者是需要的时候才创建

a. 线程不安全(适用于单线程)
/**
* 单例模式之懒汉式(线程不安全,适合单线程)
* @author lux81
*
*/
public class Singleton4 {
private static Singleton4 instance;

private Singleton4() {
}

//提供给外面获取实例对象的get方法
public static Singleton4 getInstance() {
//如果对象不存在则创建,已经存在直接返回
if (instance == null) {
           //此处如果在多线程的情况下,会出现创建多个不同对象的情况
           try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton4();
}
return instance;
}
}

//测试
public class TestSingleton4 {
public static void main(String[] args) {
Singleton4 s1 = Singleton4.getInstance();
Singleton4 s2 = Singleton4.getInstance();
System.out.println(s1 == s2); //返回结果 true
       
       //使用多线程方式测试
       Callable<Singleton4> c = new Callable<Singleton4>() {
@Override
public Singleton4 call() throws Exception {
return Singleton4.getInstance();
}
};
       //定义一个线程池 数量设置为2
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Singleton4> f3 = es.submit(c);
Future<Singleton4> f4 = es.submit(c);

Singleton4 s3 = f3.get();
Singleton4 s4 = f4.get();

System.out.println(s3 == s4); //结果为false

es.shutdown();
}
}

 

b. 线程安全(适用于多线程)

在方式a基础上进行优化

/**
* 单例模式之懒汉式(线程安全)
* @author lux81
*
*/
public class Singleton5 {
private static Singleton5 instance;

private Singleton5() {
}

//提供给外面获取实例对象的get方法
public static Singleton5 getInstance() {
       //如果已经存在对象 就不需要再加同步锁
if (instance == null) {
//加上同步代码块 保证线程安全
synchronized (Singleton5.class) {
if (instance == null) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
instance = new Singleton5();
}
}
}
return instance;
}
}
//测试
public class TestSingleton5 {
public static void main(String[] args) throws InterruptedException, ExecutionException {
Callable<Singleton4> c = new Callable<Singleton4>() {
@Override
public Singleton4 call() throws Exception {
return Singleton4.getInstance();
}
};
ExecutorService es = Executors.newFixedThreadPool(2);
Future<Singleton4> f1 = es.submit(c);
Future<Singleton4> f2 = es.submit(c);

Singleton4 s1 = f1.get();
Singleton4 s2 = f2.get();

System.out.println(s1 == s2); //返回结果为 true

es.shutdown();
}
}

 

c. 静态内部类方式(适用于多线程)

方式b看起来有点复杂,而静态内部类方式既能实现延迟加载,也能保证线程安全

/**
* 单例模式之懒汉式(静态内部类)
* 只有在调用getInstance()方法时才会 加载和初始化内部类,进而才会创建实例对象
* 静态内部类不会随着外部类的加载和初始化而初始化,是独立的,单独加载和初始化
* @author lux81
*
*/
public class Singleton6 {
private Singleton6() {
}
//定义静态内部类 创建实例化对象
private static class Inner{
private static final Singleton6 INSTANCE = new Singleton6();
}
//还是通过get方法获取
public static Singleton6 getInstance() {
return Inner.INSTANCE;
}
}

 

posted @ 2020-06-17 00:26  明月_清风  阅读(248)  评论(0编辑  收藏  举报