单例模式
1. 定义
单例模式(Singleton Pattern)保证一个类仅有一个范例,并提供一个访问它的全局访问点。
单例模式是最简单、最常用的设计模式之一,属于创建型模式。
单例模式只涉及到一个类,该类负责创建自己的对象,而且确保只有单个对象被创建。另外,单例类提供了一种访问其唯一对象的方式,客户端可以直接使用,无需范例化该类的对象。
三个特点
- 1、单例类只能有一个范例。
- 2、单例类必须自行创建自己的范例。
- 3、单例类必须向系统的其它对象提供这一范例。
2. 单例模式的要点
1)目的
单例模式中的单例类负责创建自己的对象,而且确保只有单个对象被创建,并提供全局访问其对象的方法。
单例模式可以达到三个目的:
- 1、控制资源的使用,通过线程同步控制资源的并发访问。
- 2、控制范例产生的数量,达到节省资源的目的,同时提高运行效率。
- 3、单例对象可以作为通信媒介,实现数据共享的,在多个线程或进程之间进行通信。
2)优点
- 1、单例模式中的单例类只存在一个对象,可以节约系统资源。
- 2、单例模式在需要频繁创建和销毁的对象的场景中,可以提高系统的性能。
- 3、单例模式可以避免对共享资源的多重占用。
3)缺点
- 1、单例模式中不适用于变化的对象,不能保存多个不同的状态。
- 2、单例类的职责过重,在一定程度上违背了“单一职责原则”。
- 3、单利模式中没有抽象层,因此单例类的扩展比较困难。
4)使用场景
- 1、资源控制。
- 每台计算机可连接多个打印机,但只能有一个PrinterSpooler,以避免两个打印作业同时输出到打印机。
- 常见的数据库连接池,整个系统通常只需要一个对象,无需范例化多份数据。
- 2、数据同步
- 网站的计数器,一般采用单例模式实现数据同步,否则计数可能会被不断覆盖。
3.实现
1.)饿汉
静态变量式
public class singleton {
public static void main(String[] args) {
//测试是否只创建了一个实例
Sing sing1 = Sing.getSing();
Sing sing2 = Sing.getSing();
System.out.println(sing2==sing1);
// true
}
}
class Sing{
//1.构造器私有,外部无法new
private Sing(){}
//2.创建实例
private final static Sing sing = new Sing();
//3.对外暴露获取实例方法
public static Sing getSing(){
return sing;
}
}
说明:
- 优点,写法简单,在类的装载时就发生实例化,避免了线程同步问题!
- 缺点,类被装载就会被实例化,所以若没有使用过该实例,会造成资源浪费
- 这种静态变量式可用,但可能造成资源浪费
静态代码块式
public class singleton {
public static void main(String[] args) {
//测试是否只创建了一个实例
Sing sing1 = Sing.getSing();
Sing sing2 = Sing.getSing();
System.out.println(sing2==sing1);
// true
}
}
class Sing{
//1.构造器私有,外部无法new
private Sing(){}
//2.在本类中创建实例
private static Sing sing ;
static {
sing = new Sing();
}
//3.对外暴露获取实例方法
public static Sing getSing(){
return sing;
}
}
说明
- 和静态变量式类似,实例化放在了静态代码块中,也在类装载时被实例化,同样可能浪费资源
2.)懒汉()安全的,不安全的
线程不安全式(不推荐)
public class singleton {
public static void main(String[] args) {
//测试是否只创建了一个实例
Sing sing1 = Sing.getSing();
Sing sing2 = Sing.getSing();
System.out.println(sing2==sing1);
System.out.println(sing2.hashCode());
System.out.println(sing1.hashCode());
// true
}
}
class Sing{
//1.构造器私有,外部无法new
private Sing(){}
//2.在本类中创建实例
private static Sing sing ;
//3.对外暴露获取实例方法
public static Sing getSing(){
if(sing==null){
sing = new Sing();
}
return sing;
}
}
说明:
在多线程操作中,会有多个线程进入if语句,存在线程安全问题,不推荐使用该方法
线程安全式(不推荐)
加入同步处理代码可以解决线程安全问题,但是效率很低,同样不推荐使用
public static synchronized Sing getSing(){
if(sing==null){
sing = new Sing();
}
return sing;
}
同步代码块式
class Sing{
//1.构造器私有,外部无法new
private Sing(){}
//2.在本类中创建实例
private static Sing sing ;
//3.对外暴露获取实例方法
public static synchronized Sing getSing(){
if (sing==null){
synchronized (Sing.class){
sing = new Sing();
}
}
return sing;
}
}
说明:同样不推荐,多线程时,会造成多个实例的创建
3.)双重检查
class Sing{
//1.构造器私有,外部无法new
private Sing(){}
//2.在本类中创建实例
private static Sing sing ;
//3.对外暴露获取实例方法
public static synchronized Sing getSing(){
if (sing==null){
synchronized (Sing.class){
if(sing==null){
sing = new Sing();
}
}
}
return sing;
}
}
说明:
- 推荐使用,两次检查,可以保证线程安全,效率较高
3.)静态内部类 (推荐)
class Sing{
//1.构造器私有,外部无法new
private Sing(){}
//2.在本类中创建实例
private static class SingInstance {
private static final Sing SING=new Sing() ;
}
//3.对外暴露获取实例方法
public static synchronized Sing getSing(){
return SingInstance.SING;
}
}
说明:
- 当调用geiSing方法时,导致静态内部类被装载 ,随之创造实例
- 而且在类Sing被装载时,不会创建实例,只有在调用getSing方法才会完成实例化
- 线程安全的,类的初始化jvm保证线程安全
- 推荐使用!!
4.)枚举(推荐)
public class singleton {
public static void main(String[] args) {
//测试是否只创建了一个实例
Sing2 sing1 = Sing2.INSTANCE;
Sing2 sing2 = Sing2.INSTANCE;
System.out.println(sing2==sing1);
}
}
enum Sing2{
INSTANCE;
}
说明:
- 最推荐使用!!!
4.jdk源码中的Runtime
是使用的是单例模式中的饿汉式