【JAVA单例模式详解】

时间:2022-12-24 20:00:34

设计模式是一种思想,适合于任何一门面向对象的语言。共有23种设计模式。

单例设计模式所解决的问题就是:保证类的对象在内存中唯一。

举例:

A、B类都想要操作配置文件信息Config.java,所以在方法中都使用了Config con=new Config();但是这是两个不同的对象。对两者的操作互不影响,不符合条件。

解决思路:

1.不允许其他程序使用new创建该类对象。(别人new不可控)
2.在该类中创建一个本类实例。
3.对外提供一个方法让其他程序可以获取该对象。

解决方法:单例模式。

步骤:
1.私有化该类的构造函数
2.通过new在本类中创建一个本类对象。
3.定义一个共有的方法将创建的对象返回。

一、饿汉式

雏形:

 class Single
{
private static final Single s=new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}

为什么方法是静态的:不能new对象却想调用类中方法,方法必然是静态的,静态方法只能调用静态成员,所以对象也是静态的。

为什么对象的访问修饰符是private,不能是public 吗?不能,如果访问修饰符是Public,则Single.s也可以得到该类对象,这样就造成了不可控。

举例:

 public class Single
{
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
private static final Single s=new Single();
private Single(){}
public static Single getInstance()
{
return s;
}
}
 public class Main {
public static void main(String args[])
{
Single s=Single.getInstance();
s.setName("张三");
System.out.println(s.getName());
}
}

最后结果为:张三

 二、懒汉式

前面的是饿汉式单例模式,下面开始讲解懒汉式单例模式。

 class Single
{
private static Single s=null;
private Single(){}
public static Single getInstance()
{
if(s==null)
s=new Single();
return s;
}
}

我们可以看到懒汉式和饿汉式相比的区别就是懒汉式创建了延迟对象同时饿汉式的实例对象是被修饰为final类型。

懒汉式的好处是显而易见的,它尽最大可能节省了内存空间。

但是懒汉式又有着弊端,在多线程编程中,使用懒汉式可能会造成类的对象在内存中不唯一,虽然通过修改代码可以改正这些问题,但是效率却又降低了。而且如果想要使用该类对象,就必须创建对象,所以虽然貌似使用懒汉式有好处,但是在实际开发中使用的并不多。

总结:

懒汉式在面试的时候经常会被提到,因为知识点比较多,而且还可以和多线程结合起来综合考量。

饿汉式在实际开发中使用的比较多。

三、懒汉式在多线程中的安全隐患以及解决方案、优化策略。

下面分析懒汉式在多线程中的应用和出现的问题以及解决方法。

懒汉式在多线程中出现的问题:

懒汉式由于多加了一次判断

if(s==null)

导致了线程安全性隐患。因为CPU很有可能在执行完if语句之后切向其它线程。解决线程安全性问题的关键就是加上同步锁。

1.使用同步函数

我们可以直接使用同步函数:

class Single
{
private static Single s=null;
private Single()
{
}
public static synchronized Single getInstance()
{
if(s==null)
s=new Single();
return s;
}
}

但是直接使用同步函数的方法效率十分低下,因为每次调用此方法都需要先判断锁。

2.使用同步代码块

我们也可以使用同步代码块:

 class Single
{
private static Single s=null;
private Single()
{ }
public static Single getInstance()
{
synchronized(Single.class)
{
if(s==null)
s=new Single();
return s;
}
}
}

但是每次调用getInstance方法仍然会判断锁,事实上没有改变效率问题。

3.最终解决方案

我们可以使用另外一种方式,达到只判断一次锁,并且实现同步的目的:

 class Single
{
private static Single s=null;
private Single()
{ }
public static Single getInstance()
{
if(s==null)//和上面的相比只是多增加了一次判断
{
synchronized(Single.class)
{
if(s==null)
s=new Single();
return s;
}
}
return s;
}
}

观察代码可以发现和上面的代码相比,只是增加了一次判断而已,但是,这一次判断却解决了效率问题。

我们可以分析一下这个代码:

4.最终解决方案代码分析和总结

假设我们现在并没有创建单例对象,即s==null,那么我们调用getInstance方法的时候,会进入if块,然后进入同步代码块,此时,别的线程如果想要创建Single实例,就必须获取锁;等当前线程创建完实例对象,释放锁之后,假设正巧有几个线程已经进入了if块中,它们会拿到锁,进入同步代码块,但是由于进行了判空操作,所以不会创建Single实例,而是直接返回已经创建好的Single实例。如果有多个其他线程进入了if块,当它们依次进入同步代码块的时候,同理也不会创建新的Single实例。而没有进入if块的线程,判空操作之后不满足条件,进不了if块,而直接执行了下一条语句return s;其后的线程调用getInstance方法时,只会判断一次s==null,不满足条件直接返回Single单例s,这样就大大提高了了执行效率。

总结:在代码

 if(s==null)
{
synchronized(Single.class)
{
if(s==null)
s=new Single();
return s;
}
}
return s;
中,第一行代码是第一次判空操作,目的是提高效率;第三行代码是同步代码块的入口,目的是保证线程安全;第五行代码进行第二次判空操作是为了保证单例对象的唯一性