Singleton-单例模式
单例顾名思义就是单个实例,保证在内存中只有一个实例。
使用场景用在只需要有一个实例存在的时候,不想让别人在 new 一个实例出来的时候。
现在单例的写法玩的越来越花哨了,各种写法,本次介绍一下常用几种方式及优缺点。
1、饿汉式
特点: JVM保证线程安全
优点:写法比较简单,推荐使用
缺点:不管是否用到,类装载时就完成实例化了
public class Singleton01 {
/**
* 饿汉式
* 类加载到内存后,只实例化一个,JVM保证线程安全
*
* 这种写法比较简单,缺点就是不管是否用到,类装载时就完成实例化了。
*/
private final static Singleton01 singleton = new Singleton01();
// 构造方法设置成private,别人也就不能 new 了
private Singleton01(){};
public static Singleton01 getInstance() {
return singleton;
}
// 可以定义自己的业务方法...
public void test() {
System.out.println("----- test ------");
}
public static void main(String[] args) {
Singleton01 s1 = Singleton01.getInstance();
Singleton01 s2 = Singleton01.getInstance();
// s1 == s2 返回 true
System.out.println(s1 == s2);
}
}
2、懒汉式
特点:在用的时候初始化,不用不初始化
优点:即特点吧
缺点:非线程安全
注:为什么是非线程安全的?
如果一个线程调用 getInstance() 判断 singleton 为 null , 在还没有执行到 singleton = new Singleton02(); 这一行的时候。
另外一个线程也开始调用 getInstance() 这时候在判断 singleton 也是为 null。
这样就导致两个线程分别执行了一次 singleton = new Singleton02();
所以说这个 singleton 就不再是同一个实例了。
public class Singleton02 {
/**
* 懒汉式
* 在用的时候在初始化,不用不初始化。
* 但是 线程不安全, 为什么是线程不安全的呢?
*
* 如果一个线程调用 getInstance() 判断 singleton 为 null , 在还没有执行到 singleton =
new Singleton02(); 这一行的时候。
* 另外一个线程也开始调用 getInstance() 这时候在判断 singleton 也是为 null。
* 这样就导致两个线程分别执行了一次 singleton = new Singleton02();
* 所以说这个 singleton 就不再是同一个实例了。
*/
private static Singleton02 singleton = null;
private Singleton02(){}
public static Singleton02 getInstance() {
if(singleton == null) {
singleton = new Singleton02();
}
return singleton;
}
// 可以定义自己的业务方法...
public void test() {
System.out.println("----- test ------");
}
public static void main(String[] args) {
Singleton02 s1 = Singleton02.getInstance();
Singleton02 s2 = Singleton02.getInstance();
// s1 == s2 返回 true
System.out.println(s1 == s2);
}
}
3、懒汉式-线程安全
特点:线程安全
优点:即特点
缺点:通过 synchronized 来解决线程安全问题, 但牺牲的是效率
public class Singleton03 {
/**
* 懒汉式-线程安全
* 可以通过 synchronized 来解决线程安全问题, 但牺牲的是效率。
*/
private static Singleton03 singleton = null;
private Singleton03(){}
public static synchronized Singleton03 getInstance() {
if(singleton == null) {
singleton = new Singleton03();
}
return singleton;
}
// 可以定义自己的业务方法...
public void test() {
System.out.println("----- test ------");
}
public static void main(String[] args) {
Singleton03 s1 = Singleton03.getInstance();
Singleton03 s2 = Singleton03.getInstance();
// s1 == s2 返回 true
System.out.println(s1 == s2);
}
}
4、懒汉式 - 线程安全 - 双重检查
特点:线程安全
优点:在加锁的同时,减小同步代码块的方式来提高效率
缺点:还是会牺牲效率
public class Singleton04 {
/**
* 懒汉式 - 线程安全 - 双重检查
*
* 在加锁的同时,减小同步代码块的方式来提高效率
*/
private static Singleton04 singleton = null;
private Singleton04(){}
public static synchronized Singleton04 getInstance() {
if(singleton == null) {
synchronized(Singleton04.class) {
if(singleton == null) {
singleton = new Singleton04();
}
}
}
return singleton;
}
// 可以定义自己的业务方法...
public void test() {
System.out.println("----- test ------");
}
public static void main(String[] args) {
Singleton04 s1 = Singleton04.getInstance();
Singleton04 s2 = Singleton04.getInstance();
// s1 == s2 返回 true
System.out.println(s1 == s2);
}
}
5、饿汉式-静态内部类
特点:线程安全
优点:在加载外部类时不会加载内部类,这样可以实现懒加载
缺点:这算是比较完美的写法了,暂时没有什么缺点吧
public class Singleton05 {
/**
* 静态内部类
*
* 在加载外部类时不会加载内部类,这样可以实现懒加载
*
* 比较完美的写法
*/
private Singleton05(){}
private static class Singleton05Holder{
private final static Singleton05 singleton = new Singleton05();
}
public static Singleton05 getInstance() {
return Singleton05Holder.singleton;
}
// 可以定义自己的业务方法...
public void test() {
System.out.println("----- test ------");
}
public static void main(String[] args) {
Singleton05 s1 = Singleton05.getInstance();
Singleton05 s2 = Singleton05.getInstance();
// s1 == s2 返回 true
System.out.println(s1 == s2);
}
}
6、枚举
特点:好像是Java创始人之一提出的,在 Effective Java 书中有提到
优点:解决了线程安全问题,还可以防止反序列化
注:枚举单例为什么不会被反序列化?
枚举类没有构造方法
public enum Singleton06 {
/**
* 堪称最完美的写法 在 Effective Java 书中有提到
*
* 解决了线程安全问题,还可以防止反序列化
*
* 枚举单例不会被反序列化的原因是:枚举类没有构造方法。
*/
singleton;
// 可以定义自己的业务方法...
public void test() {
System.out.println("----- test ------");
}
public static void main(String[] args) {
Singleton06 s1 = Singleton06.singleton;
Singleton06 s2 = Singleton06.singleton;
// s1 == s2 返回 true
System.out.println(s1 == s2);
}
}
-------------------------------------------------------------------------- END -----------------------------------------------------------------------------
在工作中一般常用的还是第一种方式,简单、安全,虽然有一点点小瑕疵,个人觉得还可以。
如有问题,欢迎交流!