1.什么是单例模式?
单例模式是是一种常用的软件设计模式,最简单的设计模式之一。
单例模式是一种对象创建型模式,使用单例模式可以保证一个类只生成唯一的实例对象。即在整个程序空间中,该类只产生一个实例对象
应用场景:
-
一些资源管理器常常设计成单例模式。
-
在多个线程之间,比如servlet中,共享同一个资源或操作同一个对象。
-
在整个程序空间使用全局变量,共享资源
-
大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。
因为单例模式保证一个类只产生一个实例,所以这些情况下,单例模式就派上用场了。
2.有哪几种形式?
第一种形式:饿汉式
单例类
/**
* 饿汉式 单例模式
* 可以在多线程中保证,线程安全
*/
public class Person1 {
private String name;
public static Person1 person = new Person1();
//将构造方法私有化,这样在其他类中无法使用
private Person1(){
}
//提供一个全局的静态方法
public static Person1 getPerson(){
return person;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
测试类
public class Test {
public static void main(String[] args) {
Person1 p1 = Person1.getPerson();//通过静态方法来创建对象实例
p1.setName("李四");
System.out.println(p1.getName());
Person1 p2 = Person1.getPerson();
System.out.println(p2 == p1);//true 说明p1和p2的地址相同,同一个对象
System.out.println(p2.getName());//李四 p2并没有赋值,但是name仍然是李四
p2.setName("张三");
System.out.println(p1.getName());
}
}
饿汉式每次调用的时候不用做创建,直接返回已经创建好的实例。这样虽然节省了时间,但是却占用了空间,实例本身为static的,会一直在内存中带着。因为饿汉单例类在类的初始化时,已经自行实例化,所以线程安全。
第二种形式:懒汉式,也是常用的形式。
/**
* 懒汉式
* 只能在单线程中保证
*/
public class Person2 {
private String name;
private static Person2 person;
//将构造方法私有化,这样在其他类中无法使用
private Person2(){
}
//提供一个全局的静态方法
public static Person2 getPerson(){
if(person == null){
person = new Person2();
}
return person;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
顾名思义,lazy loading(延迟加载,一说懒加载),在需要的时候才创建单例对象,而不是随着软件系统的运行或者当类被加载器加载的时候就创建。当单例类的创建或者单例对象的存在会消耗比较多的资源,常常采用lazy loading策略。这样做的一个明显好处是提高了软件系统的效率,节约内存资源。
当多个线程并发时,可能会有几个线程同时进入getPerson()的if语句中,然后分别创建不同的person对象,这就不能保证只有唯一一个对象,所以懒汉式线程不安全,只有单线程时是安全的。
通过使用同步方法(synchronized)修改getPerson()可以使程序符合单例模式要求:
public static synchronized Person2 getPerson(){
if(person == null){
person = new Person2();
}
return person;
}
当某个线程执行同步方法时(synchronized),其他线程不能执行该方法,即独占,从而保证只有一个实例。
但是这样的话,会带来新的问题。当某个线程在执行该方法时,其他线程就会等待,效率较低。改进的方法是:只需要把需要同步的语句同步,这样就引出了第三种形式的单例模式。
第三种形式:双重检查
/**
* 双重检查
* 效率高
*/
public class Person4 {
private String name;
private static Person4 person;
//将构造方法私有化,这样在其他类中无法使用
private Person4(){
}
//改进:只需要把需要同步的语句同步
//但是可能有多个线程进入第一个if,所以需要第二次判断
public static Person4 getPerson(){
if(person == null){
synchronized (Person4.class) {
if(person == null){
person = new Person4();
}
}
}
return person;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
这种经过改良的单例模式是线程安全的。
效率提高的原因?将同步内容移动到if内部,提高了执行的效率,不必每次获取对象时都进行同步,只有第一次才同步,创建了以后就没必要了。如果双重if判断,100的线程可以同时if判断,理论消耗的时间只有一个if判断的时间。
为什么需要第二次判断?多个线程并发时,可能有多个线程进入第一个if,所以需要第二次判断。