java设计模式-单例模式

1.饿汉模式

  • 私有化空参构造方法。
  • 初始化静态对象。
  • 饿汉式单例,线程是安全的。
package com.second.app.thread.singleton;

/**
 * @author gyz
 * @date 2021/2/5 9:57
 */
public class HungryInstance {
     

    private static final HungryInstance hungryInstance = new HungryInstance();

    private HungryInstance() {
     

    }

    public static HungryInstance getInstance() {
     

        return hungryInstance;
    }
}

多线程测试一下:

package com.second.app.thread.singleton;

/**
 * @author gyz
 * @date 2021/2/5 10:10
 */
public class Main {
     
    public static void main(String[] args) {
     
        new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                System.out.println(HungryInstance.getInstance());
            }
        }, "线程A").start();
        new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                System.out.println(HungryInstance.getInstance());
            }
        }, "线程B").start();
        new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                System.out.println(HungryInstance.getInstance());
            }
        }, "线程C").start();
    }
}

输出结果:可见多个线程获取的是同一个实例对象。
java设计模式-单例模式_第1张图片

2.懒汉模式

懒汉模式不是立即加载,而是采用延时加载的方法,直接上代码。

package com.second.app.thread.singleton;

/**多线程场景下--不安全
 - @author gyz
 - @date 2021/2/5 10:18
 */
public class LazyInstance {
     

    private static LazyInstance lazyInstance = null;

    private LazyInstance() {
     

    }

    public static LazyInstance getLazyInstance() {
     
        if (lazyInstance == null) {
     
            try {
     
                Thread.sleep(300);
                lazyInstance = new LazyInstance();
            } catch (InterruptedException exception) {
     
                exception.printStackTrace();
            }
        }
        return lazyInstance;
    }
}

多线程测试一下:由结果可见,三个线程尝试获取这个单例,但是创建了三个对象,很明显是不对的。
java设计模式-单例模式_第2张图片
为什么会造成这种情况?

  • 因为多个线程执行获取单例的时候,因为线程执行这段代码的先后顺序是不一定的。假设A线程率先抢占了cpu资源,此时B,C线程并不会等A线程执行完,所以就会出现前一个线程刚new完一个对象,后面一个线程又new了一个对象的情况。因为可能同时有两个线程进入if空值判断。

所以怎么保证线程饿汉模式下线程的安全?

  • 很多人想到的第一点就是synchronized关键字,将获取单例的方法,变成同步方法,直接上代码。
package com.second.app.thread.singleton;
/**
 - @Author soul yzg
 - @Date 2021/2/6 17:42
 - 努力学习 天天进步
 */
public class HungryInstance {
     

    private static HungryInstance instance = null;

    private HungryInstance() {
     
    }

    public static synchronized HungryInstance getInstance() {
     
        try {
     
            if (instance == null) {
     
                Thread.sleep(600);
                instance = new HungryInstance();
            }
        } catch (InterruptedException e) {
     
            e.printStackTrace();
        }
        return instance;
    }
}
  • 看起来没什么问题,线程同步了,率先执行的线程尝试new一个对象,释放同步锁,后续线程拿到同步锁,进行非空判断。此时对象已经存在。所以满足全局唯一实例。

  • 缺点就是:直接在方法上加入synchronized关键字,效率不是很高,企业开发也不建议。
    java设计模式-单例模式_第3张图片

  • 这边尝试用同步代码块来解决试试:

package com.second.app.thread.singleton;

/**
 * @Author soul yzg
 * @Date 2021/2/6 17:42
 * 努力学习 天天进步
 */
public class HungryInstance {
     

    private static HungryInstance instance = null;

    private HungryInstance() {
     

    }
    public static HungryInstance getInstance() {
     

        synchronized (HungryInstance.class) {
     
            if (instance == null) {
     
                instance = new HungryInstance();
            }
        }
        return instance;
    }

}

java设计模式-单例模式_第4张图片

  • 同步代码块是比同步方法来的实在,如果真正的业务代码,可不是这两行代码,如果直接锁整个方法,并非效率很低,如果采用同步代码块,只锁部分代码,这样我认为效率会高点。

当然肯定还有更好的做法,接着往下写。

package com.second.app.thread.singleton;

/**
 * @Author soul yzg
 * @Date 2021/2/6 17:42
 * 努力学习 天天进步
 */
public class HungryInstance {
     

    private static volatile HungryInstance instance ;

    private HungryInstance() {
     

    }
      /**
     * 双重校验
     * @return
     */
    public static HungryInstance getInstance() {
     
    //第一个校验,会先判断当前是堆内存中否存在当前对象,如果不存在,才会进入判断内,
    //此时并发条件下,可能会存在多个线程进入此方法体,所以这边加入一个同步方法块,只允许同一时刻只有    一个线程进入同步代码块,
    //进入方法块中的时候,会在进行第二层判断,首先如果没有第二层判断会怎样?
    //假设没有第二层判断,如果第一个线程创建实例后释放锁,下一个线程占有锁,会再一次创建一个实例对象。因为你不能保证同一个时刻只有一个线程进入
    //if(instance == null)判断,这时候如果有多个线程,那么就会存在这样的情况。
    //所以这边为什么会加上第二层判断,
        if (instance == null) {
     
            synchronized (HungryInstance.class) {
     
                if (instance == null) {
     
                    instance = new HungryInstance();
                }
            }
        }
        return instance;
    }
}

至于为什么加volatile关键字,请参考我的另外一篇博客。
volatile关键字浅谈

  • 至于单例还有其他的一些写法,包括使用静态代码块,静态内置类。这里我就不写了,我下面要展示一种用枚举来实现单例的写法(面试被问到过)
package com.second.app.thread.singleton;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * @Author soul yzg
 * @Date 2021/2/8 8:12
 * 努力学习 天天进步
 */
public enum MyObject {
     
    /**
     *
     */
    connectionFactory;
    private Connection connection;

    private MyObject() {
     
        try {
     
            System.out.println("调用MyObject的构造");
            String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT";
            String username = "root";
            String password = "123456";
            String driverName = "com.mysql.cj.jdbc.Driver";
            Class.forName(driverName);
            connection = DriverManager.getConnection(url, username, password);
            System.out.println("连接创建成功");
        } catch (ClassNotFoundException e) {
     
            e.printStackTrace();
        } catch (SQLException e) {
     
            e.printStackTrace();
        }

    }

    public Connection getConnection() {
     
        return connection;
    }
}

package com.second.app.thread.singleton;

/**
 * @Author soul yzg
 * @Date 2021/2/8 8:19
 * 努力学习 天天进步
 */
public class Run {
     
    public static void main(String[] args) {
     
        new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                System.out.println(MyObject.connectionFactory.getConnection().hashCode());
            }
        }, "线程a").start();

        new Thread(new Runnable() {
     
            @Override
            public void run() {
     
                System.out.println(MyObject.connectionFactory.getConnection().hashCode());
            }
        }, "线程b").start();
    }
}

输出结果:
在这里插入图片描述

  • 这种写法还是有缺陷的,在优化一下,如下。
package com.second.app.thread.singleton;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;

/**
 * @Author soul yzg
 * @Date 2021/2/8 8:35
 * 努力学习 天天进步
 */
public class MyObject1 {
     

    public enum MyEnumSingleton {
     
        /**
         *
         */
        connectionFactory;

        private Connection connection;

        private MyEnumSingleton() {
     
            try {
     
                System.out.println("调用MyObject的构造");
                String url = "jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf-8&serverTimezone=GMT";
                String username = "root";
                String password = "123456";
                String driverName = "com.mysql.cj.jdbc.Driver";
                Class.forName(driverName);
                connection = DriverManager.getConnection(url, username, password);
                System.out.println("连接创建成功");
            } catch (ClassNotFoundException e) {
     
                e.printStackTrace();
            } catch (SQLException e) {
     
                e.printStackTrace();
            }

        }

        public Connection getConnection() {
     
            return connection;
        }
    }

    public static Connection getConnection() {
     
        return MyEnumSingleton.connectionFactory.getConnection();
    }
}

你可能感兴趣的:(单例,java)