【设计模式】创建型-单例模式
文章目录
- 一、单例模式
- 二、单例模式的八种实现方式
- 2.1、饿汉式(静态常量)
- 2.2、饿汉式(静态代码块)
- 2.3、懒汉式(线程不安全)
- 2.4、懒汉式(线程安全,同步方法)
- 2.5、双重检查
- 2.6、静态内部类
- 2.7、枚举
一、单例模式
单例模式(Singleton Pattern):确保某一个类只有一个实例,而且自行实例化并向整个系统提供这个实例,这个类称为单例类,它提供全局访问的方法。单例模式是一种对象创建型模式。
单例模式有三个要点:
- 某个类只能有一个实例
- 它必须自行创建这个实例
- 它必须自行向整个系统提供这个实例。
二、单例模式的八种实现方式
2.1、饿汉式(静态常量)
/*饿汉式(静态常量)*/
public class Singleton1 {
//创建一个私有构造器,不让其他类new
private Singleton1(){}
//创建一个静态常量
public static final Singleton1 INSTANCE = new Singleton1();
//实例方法,方法是静态是为了通过类名调用
public static Singleton1 newInstance(){
return INSTANCE;
}
public static void main(String[] args) {
Singleton1 s1 = Singleton1.newInstance();
Singleton1 s2 = Singleton1.newInstance();
//比较两个实例是否相等 结果:true
System.out.println(s1==s2);
}
}
优缺点:
- 优点:简单,类加载的时候就完成了实例化,避免了线程安全问题。
- 缺点:如果没用到这个实例,也会实例化,浪费了内存。
2.2、饿汉式(静态代码块)
/*饿汉式(静态代码块)*/
public class Singleton2 {
//创建一个私有构造器,不让其他类new
private Singleton2(){}
//定义一个静态实例
public static Singleton2 instance;
//静态代码块中实例化对象
static {
instance= new Singleton2();
}
//提供一个公有静态方法,放回实例化对象
public static Singleton2 newInstance(){
return instance;
}
public static void main(String[] args) {
Singleton2 s1 = Singleton2.newInstance();
Singleton2 s2 = Singleton2.newInstance();
//比较两个实例是否相等 结果:true
System.out.println(s1==s2);
}
}
优缺点跟上面的静态常量一样
2.3、懒汉式(线程不安全)
/*
* 懒汉式
* 实例是在使用的时候创建,但线程不安全,会创建多个对象
* */
public class Singleton3 {
//定义instance静态变量
private static Singleton3 instance;
private Singleton3(){}
//初始化方法,实现懒加载,需要时才创建对象
public static Singleton3 newInstance() throws InterruptedException {
//没有实例,则创建对象
if (instance == null){
//让线程睡一下,创造多线程进入条件
Thread.sleep(20);
instance = new Singleton3();
}
//实例化过,直接返回
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
//创建多线程,实现Runnable接口,重写run方法
new Thread(new Runnable() {
@Override
public void run() {
try {
//通过哈希码,看对象是否一样
System.out.println(Singleton3.newInstance().hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
优缺点:
- 优点:起到了懒加载效果,需要时才创建对象,但只适合在单线程下使用。
- 缺点:在多线程情况下,一个线程 进入了if (instance == null)判断语句块,还未来得及往下执行,另一个线程又进来了,这时就产生了多个实例,造成线程不安全。
2.4、懒汉式(线程安全,同步方法)
/*
* 懒汉式(线程安全,加入同步方法)
* */
public class Singleton4 {
//定义instance静态变量
private static Singleton4 instance;
private Singleton4(){}
//加入同步方法,保证只有一个线程进入
public static synchronized Singleton4 newInstance() throws InterruptedException {
//没有实例,则创建对象
if (instance == null){
//让线程睡一下,创造多线程进入条件
Thread.sleep(20);
instance = new Singleton4();
}
//实例化过,直接返回
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
//创建多线程,实现Runnable接口,重写run方法
new Thread(new Runnable() {
@Override
public void run() {
try {
//通过哈希码,看对象是否一样
System.out.println(Singleton4.newInstance().hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
}
}
}
方式一是一个实例,同时锁住了空判断和创建实例,线程安全。但是这就相当于全部锁住了,就跟同步方法的效果一样,线程安全但效率很低
方式二不是一个实例,线程不安全,原因是一个线程进入了空判断,还没往下执行,另一个线程来了,其中一个线程拿到锁,往下执行创建了实例,执行完释放锁后,另一个线程也往下执行了并创建对象,两者创建的对象并不一致。
2.5、双重检查
public class Singleton6 {
private static Singleton6 instance;
private Singleton6(){};
public static Singleton6 newInstance() throws InterruptedException {
//双重检查,是单例
if (instance == null){
//首先判断实例是否为空,空就上锁
synchronized (Singleton6.class){
//上锁后,如果上面new出了个对象,此时在这判断是否为空,不为空就直接返回了,确保了只有一个实例
if (instance == null){
Thread.sleep(20);
instance = new Singleton6();
}
}
}
return instance;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(() -> {
try {
System.out.println(Singleton6.newInstance().hashCode());
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
}
}
}
双重检查实际上就是在懒汉式(同步代码块)的内部再添加了一个判断,这样就保证线程安全
2.6、静态内部类
public class Singleton7 {
private Singleton7() {}
//静态内部类里实例化对象,在Singleton7加载的时候,SingletonInstance内部类不加载,只在实例的时候加载
private static class SingletonInstance{
//静态属性,实例化对象
private static final Singleton7 INSTANCE = new Singleton7();
}
//提供一个静态的公有方法,返回SingletonInstance类的实例
public static Singleton7 newInstance(){
return SingletonInstance.INSTANCE;
}
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(()->{
System.out.println(Singleton7.newInstance().hashCode());
}).start();
}
}
}
这种方式采用了类加载的机制来保证初始化实例时只有一个线程,线程安全。静态内部类在 Singleton7 类被加载时并不会立即实例化,而是在调用 newInstance方法的时候才会实例化静态内部类,通过SingletonInstance类调用实例,从而完成 Singleton 的实例化。类的静态属性只会在第一次加载类的时候初始化,JVM 帮助我们保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
2.7、枚举
package com.s.singleton;
/**
* 枚举
*/
public enum Singleton8 {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i < 100 ; i++) {
new Thread(()->
System.out.println(Singleton8.INSTANCE.hashCode())).start();
}
}
}
枚举实现是单例的,线程安全,不仅可以解决线程同步,还可以防止反序列化。《Effective Java》作者 Josh Bloch 提倡的方式。