一、单例模式定义:
单例模式确保某个类只有一个实例,而且自行实例化并向整个系统提供这个实例。在计算机系统中,线程池、缓存、日志对象、对话框、打印机、显卡的驱动程序对象常被设计成单例。这些应用都或多或少具有资源管理器的功能。每台计算机可以有若干个打印机,但只能有一个Printer Spooler,以避免两个打印作业同时输出到打印机中。每台计算机可以有若干通信端口,系统应当集中管理这些通信端口,以避免一个通信端口
二、单例模式特点:
1、单例类只能有一个实例。
2、单例类必须自己创建自己的唯一实例。
3、单例类必须给所有其他对象提供这一实例。
单例模式保证了全局对象的唯一性,比如系统启动读取配置文件就需要单例保证配置的一致性。同时被两个请求同时调用。总之,选择单例模式就是为了避免不一致状态,避免政出多头。
三,单例模式
Spring中默认配置的bean的scope为singleton,也就是单例作用域以及最早使用的MVC框架Struts1中的action就是单例模式的。单例模式应用的框架。
四, 单例模式的六种实现方式
1.饿汉式单例(立即加载方式)
/**
* 单例模式
* 饿汉式
* 直接创建实例对象 不管你是否需要该实例对象
* 不存在线程安全的问题
*
* (1) 构造器私有化 private
* (2) 自行创建,并且用静态变量保存 static
* (3) 向外界提供这个实例 public
* (4)强调这是一个单例,我们可以用final 修改
*/
public class Singleton1 {
public static final Singleton1 INSTANCE = new Singleton1();
private Singleton1(){
}
}
代码中直接实例化Singleton1 对象,也就是在项目启动的时候就为我们创建好了一份Singleton1 对象,这样的优点在于静态对象本身就是单例的,我们在使用的时候可以不考虑线程安全问题。缺点也是显而易见的,在程序初始化时就要为我们创建好这些对象放入到内存中,造成了空间的浪费。
2.饿汉式单例(加载属性文件,初始化静态资源)
public class Singleton2 {
public static final Singleton2 INSTANCE;
private String info;
static{
Properties properties = new Properties();
//调用类加载器加载当前的属性文件上
try {
properties.load(Singleton2.class.getResourceAsStream("/test.properties"));
INSTANCE = new Singleton2(properties.getProperty("info"));
} catch (IOException e) {
throw new RuntimeException(e);
}
}
private Singleton2(String info){
this.info =info;
}
public String getInfo() {
return info;
}
public void setInfo(String info) {
this.info = info;
}
@Override
public String toString() {
return "Singleton2 [info=" + info + "]";
}
}
这种加载方式适用于加载单例对象时初始化数据,比如数据库连接驱动的信息等等,属性properties文件以key value 的形式通过类加载器以及getResourceAsStream获取绝对路径加载。
3.懒汉式单例(非线程安全 舍弃)
/**
* 懒汉式单例模式(非线程安全)
* @author yzx
*
*/
public class Singleton3 {
private static Singleton3 instance;
private Singleton3() {
}
public static Singleton3 getInstacne() throws Exception{
if (instance == null){
Thread.sleep(500);
instance = new Singleton3();
}
return instance;
}
}
public class TestSingleion3 {
public static void main(String[] args) throws Exception {
/* Singleton3 s1 = Singleton3.getInstacne();
Singleton3 s2 = Singleton3.getInstacne();*/
//创建一个线程 使用匿名内部类的方式
Callable<Singleton3> callable = new Callable<Singleton3>() {
@Override
public Singleton3 call() throws Exception {
return Singleton3.getInstacne();
}
};
//创建线程池
ExecutorService service = Executors.newFixedThreadPool(2);
Future<Singleton3> f1 = service.submit(callable);
Future<Singleton3> f2 = service.submit(callable);
Singleton3 s1 = f1.get();
Singleton3 s2 = f2.get();
System.out.println(s1==s2);
System.out.println(s1);
System.out.println(s2);
service.shutdown();
}
}
由于是在getInstacne 获取单例实例,当有多线程进行调用时,一个线程阻塞(休眠)实例对象还是null,第二个线程进入时同样也为null,就会导致获取线程混乱,出现获取的单例对象不是同一个对象。
4.懒汉式单例(线程安全)
package com.atguigu.single;
/**
* 懒汉式单例模式 线程安全 且高效
*
* @author yzx
*
*/
public class Singleton4 {
private static Singleton4 instance;
private Singleton4() {
}
public static Singleton4 getInstacne() throws Exception {
// 提高效率加入判断
if (instance == null) {
// 避免出现线程安全问题 加入锁
synchronized (Singleton4.class) {
if (instance == null) {
Thread.sleep(1000);
instance = new Singleton4();
}
}
}
return instance;
}
}
这种方式 主要是通过synchronized 关键字加锁,来防止线程安全问题,可是这样又会出现新的问题,造成效率偏低,因此,在进入获取线程的synchronized代码块之前,先判断当前单例对象是否为null 可以保证高效。
5.内部类单例
/**
* 在内部类被加载和初始化时 才创建INSTANCE对象
* 静态内部类不会随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的
* 因为是在内部类加载和初始化时创建的 因此线程是安全的
* @author yzx
*
*/
public class Singleton5 {
private Singleton5 (){
}
//创建内部类加载单例对象
private static class Inner{
private static Singleton5 INSTANCE = new Singleton5();
}
public static Singleton5 getInstance(){
return Inner.INSTANCE;
}
}
在内部类被加载和初始化时 才创建INSTANCE对象 静态内部类不会随着外部类的加载和初始化而初始化,它是要单独去加载和初始化的, 因为是在内部类加载和初始化时创建的 因此线程是安全的。
6.枚举类单例
/**
* 枚举类型:标示该类型对象时有限的几个
* 我们可以限定为一个,就成了单例了
*
*
*/
public enum Singleton {
INSTANCE
}
枚举类的数量定为1就可以构成最简单的饿汉式单例,饿汉式单例(立即加载)方式相同。不过枚举类打印的是类的名字,而饿汉式单例打印的是类的地址。
这里只是例举了几种自己总结的单例模式,有问题的地方还请大家及时指出,免的误导了其他同学。