什么是单例模式
单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。
其实,GoF(《Design Patterns: Elements of Reusable Object-Oriented Software》(即后述《设计模式》一书),由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著(Addison-Wesley,1995)。这几位作者常被称为"四人组(Gang of Four)"。)对单例模式的定义是:保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。
这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义。
在应用系统开发中,我们常常有以下需求:
1、在多个线程之间,比如servlet环境,共享同一个资源或者操作同一个对象
2、在整个程序空间使用全局变量,共享资源
3、大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。
因为Singleton模式可以保证为一个类只生成唯一的实例
对象,所以这些情况,Singleton模式就派上用场了
单例模式的三个要点
1、某个类只能有一个实例;(那么怎么保证呢)
2、它必须自行创建这个实例;(自己怎么创建?)
3、它必须自行向整个系统提供这个实例。(怎么向整个系统提供这个实例)
1、要保证某个类只有一个实例,所以构造函数得私有化,这样,外界程序就不能通过new()的方式获取该类的实例。
2、自己创建这个实例,即在本类中提供静态方法,用于创建这个类的实例(这个方法能供外界调用),那既然这个公共进哪个静态方法能供外界调用,那就不免会多个代码调用这个方法,那岂不是还保证不了系统中始终只有一个这样的实例,所以还需注意,在那个静态方法中返回的实例必须是此类的静态属性变量。(即在本类中还需有一个该类对象的静态属性引
代码体现——方式一(饿汉式)
因为“一进来就创建对象”,形象称之为饿汉式。
饿汉式,不存在线程安全问题
public class Person {
//静态引用,在内存中只有一份。
public static final Person person = new Person();
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//构造函数私有化
private Person() {
}
//提供一个全局的静态方法
public static Person getPerson() {
return person;
}
}
public class MainClass {
public static void main(String[] args) {
Person2 per = Person2.getPerson();
Person2 per2 = Person2.getPerson();
per.setName("zhangsan");
per2.setName("lisi");
System.out.println(per.getName());
System.out.println(per2.getName());
}
}
运行结果
lisi
lisi
代码体现——方式一(懒汉式)
public class Person2 {
private String name;
//这里和饿汉式不同的是:这里只是个引用,并未创建对象
private static Person2 person;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//构造函数私有化
private Person2() {
}
//提供一个全局的静态方法
public static Person2 getPerson() {
//判断是否为空
if(person == null) {
person = new Person2();
}
return person;
}
}
public static void main(String[] args) throws Exception {
Person2 per = Person2.getPerson();
Person2 per2 = Person2.getPerson();
per.setName("zhangsan");
per2.setName("lisi");
System.out.println(per.getName());
System.out.println(per2.getName());
}
}
单线程下是没有问题的
运行结果:
lisi
lisi
但是在多线程下,就容易出现问题,问题就出现在Person2类的getPerson()方法上,因为这个方法分了三步:
正是因为这样,才可能会出现线程安全问题
比如:线程1刚判断我爱你是否为空,这时,线程一休眠了,此时,线程二进,进方法了,由于线程一正在休眠,还未创建对象,此时线程二刚好进来判断,判断的结果肯定为空,所以线程二自然也会去创建对象,这样程序执行下来,这个person就有两个实例了。
代码:
public class Person2 {
private String name;
//这里和饿汉式不同的是:这里只是个引用,并未创建对象
private static Person2 person;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//构造函数私有化
private Person2() {
}
//提供一个全局的静态方法
public static Person2 getPerson() throws Exception {
//判断是否为空
if(person == null) {
Thread.sleep(1000);
person = new Person2();
}
return person;
}
}
public class MainClass {
static Runnable runnable = new Runnable() {
public void run() {
Person2 per = null;
try {
per = Person2.getPerson();
} catch (Exception e) {
e.printStackTrace();
}
per.setName("zhangsan");
System.out.println(per.getName());
}
};
static Runnable runnable1 = new Runnable() {
public void run() {
Person2 per = null;
try {
per = Person2.getPerson();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
per.setName("lisi");
System.out.println(per.getName());
}
};
public static void main(String[] args) throws Exception {
/*Person2 per = Person2.getPerson();
Person2 per2 = Person2.getPerson();
per.setName("zhangsan");
per2.setName("lisi");
System.out.println(per.getName());
System.out.println(per2.getName()); */
Thread thread1 =new Thread(runnable);
Thread thread2 =new Thread(runnable1);
thread1.start();
thread2.start();
}
}
第一次执行结果:
第二次执行
解决方案:当然是用同步
public class Person3 {
private String name;
private static Person3 person;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//构造函数私有化
private Person3() {
}
//提供一个全局的静态方法,使用同步方法
public static synchronized Person3 getPerson() throws Exception {
if(person == null) {
Thread.sleep(1000);
person = new Person3();
}
return person;
}
}
public class MainClass {
static Runnable runnable = new Runnable() {
public void run() {
Person3 per = null;
try {
per = Person3.getPerson();
} catch (Exception e) {
e.printStackTrace();
}
per.setName("zhangsan");
System.out.println(per.getName());
}
};
static Runnable runnable1 = new Runnable() {
public void run() {a
Person3 per = null;
try {
per = Person3.getPerson();
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
per.setName("lisi");
System.out.println(per.getName());
}
};
public static void main(String[] args) throws Exception {
/*Person2 per = Person2.getPerson();
Person2 per2 = Person2.getPerson();
per.setName("zhangsan");
per2.setName("lisi");
System.out.println(per.getName());
System.out.println(per2.getName()); */
Thread thread1 =new Thread(runnable);
Thread thread2 =new Thread(runnable1);
thread1.start();
thread2.start();
}
}
运行结果:
即要么全是zhangsan ,要么全是lisi。
还有一种方式 叫做双重检查
public class Person4 {
private String name;
private static Person4 person;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
//构造函数私有化
private Person4() {
}
//提供一个全局的静态方法
public static Person4 getPerson() {
if(person == null) {
synchronized (Person4.class) {
//这个就是第二次检查
//(因为此时这个方法不是同步的,第一、二两个线程可能相差零点几毫秒依次进入方法,此时,若不做第二次检查,这两个线程还是各自new 自己的)
if(person == null) {
person = new Person4();
}
}
}
return person;
}
}