一.引入
对于系统的某些类来说,只有一个实例很重要。例如:一个系统中可以存在多个打印任务,但是只能有一个正在工作的任务等等;如果不使用机制对窗口对象进行唯一实例化,将会弹出多个窗口,如果这些窗口显式的内容完全一致,则是重复对象,浪费内存资源;如果这些窗口显式的内容不一致,则意味着在某一瞬间系统是有多个状态的,与实际不符,也会给用户带来误解,不知道哪一个才是真实的状态。所以有时确保系统中某个对象的唯一性即一个类只能有一个实例非常重要。
例如(定义一个类,其创建的不同对象实质上是不一样的):
// 定义一个Cat类
public class Cat {
private String name;
private int age;
public Cat() {
super();
// TODO Auto-generated constructor stub
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
}
// 测试Cat
public class TestCat {
public static void main(String[] args) {
/*
* 创建对象:
* 一个类可以创建出多个对象
*/
Cat cat1 = new Cat();
cat1.setName("咪咪");
cat1.setAge(3);
System.out.println(cat1);
Cat cat2 = new Cat();
cat2.setName("喵喵");
cat2.setAge(5);
System.out.println(cat2);
Cat cat3 = cat1;
System.out.println(cat3);
// cat1和cat3是一样的,但是和cat2不一样
}
}
在上述例子中,一个Cat类可以创建出多个实例对象。 那如何保证一个类只有一个实例并且这个实例易于被访问呢?定义一个全局变量可以确保对象随时都可以被访问,但不能防止我们实例化多个对象。更好的办法就是让类自身负责保存他的唯一实例。这个类可以保证没有其他实例被创建,并且它可以提供一个访问该实例的方法。
二.单例模式
(一).要点:
1.某个类只能有一个实例(对象);
2.必须自行创建这个实例;
3.必须自行向整个系统提供这个实例。
(二).目的
保证该类只有一个对象。通俗地讲:就是创建的所有对象都访问唯一实例。
(三).方法步骤
以上述的Cat类举例:
1.把构造方法私有化;(防止调用构造方法创建不同的对象)
public class Cat {
// 构造函数私有化
private Cat() {
super();
}
}
public class TestCat {
public static void main(String[] args) {
// Cat cat1 = new Cat();
// 构造方法私有化之后,创建对象编译错误:
// The constructor Cat() is not visible
}
}
2.在成员位置自己创建一个对象(成员变量);
3.提供一个公共的方法进行访问。
(在此第2、3步骤合为一个步骤)!!!
public class Cat {
public static Cat cat = new Cat();
/*
* static在此函数的作用:
* 由于getCat()方法返回的是cat变量,并且使用static修饰,
* 就会在类加载的时进入到内存中,然而此时的cat并没有加载到内存中,
* 因此cat也需要使用static进行修饰
*/
/*
*对外提供一个方法,让外部可以获取到Cat,因此返回值是Cat类型
*/
public static Cat getCat() {
/*
* static在此函数的作用:
* 已经不能通过new方法获取构造函数进行创建对象,
* 只能对外提供通过类名.方法名调用此方法得到Cat。
*/
return cat;
}
// 构造函数私有化
private Cat() {
super();
}
}
public class TestCat {
public static void main(String[] args) {
// Cat cat1 = new Cat();
// 构造方法私有化之后,创建对象编译错误:
// The constructor Cat() is not visible
// 只能通过类名.方法名获取到Cat
Cat cat1 = Cat.getCat();
System.out.println(cat1);//com.etime1.Cat@70dea4e
Cat cat2 = Cat.getCat();
System.out.println(cat2);//com.etime1.Cat@70dea4e
/*
* cat1和cat2的内存地址一致
* 这里我们已经得到了对象的唯一实例
*/
}
}
通过这个步骤我们已经创建了对象的唯一实例,但是成员变量是被public修饰的,就会存在被外界修改的风险,如(Cat类在此并未做任何修改):
public class TestCat {
public static void main(String[] args) {
// 未被修改
Cat cat1 = Cat.getCat();
System.out.println(cat1);//com.etime1.Cat@70dea4e
// 可以通过类名.变量名修改
Cat.cat = null;
// 已经被修改了
Cat cat2 = Cat.getCat();
System.out.println(cat2);//null
}
}
通过上述示例我们可以看出,唯一实例在此时是可以被修改的,因此就要在Cat的成员变量中加上final修饰防止被修改。
public class Cat {
// 加上final修饰
public static final Cat cat = new Cat();
public static Cat getCat() {
return cat;
}
private Cat() {
super();
}
}
public class TestCat2 {
public static void main(String[] args) {
// 未被修改
Cat cat1 = Cat.getCat();
System.out.println(cat1);//com.etime1.Cat@70dea4e
// 此时已经不可以修改,否则编译报错:
// Cat.cat = null;
Cat cat2 = Cat.getCat();
System.out.println(cat2);//com.etime2.Cat@70dea4e
}
}
在此时虽然Cat已经不可以被修改,但是外部任然可以得到Cat的成员变量,因此我们将成员变量私有化,只提供一个方法供外界访问唯一实例。
public class Cat {
// 将public改为private,外部不可访问
private static final Cat cat = new Cat();
public static Cat getCat() {
return cat;
}
private Cat() {
super();
}
}
(四).优点
1.实例控制:单例模式会阻止其他对象实例化其自己的单例对象的副本,从而确保所有对象都访问唯一实例。
2.灵活性:因为类控制了实例化过程,所以类可以灵活更改实例化过程。
三.懒汉式和饿汉式
Singleton模式主要作用是保证在Java应用程序中,一个class类只有一个实例存在。一般Singleton模式通常有两种形式。
(一).饿汉式
饿汉式一开始就会创建实例。和上述示例中的Cat类相同。
// 饿汉式
public class Singleton {
// 在自己内部定义自己的一个实例,只供内部调用
private static final Singleton singleton = new Singleton();
// 提供一个供外部访问本class的静态方法,可以直接访问
public static Singleton getSingleton() {
return singleton;
}
private Singleton() {
super();
}
}
(二).懒汉式
我们将依据上述的懒汉式对懒汉式进行进一步的完善。
懒汉式,也是常用的形式。当别人访问时再创建。
// 懒汉式
public class Singleton {
/*
* 由于静态方法修饰的成员变量会随着类的加载而存在,在加载类的时候不管有没有用到这个实例,
* 变量都会被加载,这样就极大的浪费了内存资源;因此我们将变量置为空,当需要使用的时候在去创建实例对象。
*/
private static Singleton singleton = null;
public static synchronized Singleton getSingleton() {
if(singleton==null) {
singleton=new Singleton();
// 这里需要更改singleton,因此变量不可以使用final修饰
}
return singleton;
}
private Singleton() {
super();
}
}
(三).懒汉式和饿汉式的区别
1.饿汉式是线程安全的,在类创建的同时就已经创建好一个静态的对象供系统使用,以后不在改变。懒汉式如果在创建实例对象时不加上synchronized则会导致对对象的访问不是线程安全的。
2.从实现方式来说他们最大的区别就是懒汉式是延时加载,他是在需要的时候才创建对象,而饿汉式在JVM启动的时候就会创建,饿汉式无需关注多线程问题、写法简单明了、能用则用。但是他是加载类时创建实例,就会缓存很多实例,大大的降低了效率,因为这个类一加载不管实例使用与否都会创建。
3.简单的来说就是一个是饿的不行了,上来就吃,撑死也无所谓(饿汉式)。一个是我不饿,等到饿的时候再吃(懒汉式)。