首先介绍下单例模式的3个注意点:
1)单例类只能有一个实例;
2)单例类必须自己创建自己的唯一实例;
3)单例类必须给所有其他对象提供这一实例;
下面是8种实现的具体细节:
1.饿汉式(静态常量)
2.饿汉式(静态代码块)
3.懒汉式(线程不安全)
4.懒汉式(线程安全,同步方法)
5.懒汉式(线程不安全,同步方法)
6.双重检查
7.静态内部类
8.枚举
在开发时,如果是单线程,推荐使用第1、2实现;如果是多线程,推荐使用第6、7、8实现。
1.饿汉式(静态常量)
public class SingletonTest01 {
public static void main(String[] args) {
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
System.out.println(s1==s2);
System.out.println("s1.hashCode():"+s1.hashCode());
System.out.println("s2.hashCode():"+s2.hashCode());
}
}
class Singleton{
private static final Singleton singleton=new Singleton();
private Singleton(){}//构造器私有化,防止new
public static Singleton getInstance(){
return singleton;
}
}
输出:
true
s1.hashCode():356573597
s2.hashCode():356573597
分析:1)优点:这种写法比较简单,在类加载时就完成实例化,并且可以避免线程问题;
2)缺点:在类加载时就实例化,没有达到lazy loading(懒加载)的效果。如果自始至终都没有用过这个实例,则会造成内存浪费;
3)这种方式基于类加载机制避免了多线程问题,但是在单例模式中大多都是调用getInstance方法,除了调用getInstance方法外导致类加载的原因有很多(下面列举了一些),如果是别的原因导致类加载,这时就会创建实例,就没有达到懒加载的效果;
结论:这种单例模式可用,可能造成内存浪费。
造成类加载的原因:
1)创建类的实例,也就是new一个对象;
2)访问某个类或接口的静态变量,或者对静态变量赋值;
3)调用类的静态方法;
4)反射;
5)初始化一个类的子类;
6)JVM启动时标记的启动类。
2.饿汉式(静态代码块)
public class SingletonTest02 {
public static void main(String[] args) {
Singleton s1= Singleton.getInstance();
Singleton s2= Singleton.getInstance();
System.out.println(s1==s2);
System.out.println("s1.hashCode():"+s1.hashCode());
System.out.println("s2.hashCode():"+s2.hashCode());
}
}
class Singleton{
private static Singleton singleton;
static{
singleton=new Singleton();
}
private Singleton(){}//私有构造方法防止new
public static Singleton getInstance(){
return singleton;
}
}
输出:
true
s1.hashCode():356573597
s2.hashCode():356573597
分析:1)这种方式和上面的方式类似,只不过将类的实例化放在了静态代码块里,也是在类加载时就完成了实例化,优缺点和上面相同;
结论:这种单例模式可用,可能造成内存浪费。
3.懒汉式(线程不安全)
public class SingletonTest03 {
public static void main(String[] args) {
Singleton s1= Singleton.getInstance();
Singleton s2= Singleton.getInstance();
System.out.println(s1==s2);
System.out.println("s1.hashCode():"+s1.hashCode());
System.out.println("s2.hashCode():"+s2.hashCode());
}
}
class Singleton{
private static Singleton singleton;
private Singleton(){}//私有构造方法防止new
public static Singleton getInstance(){
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
分析:1)起到了lazy loading的效果,但只能在单线程下使用;
2)如果在多线程下使用,假如一个线程进入了if(singleton==null)判断语句,还未来得及往下执行(还未执行singleton=new Singleton();因此还未创建实例),另一个线程也通过了这个判断语句,则这两个线程会创建两个实例。因此在多线程环境下不能使用这种方式实现单例模式;
结论:在实际开发中,不要使用这种方式。
4.懒汉式(线程安全,同步方法)
public class SingletonTest04 {
public static void main(String[] args) {
Singleton s1= Singleton.getInstance();
Singleton s2= Singleton.getInstance();
System.out.println(s1==s2);
System.out.println("s1.hashCode():"+s1.hashCode());
System.out.println("s2.hashCode():"+s2.hashCode());
}
}
class Singleton{
private static Singleton singleton;
private Singleton(){}//私有构造方法防止new
public static synchronized Singleton getInstance(){//添加synchronized关键字
if(singleton==null){
singleton=new Singleton();
}
return singleton;
}
}
分析:1)解决了线程安全问题;
2)效率太低了,每个线程在想获得类的实例时,执行getInstance方法都要同步。而这个方法只要执行一次实例化代码就够了,后面的想获得实例,只需要return就行了。
结论:在实际开发中,不推荐这种方式。
5.懒汉式(线程不安全,同步代码块)
public class SingletonTest05 {
public static void main(String[] args) {
Singleton s1= Singleton.getInstance();
Singleton s2= Singleton.getInstance();
System.out.println(s1==s2);
System.out.println("s1.hashCode():"+s1.hashCode());
System.out.println("s2.hashCode():"+s2.hashCode());
}
}
class Singleton{
private static Singleton singleton;
private Singleton(){}//私有构造方法防止new
public static Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class){
singleton=new Singleton();
}
}
return singleton;
}
}
分析:线程不安全,与方式3相似。
结论:不推荐使用。
6.双重检查
public class SingletonTest06 {
public static void main(String[] args) {
Singleton s1= Singleton.getInstance();
Singleton s2= Singleton.getInstance();
System.out.println(s1==s2);
System.out.println("s1.hashCode():"+s1.hashCode());
System.out.println("s2.hashCode():"+s2.hashCode());
}
}
class Singleton{
private static volatile Singleton singleton;
private Singleton(){}//私有构造方法防止new
public static Singleton getInstance(){
if(singleton==null){
synchronized (Singleton.class){
if(singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
分析:1)双重检查是多线程中常用到的,进行两次检查,可以保证线程的安全;
2)实例代码只需执行一次,后面再次访问时,第一个if条件将不满足,直接return实例,避免了反复进行方法同步;
结论:在实际开发中,推荐使用这种方式实现单例模式
另:双重检查为何要加volatile关键字,参考博客:volatile关键字在单例模式(双重校验锁)中的作用,注意看博主的评论。
7.静态内部类
public class SingletonTest07 {
public static void main(String[] args) {
Singleton s1= Singleton.getInstance();
Singleton s2= Singleton.getInstance();
System.out.println(s1==s2);
System.out.println("s1.hashCode():"+s1.hashCode());
System.out.println("s2.hashCode():"+s2.hashCode());
}
}
class Singleton{
private Singleton(){}//私有构造方法防止new
private static class SingletonInstance{
private static final Singleton INSTANCE=new Singleton();
}
public static Singleton getInstance(){
return SingletonInstance.INSTANCE;
}
}
分析:1)采用了类加载的机制保证线程安全;
2)静态内部类方式使得在Singleton类被加载时不会立即实例化,而是在调用getInstance方法时实例化;
3)类的静态属性只会在类第一次加载时初始化,所以在这里,JVM保证了线程安全性,在类进行初始化时,别的类是无法进入的;
4)优点:线程安全,利用静态内部类实现延时加载,效率高
结论:推荐使用
8.枚举
public class SingletonTest08 {
public static void main(String[] args) {
Singleton s1= Singleton.INSTANCE;
Singleton s2= Singleton.INSTANCE;
System.out.println(s1==s2);
System.out.println("s1.hashCode():"+s1.hashCode());
System.out.println("s2.hashCode():"+s2.hashCode());
}
}
enum Singleton{
INSTANCE;
}
分析:利用jdk1.5中添加的枚举实现单例模式,不仅能够避免多线程同步问题,而且还能防止反序列化重新创建新的对象;
结论:推荐使用
我看到其他博友写6种实现方式的比较多,我这里把饿汉式细分成了静态变量和静态代码块两种实现方式,至于第5种实现方式,则是为了提醒大家这种实现方式并不能保证线程安全,原因和第3种实现方式相同,都是一个线程进入if后,还未执行下面的代码,假设有另一个线程抢占了CPU资源,开始执行代码,也通过了if语句,那么就会产生两个实例。
以上是我在观看了 尚硅谷Java设计模式 视频后,结合视频笔记以及自己理解整理出来的,如有错误或建议请多多指教。