相关知识:
通常设计模式有23种,主要分为三大类:
- 创建型模式(5种):单例模式、工厂方法模式、抽象工厂模式、建造者模式、原型模式。
- 结构型模式(7种):适配器模式、装饰模式、代理模式、外观模式、桥接模式、组合模式、享元模式。
- 行为型模式(11种):模板方法模式、中介者模式、策略模式、观察者模式、迭代器模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、解释器模式。
前言:
单例模式:指一个类只有一个实例,且该类能自行创建这个实例的一种模式。为了节省内存资源、保证数据内容的一致性,对某些类要求只能创建一个实例,这就是所谓的单例模式。
特点:
- 单例类只有一个实例对象
- 该单例对象必须由单例类自行创建
- 单例类对外提供一个访问该单例的全局访问点
注意:
普通类的构造函数是公有的,外部类可以通过“new 构造函数()”来生成多个实例。但是,如果将类的构造函数设为私有的,外部类就无法调用该构造函数,也就无法生成多个实例。这时该类自身必须定义一个静态私有实例,并向外提供一个静态的公有函数用于创建或获取该静态私有实例。所以单例模式的构造函数应是私有的。
实现:
项目结构:
普通类Apple:
//项目结构图中普通类
package com.dp.singleton;
public class Apple {
}
饿汉式单例实现类HungrySingleton:
//项目结构图中饿汉式单例实现类
package com.dp.singleton;
public class HungrySingleton {
private static HungrySingleton hungrySingleton = new HungrySingleton();
private HungrySingleton(){
}
public static HungrySingleton getInstace(){
return hungrySingleton;
}
public void print(){
System.out.println("我是饿汉式单例模式...");
}
}
懒汉式单例实现类LazySingleton:
注意:如果编写的是多线程程序,则不要删除代码中的关键字 volatile 和 synchronized,否则将存在线程非安全的问题。如果不删除这两个关键字就能保证线程安全,但是每次访问时都要同步,会影响性能,且消耗更多的资源,这是懒汉式单例的缺点。
//项目结构图中懒汉式单例实现类
package com.dp.singleton;
public class LazySingleton {
//保证 instance 在所有线程中同步
private static volatile LazySingleton lazySingleton= null;
//private 避免类在外部被实例化
private LazySingleton(){}
//getInstance 方法前加同步
public static synchronized LazySingleton getInstance(){
if(null == lazySingleton){
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
public void print(){
System.out.println("我是懒汉式单例模式...");
}
}
测试类TestMain:测试结果看代码中注释打印部分
//项目结构图中测试类
package com.dp.singleton;
public class TestMain {
public static void main(String[] args) {
//一般创建对象测试
Apple app1 = new Apple();
Apple app2 = new Apple();
System.out.println(app1 == app2); //false
//饿汉式创建对象
HungrySingleton hungrySingleton1 = HungrySingleton.getInstace();
HungrySingleton hungrySingleton2 = HungrySingleton.getInstace();
hungrySingleton1.print(); //我是饿汉式设计模式...
hungrySingleton2.print(); //我是饿汉式设计模式...
System.out.println(hungrySingleton1 == hungrySingleton2); //true
//懒汉式创建对象
LazySingleton lazySingleton1 = LazySingleton.getInstance();
LazySingleton lazySingleton2 = LazySingleton.getInstance();
lazySingleton1.print(); //我是懒汉式单例...
lazySingleton2.print(); //我是懒汉式单例...
System.out.println(lazySingleton1 ==lazySingleton2); //true
}
}
总结:
实例化方面:懒汉式默认不会实例化,外部什么时候调用什么时候new。饿汉式在类加载的时候就实例化,并且创建单例对象。
线程安全方面:饿汉式线程安全 (在线程还没出现之前就已经实例化了,因此饿汉式线程一定是安全的)。懒汉式线程不安全( 因为懒汉式加载是在使用时才会去new 实例的,那么你去new的时候是一个动态的过程,是放到方法中实现的,比如:public static synchronized Lazy getInstance(){ if(lazy==null){ lazy=new Lazy(); } 如果这个时候有多个线程访问这个实例,这个时候实例还不存在,还在new,就会进入到方法中,有多少线程就会new出多少个实例。一个方法只能return一个实例,那最终return出哪个呢?是不是会覆盖很多new的实例?这种情况当然也可以解决,那就是加同步锁,避免这种情况发生) 。
执行效率上:饿汉式没有加任何的锁,因此执行效率比较高。懒汉式一般使用都会加同步锁,效率比饿汉式差。
性能上:饿汉式在类加载的时候就初始化,不管你是否使用,它都实例化了,所以会占据空间,浪费内存。懒汉式什么时候需要什么时候实例化,相对来说不浪费内存。