单例模式
单例模式是 :一个类只允许产生一个实例化对象(如太阳类只有一个太阳对象)。
如何只产生一个是实例化对象?
- 构造方法私有化: 我们知道是构造函数是实例化对象,要只产生一个实例化对象,就需要把构造方法用private封装起来,那么外部就无法调构造方法。
- 在类里实例化一个私有全局静态对象:既然类外部无法实例化对象,就必须在类里实例化对象,而且对象需要是静态属性,如果不是静态,还得实例化对象取得这个属性,所以声明为静态,在类加载的时候就有这个对象。
- 一个静态公有的取得实例化对象方法:既然在类里实例化的对象是私有,就要有一个getXX方法来取得这个对象,同样这个方法需要是静态,在外部通过类名来调用。
单例模式又分为:饿汉模式和懒汉模式。
饿汉模式是只要加载类就会new一个对象,而懒汉模式是用到对象才new。
首先看饿汉模式代码:
package CODE;
//饿汉模式
class Person
{
private String name;
private static final Person person =new Person(); //实例化一个静态全局常量对象,
// 因为这个对象实例化出来不会再被改变,所以声明为final ,static final 是全局常量
private Person() //构造方法私有化
{}
public static Person getPerson()
{
return person;
}
}
public class Singleton
{
public static void main(String[] args)
{
Person person1=Person.getPerson();
Person person2=Person.getPerson();
Person person3=Person.getPerson();
System.out.println(person1); //CODE.Person@4554617c
System.out.println(person2); //CODE.Person@4554617c
System.out.println(person3); //CODE.Person@4554617c
//可以发现三个对象的地址是一样的,事实上只实例化了一个对象,三个引用指向同一块堆内部
}
}
懒汉模式:
class Person
{
private Person() //构造方法私有化
{}
private static Person person; //该成员变量默认值为null
public static Person getPerson()
{
if(person==null) //懒汉模式
{
person=new Person();
}
return person;
}
}
上述方法存在线程安全问题,可能会new 2个对象。
//懒汉:单例模式
class Singleton
{
private static Singleton singleton;
private Singleton()
{
}
public static Singleton getSingleton()
{
if(singleton==null)
{
//多个线程进入同步块外面
synchronized (Singleton.class)
{
if(singleton==null)
{
//再次判断,是因为假如线程1、线程2都进入第一个if判断,线程1先进入同步代码块,
//new Singleton()后,释放锁后,线程2进入同步代码块,假如没有再次判断,会再次
//实例化对象
singleton=new Singleton();
}
}
}
return singleton;
}
}
public class volatileSigton {
public static void main(String[] args) {
System.out.println(Singleton.getSingleton()==Singleton.getSingleton());
}
}
但是上述代码有问题: singleton=new Singleton();此语句JVM会做3件事:1.给singleton分配内存,2.调用Singleton的构造函数来初始化成员变量,3.将singleton指向分配的内存空间(singleton!=null)。但是JVM的即时编译器中存在指令重排的优化,即第二步第三步顺序不能保证,假如是1、3、2执行:当第三步执行完毕,第二步没有执行,被线程2抢占,此时singleton!=null,但是没有初始化,线程2直接返回singleton,那么就存在问题。那么将singleton声明为volatile,即禁止指令重排,就OK。
那么完整单例模式如下:
//懒汉:单例模式
class Singleton
{
private static volatile Singleton singleton; //声明为volatile禁止指令重排
private Singleton()
{
}
public static Singleton getSingleton()
{
if(singleton==null)
{
synchronized (Singleton.class)
{
if(singleton==null)
{
singleton=new Singleton();
}
}
}
return singleton;
}
}
public class volatileSigton {
public static void main(String[] args) {
System.out.println(Singleton.getSingleton()==Singleton.getSingleton());
}
}
多例模式:
多例模式指可以实例化出有限个对象。
多例和单例的区别是对象个数,那么在设计单例模式基础上需要加上以下步骤:
在类里定义有限个静态对象,用flag返回想要的对象,代码如下:
多例模式
class Sex
{
private String sex;
public static final int MALE_FLAG=1;
public static final int FEMALE_FLAG=2;
private static final Sex male=new Sex("男");
private static final Sex femal=new Sex("女");
private Sex(String sex)
{
this.sex=sex;
}
public static Sex getSex(int flag) //定义一个falg,告诉要什么类型对象
{
switch(flag)
{
case MALE_FLAG:
return male;
case FEMALE_FLAG:
return femal;
default:
return null;
}
}
public String toString()
{
return this.sex;
}
}
public class Singleton
{
public static void main(String[] args)
{
Sex male=Sex.getSex(Sex.MALE_FLAG);
Sex female=Sex.getSex(Sex.FEMALE_FLAG);
System.out.println(male); //男
System.out.println(female); //女
}
}
其实多例模式我们只需要理解概念即可,多例模式已经被枚举取代。
单例设计模式的应用场景:
单例模式只允许创建一个对象,因此节省内存,加快对象访问速度,因此对象需要被公用的场合适合使用,如多个模块使用同一个数据源连接对象等等。
1.资源共享时,避免由于资源操作时导致的性能或损耗,如日志文件、应用配置。
日志文件:共享的日志文件一直处于打开状态,只能有一个实例去操作,否则内容不好追加;
日志文件是后缀为.log的文件,是由系统自动记录的关于系统,应用程序,安全等方面的正常或异常事件。
数据库连接池:数据库连接池是一种共享资源,数据库,软件系统使用数据库连接池,主要是节省打开或关闭数据库的数据库连接所引起的性能损耗,用单例来维护,可以降低这种损耗。
回收站:在整个运行过程,回收站一直维护一个仅有的实例。
2.控制资源的情况,方便资源之间的相互通信,如线程池。线程池对池中线程方便控制。