定义:保证一个类仅有一个实例,并提供一个访问它的全局访问点
解决的问题
一个全局使用的类频繁地创建与销毁。当您想控制实例数目,节省系统资源的时候,使用单例
核心要点
- 1、单例类只能有一个实例。(静态变量)
- 2、单例类必须自己创建自己的唯一实例。(私有化构造函数)
- 3、单例类必须给所有其他对象提供这一实例。(全局访问点)
类图
饿汉、懒汉、枚举式、容器式单例
饿汉:类加载时就创建实例,这种被称为饿汉模式
懒汉:第一次被引用是,创建实例,被称为懒汉模式
枚举式单例:使用枚举的方式实现单例
容器式单例:将多个对象放在一个HashMap中,使用时直接从map中获取使用,而不单独创建,优点是可以创建多种单例对象
代码实现
饿汉模式-静态常量
- 线程安全,可能造成内存浪费
- 实现方式简单,类加载是就实例化 了。
/**
* 饿汉模式-静态常量
*
* 避免了线程同步的问题
* 造成内存的浪费
* @author Promsing(张有博)
* @version 1.0.0
* @since 2022/9/2 - 9:36
*/
public class Singleton01 {
public static void main(String[] args) {
Singleton singleton = Singleton.getSingleton();
Singleton singleton1 = Singleton.getSingleton();
System.out.println(singleton.hashCode());
System.out.println(singleton1.hashCode());
}
}
class Singleton{
//私有化构造函数
private Singleton() {
}
//唯一实例
private final static Singleton SINGLETON=new Singleton();
//全局访问点
public static Singleton getSingleton(){
return SINGLETON;
}
}
饿汉模式-静态代码块
- 线程安全,可能造成内存浪费
- 实现方式简单
/**
* 饿汉模式-静态代码块
*
* 避免了线程同步的问题
* 造成内存的浪费
* @author Promsing(张有博)
* @version 1.0.0
* @since 2022/9/2 - 9:36
*/
public class Singleton02 {
public static void main(String[] args) {
}
}
class Singleton{
//私有化构造函数
private Singleton() {
}
static {
SINGLETON=new Singleton();
}
//唯一实例
private static Singleton SINGLETON;
//全局访问点
public static Singleton getSingleton(){
return SINGLETON;
}
}
懒汉-线程不安全
- 线程不安全
- 起到了懒加载的效果
/**
* 懒汉,线程不安全
* 起到了懒加载的效果
* 多线程不能使用
*
* @author Promsing(张有博)
* @version 1.0.0
* @since 2022/9/2 - 9:49
*/
public class Singleton03 {
public static void main(String[] args) {
}
}
class Singleton{
//私有化构造函数
private Singleton() {
}
//唯一实例
private static Singleton SINGLETON;
//全局访问点
//懒汉,判断是否为null,是:new 否:返回实例
public static Singleton getSingleton(){
//多线程进入会出问题。
if (SINGLETON==null){
SINGLETON=new Singleton();
}
return SINGLETON;
}
}
懒汉-线程安全(同步代码块)
- 解决了线程安全问题
- 效率低,不推荐使用
/**
* 懒汉,线程安全-同步方法
* 效率太低,不建议使用
* 使用synchronized加锁
*
* @author Promsing(张有博)
* @version 1.0.0
* @since 2022/9/2 - 9:55
*/
public class Singleton04 {
public static void main(String[] args) {
}
}
class Singleton{
//私有化构造函数
private Singleton() {
}
//唯一实例
private static Singleton SINGLETON;
//全局访问点
//懒汉,判断是否为null,是:new 否:返回实例
//使用synchronized加锁,多线程访问,每次都会阻塞、上锁
public static synchronized Singleton getSingleton(){
if (SINGLETON==null){
SINGLETON=new Singleton();
}
return SINGLETON;
}
}
懒汉-双检锁
- 面试常问,双检锁的形式。if -sycn-if-new
- 线程安全+延迟加载+效率高
- 使用了volatile修饰变量:线程间可见
/**
* 懒汉-双检锁:推荐使用
* 懒汉+线程安全+volatile
*
* @author Promsing(张有博)
* @version 1.0.0
* @since 2022/9/2 - 10:34
*/
public class Singleton05 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println(Singleton.getSingleton().hashCode());
}
});
}
pool.shutdown();
}
}
class Singleton {
//私有化构造函数
private Singleton() {
}
//唯一实例,使用volatile修饰:线程间可见
private static volatile Singleton SINGLETON;
//全局访问点
//懒汉、双检锁
//线程安全+懒加载
public static Singleton getSingleton(){
if (SINGLETON==null){
synchronized (Singleton.class){
if (SINGLETON==null){
SINGLETON=new Singleton();
}
}
}
return SINGLETON;
}
}
懒汉-静态内部类
- 使用类装载的机制来保证初始化实例时只有一个线程访问。
- 静态内部类方式在外部类装载时,不会立即实例化。只有调用getSingleton()方法时,才会装载SingletonInstance类,从而实例化Singleton
/**
* 懒加载-静态内部类:推荐使用
*
* @author Promsing(张有博)
* @version 1.0.0
* @since 2022/9/2 - 10:49
*/
public class Singleton06 {
public static void main(String[] args) {
ExecutorService pool = Executors.newFixedThreadPool(5);
for (int i = 0; i < 20; i++) {
pool.submit(new Runnable() {
@Override
public void run() {
System.out.println(Thread.currentThread().getName());
System.out.println(Singleton.getSingleton().hashCode());
}
});
}
pool.shutdown();
}
}
//静态内部类-推荐使用
class Singleton{
//私有化构造函数
private Singleton() {
}
//外部类加载时,内部类不会加载
//只有内部类使用时,才会被加载,懒加载
//JVM加载类是线程安全的
private static class SingletonInstance{
//唯一实例
private static final Singleton SINGLETON=new Singleton();
}
//全局访问点
public static Singleton getSingleton(){
return SingletonInstance.SINGLETON;
}
}
枚举式单例
/**
* 使用枚举
* 使用枚举的方式,不仅能够避免多线程同步问题,还能防止反序列化重新创建对象
* @author Promsing(张有博)
* @version 1.0.0
* @since 2022/9/2 - 10:59
*/
public class Singleton07 {
public static void main(String[] args) {
Singleton instance = Singleton.INSTANCE;
Singleton instance1 = Singleton.INSTANCE;
System.out.println(instance.hashCode());
System.out.println(instance1.hashCode());
instance.sayHello();
//Runtime
}
}
//使用枚举的方式,不仅能够避免多线程同步问题,还能防止反序列化重新创建对象
//推荐使用
enum Singleton{
INSTANCE;
public void sayHello(){
System.out.println("Hello~~~");
}
}
容器式单例
- 实现方式类似SpringIOC
- IOC容器的工作:负责创建、管理类实例,向使用者提供实例。
- IOC容器负责来创建类实例对象,使用者需要实例就从IOC容器中get。
/**
* 模仿IOC容器,将创建的对象放在Map集合中
* 加载该类时会读取配置文件中,将类放入Map中
*
* @author Promsing(张有博)
* @version 1.0.0
* @since 2022/5/19 - 8:45
*/
public class BeanFactory {
//定义一个properties对象
private static Properties props;
//定义一个Map,用于存放我们创建的对象(单例,当类加载之后就有了对象,之后从Map中获取)
private static Map<String,Object> beans = new ConcurrentHashMap<String,Object>();
//容器
static {
try {
props=new Properties();
//将bean.properties放在了resources路径下
InputStream is=BeanFactory.class.getClassLoader().getResourceAsStream("bean.properties");
props.load(is);
//实例化容器
//从配置文件中获取所有key值
Enumeration<Object> keys = props.keys();
while (keys.hasMoreElements()){
//取出每一个key
String key = keys.nextElement().toString();
//根据key获取value
String path = props.getProperty(key);
Object value=Class.forName(path).newInstance();
//放入容器中
beans.put(key,value);
}
}catch (Exception e){
e.printStackTrace();
}
}
//提供一个访问Map容器的入口
//全局访问点
public static Object getInstance(String name){
return beans.get(name);
}
}
拓展
JDK源码-Runtime
使用饿汉模式
public class Runtime {
private static Runtime currentRuntime = new Runtime();
/**
* Returns the runtime object associated with the current Java application.
* Most of the methods of class <code>Runtime</code> are instance
* methods and must be invoked with respect to the current runtime object.
*
* @return the <code>Runtime</code> object associated with the current
* Java application.
*/
public static Runtime getRuntime() {
return currentRuntime;
}
/** Don't let anyone else instantiate this class */
private Runtime() {}
}
Spring-IOC容器单例
源码类:DefaultSingletonBeanRegistry
方法:registerSingleton
//一级缓存:存放完整对象
private final Map<String, Object> singletonObjects = new ConcurrentHashMap<>(256);
//三级缓存:存储创建单例BEAN的工厂
private final Map<String, ObjectFactory<?>> singletonFactories = new HashMap<>(16);
//二级缓存:存放未属性注入的对象
private final Map<String, Object> earlySingletonObjects = new ConcurrentHashMap<>(16);
//注册单例Bean
public void registerSingleton(String beanName, Object singletonObject) throws IllegalStateException {
Assert.notNull(beanName, "Bean name must not be null");
Assert.notNull(singletonObject, "Singleton object must not be null");
//锁住一级缓存
synchronized (this.singletonObjects) {
Object oldObject = this.singletonObjects.get(beanName);
//当对象存在时,报错
if (oldObject != null) {
throw new IllegalStateException("Could not register object [" + singletonObject +
"] under bean name '" + beanName + "': there is already object [" + oldObject + "] bound");
}
//当对象不存在,添加到容器中
addSingleton(beanName, singletonObject);
}
}
//添加单例的方法,通过加锁将我们的单例放到map中,然后通过getSingleton获取
protected void addSingleton(String beanName, Object singletonObject) {
synchronized (this.singletonObjects) {
this.singletonObjects.put(beanName, singletonObject);
this.singletonFactories.remove(beanName);
this.earlySingletonObjects.remove(beanName);
this.registeredSingletons.add(beanName);
}
}
解决-反射破坏单例
/**
* 反射破坏单例
*
* @author Promsing(张有博)
* @version 1.0.0
* @since 2022/9/2 - 12:20
*/
public class Singleton08 {
public static void main(String[] args) {
try {
//正常获取对象
Singleton s1 = Singleton.getSingleton();
System.out.println(s1.hashCode());
//反射获取对象
Class<Singleton> singletonClass = Singleton.class;
Constructor<Singleton> constructor = singletonClass.getDeclaredConstructor(null);
constructor.setAccessible(true);
Singleton s2 = constructor.newInstance();
System.out.println(s2.hashCode());
} catch (Exception e) {
e.printStackTrace();
}
}
}
class Singleton {
//私有化构造函数
private Singleton() {
if (Singleton.SINGLETON!=null){
throw new RuntimeException("不允许创建多个实例");
}
}
//唯一实例
private static volatile Singleton SINGLETON;
//全局访问点
//懒汉、双检锁
//线程安全+懒加载
public static Singleton getSingleton(){
if (SINGLETON==null){
synchronized (Singleton.class){
if (SINGLETON==null){
SINGLETON=new Singleton();
}
}
}
return SINGLETON;
}
}
解决-序列化破坏单例
使用readResolve()方法或者使用枚举式单例、容器式单例
/**
* 序列化破坏单例
*
* @author Promsing(张有博)
* @version 1.0.0
* @since 2022/9/2 - 12:30
*/
public class Singleton09 {
}
class Singleton implements Serializable {
//私有化构造函数
private Singleton() {
}
//唯一实例
private static Singleton SINGLETON=new Singleton();
//饿汉
public static Singleton getSingleton(){
return Singleton.SINGLETON;
}
//重点方法:解决序列化破坏单例
//写一个这个方法就解决了
private Object readResolve(){
return Singleton.SINGLETON;
}
}
如果本篇博客对您有一定的帮助,大家记得留言
+点赞
+收藏
哦
本文含有隐藏内容,请 开通VIP 后查看