一、概念
1.单例模式是Java设计模式中最简单的设计模式之一。
2.单例模式属于对象创建型模式
3.单例模式的作用是保证一个类仅有一个实力,并提供一个访问他的全局访问点。
理解:某些类创建对象是非常消耗内存消耗时间的,这种类创建的对象我们称它为重量级对象,由于重量级对象的这些特征,所以我们如果要使用重量级对象,那么我们就需要把这个类设计为单例模式,这样做的好处有三点:
3.1保证对象在内存中只有一个,从而减少内存的开销
3.2使用者不需要考虑创建的细节,使用方便
3.3可以控制对象的创建时刻
知识点:因为类在整个jvm中仅有一个对象实例:我们需要知道如下两个关键点;
对象的创建方式有四种(单例模式要做的就是怎么屏蔽外界能够通过下面的方式创建)
.new .反射 .反序列化 .克隆
需要提供一个全局的访问点两种方式
.公开的静态变量(不利于扩展) .一个公开的静态方法,返回唯一的实例
二、java代码实现
先看一下我们最原始的写法:
package singletonclass;
public class SingletoClass {
public void test(){
System.out.println("哈哈");
}
}
package singletonclass;
public class Main {
public static void main(String args[]) {
SingletoClass single1 = new SingletoClass(); //这里我们想创建几个就是几个,所以这里不是单例模式
single.test();
}
}
1.饿汉式
因为是静态static修饰的实例,所以当类加载的时候,实例就进行了加载。
1.1针对上面的代码,我们可能最先想到的是,我们将我们类里面的构造方法进行私有化,这样的话,在Main类里面就不能进行实例化了,但是这样Main类中就不能使用了,所以我们需要在SingletoClass类里面提供一个唯一的实例,我们在类里面使用一个公开的静态变量来进行提供访问:
package singletonclass;
public class SingletoClass {
public static final SingletoClass ONLY = new SingletoClass(); //全局静态变量
private SingletoClass(){
}
public void test(){
System.out.println("哈哈");
}
}
package singletonclass;
public class Main {
public static void main(String args[]) {
SingletoClass single = SingletoClass.ONLY;
single.test();
}
}
1.2上面的代码确实实现了单例模式,但是前面我们提到了,这种方式的扩展性不好,假如我们要对那个对象进行一些控制的话,我们是不能编写代码的。所以我们采用公开的静态方法来实现:
package singletonclass;
public class SingletoClass {
public static final SingletoClass ONLY = new SingletoClass(); //全局静态变量
private SingletoClass(){
}
//提供一个全局的访问点
public static SingletoClass getInstance(){
return ONLY;
}
public void test(){
System.out.println("哈哈");
}
}
package singletonclass;
public class Main {
public static void main(String args[]) {
SingletoClass single1 = SingletoClass.getInstance();
SingletoClass single2 = SingletoClass.getInstance();
System.out.println(single1==single2); //这里输出是true,说明只创建了一个对象
single1.test();
}
}
2.懒汉式
上面的饿汉式存在一定的问题,由于是类加载的时候就对其进行了实例化,这样第一是我们不能控制实例创建的时间,第二是,万一我们没用到这个实例,创建无疑是就浪费了。
package singletonclass;
public class SingletoClass {
public static SingletoClass ONLY ; //保存唯一的实例
private SingletoClass(){
}
//提供一个全局的访问点
public static SingletoClass getInstance(){
if(ONLY==null){
ONLY = new SingletoClass();
}
return ONLY;
}
public void test(){
System.out.println("哈哈");
}
}
package singletonclass;
public class Main {
public static void main(String args[]) {
SingletoClass single1 = SingletoClass.getInstance();
SingletoClass single2 = SingletoClass.getInstance();
System.out.println(single1==single2);
single1.test();
}
}
3.加锁懒汉式
上面的懒汉式严格意义上来说,并不是真正的懒汉式,因为它不是线程安全的,
//在多线程的情况下,线程1发现ONLY==null,进入到了if语句里,就在x线程1还没来得及执行ONLY==new 这句代码的这一瞬间,线程2也发现了ONLY==null也进入到了if语句,这个时候创建就会发生冲突
if(ONLY==null){
ONLY = new SingletoClass();
}
所以我们需要对其进行同步
//提供一个全局的访问点
public static synchronized SingletoClass getInstance(){
if(ONLY==null){
ONLY = new SingletoClass();
}
return ONLY;
}
synchronized SingletoClass getInstance(){
if(ONLY==null){
ONLY = new SingletoClass();
}
return ONLY;
}
4.双重验证式懒汉式
上面的加锁懒汉式看上去似乎很完美,但是不然,因为我们采用了同步,效率降低是一方面,第二方面,假如我们ONLY已经实例化了,以后的每次访问还是要执行同步的这样一个操作。这样明显降低效率了。
我们将同步方法改为同步代码块:
public static SingletoClass getInstance(){
if(ONLY==null){
synchronized(SingletoClass.class){
if(ONLY==null){
ONLY = new SingletoClass();
}
}
}
return ONLY;
}
5.类加载方式
上面的双重验证确实很完美了,延迟加载,线程安全,效率高,但是还是有个问题,那就是代码的复杂度变高了。
使用内部类的方式来实现:延迟加载,线程安全,效率高,代码简单
package singletonclass;
public class SingletoClass {
//屏蔽外部的new
private SingletoClass(){
}
//静态内部类,用于持有唯一的SingletoClass的实例
private static class OnlyInstanceHoder{
static private SingletoClass ONLY = new SingletoClass();
}
//公开一个唯一的访问点
public static SingletoClass getInstance(){
return OnlyInstanceHoder.ONLY;
}
}
6.枚举实现
jdk1.5引入的,具备上面的所有优点。还防止序列化。
package singletonclass;
public enum SingletoClass {
INSTANCE;
public void test(){
System.out.println("single");
}
}
package singletonclass;
public class Main {
public static void main(String args[]) {
singletonclass.SingletoClass.INSTANCE.test();;
}
}