23大设计模式—01单例模式
简介:单例模式就是保证类只有一个实例,并且提供一个可以访问到该单例对象的全局访问点
下面举几个使用到单例模式的例子:
Windows的Task Manager(任务管理器)就是很典型的单例模式
项目中,读取配置文件的类,一般也只有一个对象。没有必要每次使用配置文件数据,每次new一个对象去读取。
数据库连接池的设计一般也是采用单例模式,因为数据库连接是一种数据库资源
在Spring中,每个Bean默认就是单例的,这样做的优点是Spring容器可以管理
在servlet编程中,每个Servlet也是单例
在spring MVC框架/struts1框架中,控制器对象也是单例
上面的例子了解即可,当你用到时自然会理解
单例模式一般有5种常见实现方式
一.五大实现方式
1.饿汉式
public class SingleTon {
//第一步,定义类对象,对象的修饰符为 private static final(当然可以不要final),
//这样写保证了在JVM加载字节码文件进内存初始化时就可以产生对象,没有延时
private static SingleTon singleTon = new SingleTon();
//第二步,定义空参的构造方法,修饰符为private
private SingleTon(){
}
//第三步,定义方法返回单例对象,修饰符为public static
public static SingleTon getInstance(){
return singleTon;
}
}
观看上面的代码发现:除了单例对象没有其它的成员,因此不会存在数据共享,也就没有了线程安全问题,同时由于线程安全,因此没有使用线程同步技术,那么调用的效率也高
总结:线程安全,调用效率高,但是没有延时加载
2.懒汉式
public class SingleTon {
//第一步,定义类对象,对象的修饰符为 private static
private static SingleTon singleTon;
//第二步,定义空参的构造方法,修饰符为private
private SingleTon(){
}
//第三步,定义方法返回单例对象,修饰符为public static synchronized防止线程安全
public static synchronized SingleTon getInstance(){
if(singleTon==null)
{
singleTon = new SingleTon();
}
return singleTon;
}
}
总结:线程安全,调用效率不高(因为有同步)。 但是,可以延时加载,那么资源的利用率高了(因为到要用的时候才去创建,就不会像饿汉式那样一上来就创建单例对象,却有可能这个对象一次没用)。
如果你对多线程不太了解可以看我的这篇文章:多线程知识复习巩固
注意懒汉模式与饿汉模式代码上的区别。
建议如果对象用的频繁就用饿汉式,否则用懒汉式
一般掌握了上面的两种方式就足够了,下面3种是扩展的
3.双重检测锁实现
由于JVM底层内部模型原因,偶尔会出问题。不建议使用,而且工作根本不会用到,了解即可(这个代码我也不会,都是在网上找的,是真的不重要)
public class SingletonDemo3 {
private static SingletonDemo3 instance = null;
public static SingletonDemo3 getInstance() {
if (instance == null) {
SingletonDemo3 sc;
synchronized (SingletonDemo3.class) {
sc = instance;
if (sc == null) {
synchronized (SingletonDemo3.class) {
if(sc == null) {
sc = new SingletonDemo3();
}
}
instance = sc;
}
}
}
return instance;
}
private SingletonDemo3(){
}
}
4.静态内部类实现方式
先复习一个java基础的知识点
1.外部类初次加载,会初始化静态变量、静态代码块、静态方法,但不会加载内部类和静态内部类。
2.实例化外部类,调用外部类的静态方法、静态变量,则外部类必须先进行加载,但只加载一次。
3.直接调用静态内部类时,外部类不会加载。
更多详细内容请学习JVM的高阶知识
public class SingleTon {
//定义静态内部类,里面赋予单例对象
private static class SingletonClassInner{
private static final SingleTon instance = new SingleTon();
}
//通过该函数获取单例对象,只有在调用该方法时才会初始化单例对象,这是一种懒加载机制
public static SingleTon getInstance(){
return SingletonClassInner.instance;
}
private SingleTon(){
}
}
上面的加载内部类机制告诉我们线程是安全的(Java的类加载机制是线程安全的),而final关键字也是保证单例的一个方式
总结:由于没有使用synchronized因此不存在延时加载的问题,那么他是高效的,同时线程安全且可延时加载
5.使用枚举的实现方式
public enum Single {
//枚举元素本身就是一个单例的对象并且不需要担心反射与反序列化带来的问题
INSTANCE;
//可以定义方法来添加操作
public void SingleOperation(){
}
}
总结:线程安全,调用效率高,不能延时加载且不需要担心反射与反序列化带来的问题
二.破解单例模式及解决方案
不过还是有问题,上面前四种方法中才在以下问题:
①反射可以破解上面几种(不包含枚举式)实现方式!
可以在构造方法中手动抛出异常控制
例子:
使用静态内部类的方式来演示
public static void main(String[] args) throws IllegalAccessException, InstantiationException, NoSuchMethodException, InvocationTargetException {
Class<SingleTon> singleClass = SingleTon.class;
Constructor<SingleTon> c = singleClass.getDeclaredConstructor(null);
c.setAccessible(true);
SingleTon single1 = c.newInstance();
SingleTon single2 = c.newInstance();
System.out.println(single1);
System.out.println(single2);
}
输出如下:
SingleTon@2503dbd3
SingleTon@4b67cf4d
解决方案:在构造方法中抛出异常即可
public class SingleTon {
private static class SingleClass{
private static final SingleTon instance = new SingleTon();
}
private SingleTon(){
if(SingleClass.instance!=null){
throw new RuntimeException();
}
}
public static SingleTon getInstance(){
return SingleClass.instance;
}
}
②反序列化可以破解上面几种((不包含枚举式))实现方式!
可以通过定义readResolve()防止获得不同对象。 反序列化时,如果对象所在类定义了readResolve(),(实际是一种回调),定义返回哪个对象
使用静态内部类的方式来演示,注意去实现Serializable
public static void main(String[] args) throws IOException, ClassNotFoundException {
SingleTon s1 = SingleTon.getInstance();
SingleTon s2 = SingleTon.getInstance();
System.out.println(s1==s2);
//通过序列化来破解
FileOutputStream fileOutputStream = new FileOutputStream("D:\\a.txt");
ObjectOutputStream objectOutputStream = new ObjectOutputStream(fileOutputStream);
objectOutputStream.writeObject(s1);
objectOutputStream.close();
FileInputStream fileInputStream = new FileInputStream("D:\\a.txt");
ObjectInputStream objectInputStream = new ObjectInputStream(fileInputStream);
SingleTon s3=(SingleTon) objectInputStream.readObject();
System.out.println(s3==s1);
}
输出:
true
false
解决方案:
添加readResolve方法
import java.io.Serializable;
public class SingleTon implements Serializable {
private static class SingleClass{
private static final SingleTon instance = new SingleTon();
}
private SingleTon(){
if(SingleClass.instance!=null){
throw new RuntimeException();
}
}
public static SingleTon getInstance(){
return SingleClass.instance;
}
//反序列化时,如果定义了readResolve()则直接返回此方法指定的对象。而不需要单独再创建新对象!
public Object readResolve(){
return SingleClass.instance;
}
}
三.效率测试
这里给出一个测试静态内部类例子,其他类似:
import org.junit.Test;
import java.io.*;
import java.util.concurrent.CountDownLatch;
public class AVL {
public static void main(String[] args) throws IOException, ClassNotFoundException, InterruptedException {
long start = System.currentTimeMillis();
CountDownLatch countDownLatch = new CountDownLatch(10);
final int nums = 10;
for(int i=0;i<nums;i++){
new Thread(){
@Override
public void run() {
SingleTon.getInstance();
countDownLatch.countDown();
}
}.start();
}
countDownLatch.await();
System.out.println(System.currentTimeMillis()-start);
}
}
结果:
上一篇:前言