目录
1.单例模式概述
单例模式是结构最简单的设计模式,在它的核心结构中只包含一个被称为单例类的特殊类。通过单例模式可以确保系统中的一个类只有一个实例而该实例易于被外界访问,从而方便对实例个数进行控制,节约系统资源。
对于一个系统只有一个实例其实很重要,例如windows系统只有一个任务管理器,一个系统只能有一个计时工具Id生成器等。如何保证一个类只有一个实例并且这个实例很容易被访问呢?定义一个统一的全局变量可以确保对象随时都可以被访问,但不能防止创建多个对象。一个更好的解决办法是让类自身负责创建和保存它的唯一实例,并保证不能创建其他实例,它还提供一个访问该实例的方法,这就是单例模式的动机。
单例模式是一种对象创建模式。单例模式有3个要点
一个是类只能有一个实例
二是它必须自行创建这个实例
三是它必须自行向整个系统提供这个实例。
单例模式结构图
2.单例模式的实现
单例模式的目的是保证只有一个类且仅有一个实例,并提供一个访问他的全局访问点。单例模式包含角色只有一个额,也就是单例类的Singleton。单例类拥有一个私有的构造函数,确保用户无法通过new关键字直接实例化,除此之外单例类还有一个静态的共有方法用来获取实例,该工厂方法先判断实例的存在性再创建实例化自己,以确保只有一个实例被创建
package singleton;
/**
* @Auther: Frank
* @Date: 2018/9/5 19:51
* @Description: 单例模式
*/
public class Singleton {
//定义静态私有的成员变量
private static Singleton singleton = null;
//定义私有的构造函数
private Singleton() {
}
//定义静态共有工厂方法,返回唯一实例
public static Singleton getInstance() {
if (singleton == null)
singleton = new Singleton();
return singleton;
}
}
测试类
package singleton.test;
import singleton.Singleton;
/**
* @Auther: Frank
* @Date: 2018/9/5 19:57
* @Description:
*/
public class SingletonTest {
public static void main(String[] args) {
Singleton singleton1=Singleton.getInstance();
Singleton singleton2=Singleton.getInstance();
if(singleton1==singleton2){
System.out.println("两个对象 是相同实例");
}else{
System.out.println("两个对象 是不同实例");
}
}
}
可以发现有两个实例化动作,但是创建的实例是同一个实例,保证了唯一性。
2.1单例模式--之饿汉模式
饿汉模式顾名思义就是不顾一切的创建实例,就是在类加载完成之后就自动创建了实例,
package singleton;
/**
* @Auther: Frank
* @Date: 2018/9/6 20:39
* @Description: 饿汉模式的单例实现 类被加载时 instance就会被创建 会浪费资源
*/
public class SingletonHungry {
private static SingletonHungry instance = new SingletonHungry();
private SingletonHungry() {
}
public static SingletonHungry getInstance() {
return instance;
}
}
package singleton.test;
import singleton.SingletonHungry;
/**
* @Auther: Frank
* @Date: 2018/9/6 20:54
* @Description:
*/
public class SingletonHungryTest {
public static void main(String[] args) {
SingletonHungry singletonHungry = SingletonHungry.getInstance();
SingletonHungry singletonHungry2 = SingletonHungry.getInstance();
System.out.println(singletonHungry == singletonHungry2);
}
}
2.2单例模式--懒汉模式
懒汉模式单例类在第一次使用时创建,无需一直占用系统资源也就是所谓的延迟加载,但是必须多线程安全的问题,需要通过双重检查锁机制来进行控制,这导致系统的性能受到一定影响。
懒汉式单例类与双重检查锁定
package singleton;
/**
* @Auther: Frank
* @Date: 2018/9/6 20:42
* @Description:懒汉单例 ===延迟加载 调用时才加载 instance 双重锁鉴定 但是存在性能问题
*/
public class SingletonLazy {
private static SingletonLazy instance = null;
private SingletonLazy() {
}
public static SingletonLazy getInstance() {
if (instance == null) {
synchronized (SingletonLazy.class) {
if (instance == null) {
instance = new SingletonLazy();
}
}
}
return instance;
}
}
测试类
package singleton.test;
import singleton.SingletonLazy;
/**
* @Auther: Frank
* @Date: 2018/9/6 20:56
* @Description:
*/
public class SingletonLazyTest {
public static void main(String[] args) {
SingletonLazy singletonLazy = SingletonLazy.getInstance();
SingletonLazy singletonLazy2 = SingletonLazy.getInstance();
System.out.println(singletonLazy == singletonLazy2);
}
}
2.3使用静态内部类实现单例模式
饿汉模式不能实现延迟加载,不管将来用不用始终占据了内存;懒汉模式单例类操作线程安全繁琐,而且性能受影响。可见都存在一些缺陷。为了克服这种问题,在java语言中可以通过静态内部类实现单例模式。
在单例类中增加一个静态内部类,在该类内部中创建单例对象。再将该对象通过给getInstance方法返回给外部类使用,代码如下
package singleton;
/**
* @Auther: Frank
* @Date: 2018/9/6 20:47
* @Description:使用静态内部类实现单例模式 ----即解决了饿汉的资源浪费问题 又解决了懒汉的性能问题
*/
public class SingletonStatic {
private SingletonStatic() {
}
//静态内部类
private static class HolderClass {
private static SingletonStatic instance = new SingletonStatic();
}
public static SingletonStatic getInstance() {
return HolderClass.instance;
}
}
测试类
package singleton.test;
import singleton.SingletonStatic;
/**
* @Auther: Frank
* @Date: 2018/9/6 20:55
* @Description:
*/
public class SingletonStaticTest {
public static void main(String[] args) {
SingletonStatic singletonStatic=SingletonStatic.getInstance();
SingletonStatic singletonStatic2=SingletonStatic.getInstance();
System.out.println(singletonStatic==singletonStatic2);
}
}
通过使用静态内部类的方式实现单例,既解决了饿汉模式的资源浪费问题,和懒汉模式的性能问题,不失为java语言最好的一种单例实现方式;缺点是与编程语言本身的特性有关,很多别的面向对象语言并不支持。
3.单例模式应用实例
模拟windows任务管理器
只是一个实例,当然windows任务管理器内部机制复杂的多。
需求写一个模拟写一个windows任务管理器 对存活的进程的数据进行查看
* 由于新增进程 关闭进程 同时进行 需要保证状态唯一性
* 所以只能启用一个任务管理器进行操作
package singleton;
import java.util.ArrayList;
import java.util.List;
/**
* @Auther: Frank
* @Date: 2018/9/6 20:08
* @Description: 需求写一个模拟写一个windows任务管理器 对存活的进程的数据进行查看
* 由于新增进程 关闭进程 同时进行 需要保证状态唯一性
* 所以只能启用一个任务管理器进行操作
*/
public class SingletonTaskManager {
private static SingletonTaskManager instance = null;
private List<Integer> process = null;
private SingletonTaskManager() {
process = new ArrayList();
}
public static SingletonTaskManager getInstance() {
if (instance == null)
instance = new SingletonTaskManager();
return instance;
}
//新增线程
public void addProcess(int port) {
process.add(port);
}
//杀掉进程
public void deleteProcess(int port) {
for (int i = 0; i < process.size(); i++) {
if (port == process.get(i))
process.remove(i);
}
}
//获取所有进程
public List<Integer> getAllServer() {
return process;
}
}
package singleton.test;
import singleton.SingletonTaskManager;
/**
* @Auther: Frank
* @Date: 2018/9/6 20:19
* @Description:
*/
public class SingletonTaskManagerTest {
public static void main(String[] args) {
SingletonTaskManager singletonTaskManagerA = SingletonTaskManager.getInstance();
SingletonTaskManager singletonTaskManagerB = SingletonTaskManager.getInstance();
SingletonTaskManager singletonTaskManagerC = SingletonTaskManager.getInstance();
SingletonTaskManager singletonTaskManagerD = SingletonTaskManager.getInstance();
if (singletonTaskManagerA == singletonTaskManagerB && singletonTaskManagerB == singletonTaskManagerC && singletonTaskManagerC == singletonTaskManagerD) {
System.out.println("任务管理器具有一致性");
}
singletonTaskManagerA.addProcess(8080);
singletonTaskManagerA.addProcess(8081);
singletonTaskManagerA.addProcess(8082);
singletonTaskManagerA.addProcess(8083);
singletonTaskManagerB.addProcess(8084);
singletonTaskManagerC.deleteProcess(8083);
//无论创建多少个任务管理器 都只有一个实例 怎么增删减 结果都具有一致性
System.out.println(singletonTaskManagerA.getAllServer());
System.out.println(singletonTaskManagerB.getAllServer());
}
}
可以看到,无论创建多少个任务管理器 都只有一个实例 怎么增删减 结果都具有一致性。
4.单例模式适用环境
1.系统只需要一个实例对象,例如系统要求提供一个唯一的序列号生成器或资源管理器,或者因为系统资源消耗太大只允许创建一个对象。
2.客户调用类的单个实例只允许使用一个公共访问点,出来该公共访问点,不能通过其他途径访问该实例。