当前位置:

首页 > 编程开发 > java单例模式和线程安全问题怎么解决

java单例模式和线程安全问题怎么解决

单例模式、多实例模式、和线程安全单例模式单例模式是指确保一个类仅有一个唯一的实例,并且提供了一个全局的访问点。分类:懒汉式、饿汉式为什么需要单例模式?再某些特殊的情况下,存在一个类仅能用来产生一个唯一对象的必要性。例如:打印机室有许多打印机,但是它的打印管理系统只有一个打印任务控制对象,该对象管理打印排队并分配打印任务给各个打印机。单例模式正是为了解决这样的需求而产生的。实现思路:为了防止客户端利用构造器创建多个对象,将构造方法声明为private类型。但这样会使得这个类不可用,所以必须提供一个可以获得实

    单例模式、多实例模式、和线程安全

    单例模式

    单例模式是指确保一个类仅有一个唯一的实例,并且提供了一个全局的访问点。

    分类: 懒汉式、饿汉式

    为什么需要单例模式?

    再某些特殊的情况下,存在一个类仅能用来产生一个唯一对象的必要性。例如:打印机室有许多打印机,但是它的打印管理系统只有一个打印任务控制对象,该对象管理打印排队并分配打印任务给各个打印机。单例模式正是为了解决这样的需求而产生的。

    实现思路:

    为了防止客户端利用构造器创建多个对象,将构造方法声明为 private 类型。但这样会使得这个类不可用,所以必须提供一个可以获得实例的静态方法,通常称为 getInstance 方法, 该方法返回一个实例。这个方法必须是静态的,因为静态方法是根据类名调用的,否则也是无法使用的。

    类图:懒汉式

    java单例模式和线程安全问题怎么解决

    类图:饿汉式

    java单例模式和线程安全问题怎么解决

    先来看一个简单的例子:

    测试单例类:Dog’

    //懒汉式
    public class Dog {
    	private static Dog dog;
    	private String name;
    	private int age;
    	
    	//私有的构造器
    	private Dog() {}
    	
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public int getAge() {
    		return age;
    	}
    
    	public void setAge(int age) {
    		this.age = age;
    	}
    
    	//静态工厂方法
    	public static Dog getInstance() {
    		if (dog == null) {
    			dog = new Dog();
    		}
    		return dog;
    	}
    
    	@Override
    	public String toString() {
    		return "Dog [name=" + name + ", age=" + age + "]";
    	}
    }

    测试单例类:Cat

    //饿汉式
    public class Cat {
    	private static Cat cat = new Cat();
    	private String name;
    	private int age;
    	
    	//私有构造器
    	private Cat() {}
    	
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    	public int getAge() {
    		return age;
    	}
    	
    	public void setAge(int age) {
    		this.age = age;
    	}
    
    	//静态工厂方法
    	public static Cat getInstance() {
    		return cat;
    	}
    
    	@Override
    	public String toString() {
    		return "Cat [name=" + name + ", age=" + age + "]";
    	}
    }

    测试类

    import java.util.HashSet;
    import java.util.Set;
    
    public class Client {
    
    	public static void main(String[] args) {
    		//单线程模式测试
    		Dog dog1 = Dog.getInstance();
    		Dog dog2 = Dog.getInstance();
    		System.out.println("dog1 == dog2: "+(dog1 == dog2));
    		
    		Cat cat1 = Cat.getInstance();
    		Cat cat2 = Cat.getInstance();
    		System.out.println("cat1 == cat2: "+(cat1 == cat2));
    	}
    }

    运行结果

    java单例模式和线程安全问题怎么解决

    懒汉式和饿汉式对比

    创建区别

    懒汉式是在第一次调用静态方法 getInstance() 时创建单例对象。
    饿汉式是在类加载时创建单例对象,即在声明静态单例对象时实例化单例类。

    线程安全

    懒汉式是线程不安全的,而饿汉式是线程安全的(下面会测试)。

    资源占用

    懒汉式是等到使用时才会创建,而饿汉式是在类加载时创建。所以懒汉式没有饿汉式快,但是饿汉式比较占用资源,如果一直不使用,会很占据资源。

    多线程模式下的安全性

    多线程类

    import java.util.HashSet;
    import java.util.Set;
    
    public class DogThread extends Thread{
    	private Dog dog;
    	private Set set;
    	
    	public DogThread() {
    		set = new HashSet<>();
    	}
    	
    	//这个方法是为了测试添加的。
    	public int getCount() {
    		return set.size();
    	}
    	
    	@Override
    	public void run() {
    		dog = Dog.getInstance();
    		set.add(dog);
    	}
    }

    多线程测试类

    import java.util.HashSet;
    import java.util.Set;
    
    public class Client {
    
    	public static void main(String[] args) {
    		//单线程模式测试
    		Dog dog1 = Dog.getInstance();
    		Dog dog2 = Dog.getInstance();
    		System.out.println("dog1 == dog2: "+(dog1 == dog2));
    		
    		Cat cat1 = Cat.getInstance();
    		Cat cat2 = Cat.getInstance();
    		System.out.println("cat1 == cat2: "+(cat1 == cat2));
    		
    		//多线程模式测试
    		DogThread dogThread = new DogThread();
    		Thread thread = null;
    		for (int i = 0; i < 10; i++) {
    			thread = new Thread(dogThread);
    			thread.start();	
    		}
    		
    		try {
    			Thread.sleep(2000); //主线程等待子线程完成!
    		} catch (InterruptedException e) {
    			e.printStackTrace();
    		}
    		System.out.println("dog's number: "+dogThread.getCount());
    	}
    }

    运行结果
    注意:多线程的结果是很难预测的,这里涉及线程的竞争,可能多次运行结果是一样的(多次一样并不代表是绝对正确),但是只要多次测试,就能看到不一样的结果。

    java单例模式和线程安全问题怎么解决

    java单例模式和线程安全问题怎么解决

    说明

    这里我使用一点集合的技巧,利用 Set 集合的特性,把每次产生的 dog 对象存入 Set集合中,最后只要调用集合的 size() 方法就行了。可以看出来产生了两个 dog 对象,这就是产生了错误,这就是属于编程错误了。还要明白多线程下不一定会出错,所以产生的 dog 对象小于线程数。
    由于 饿汉式单例 是线程安全的,这里就不测试了,有兴趣的可以测试一下。

    解决懒汉式单例线程安全的方法:同步
    注意:同步有很多种方法,也可以使用 Lock 进行处理,同步是一种方法,不是特指 synchronzied 这个关键字,感兴趣的人可以多探究一下。
    并且同步的方法通常比较慢,性能方面也要权衡。

    	//静态同步工厂方法
    	public synchronized static Dog getInstance() {
    		if (dog == null) {
    			dog = new Dog();
    		}
    		return dog;
    	}

    多实例模式

    这里补充一个多实例的模式,就是对象数量是固定数目的。可以看出单例模式的推广。当然了实现方式也有很多,大家可以尝试以下,这里是我的方式。

    多实例模式类

    //固定数目实例模式
    public class MultiInstance {
    	//实例数量,这里为四个
    	private final static int INSTANCE_COUNT = 4;
    	private static int COUNT = 0;
    	private static MultiInstance[] instance = new MultiInstance[4];
    	
    	private MultiInstance() {};
    	
    	public static MultiInstance getInstance() {
    		//注意数组的下标只能为 COUNT - 1
    		if (MultiInstance.COUNT <= MultiInstance.INSTANCE_COUNT - 1) {
    			instance[MultiInstance.COUNT] = new MultiInstance();
    			MultiInstance.COUNT++;
    		}
    		//返回实例前,执行了 COUNT++ 操作,所以 应该返回上一个实例
    		return MultiInstance.instance[MultiInstance.COUNT-1];  
    	}
    }

    测试类

    import java.util.HashSet;
    import java.util.Set;
    
    public class Test {
    	public static void main(String[] args) {
    		
    		System.out.println("------------------------");
    		testMultiInstance();
    	}
    
    	//测试多实例模式(单例的扩展,固定数目实例)
    	public static void testMultiInstance() {
    		Set instanceSet = new HashSet<>();
    		MultiInstance instance = null;
    		for (int i = 0; i < 10; i++) {
    			instance = MultiInstance.getInstance();
    			instanceSet.add(instance);
    		}
    		System.out.println("8个实例中,不同的实例有:"+instanceSet.size());   
    	}
    }

    运行结果
    注意:如果在多线程环境下使用,也是要考虑线程安全的。感兴趣的可以自己实现一下。

    java单例模式和线程安全问题怎么解决

    单例模式一定是安全的吗?

    不一定,有很多方法可以破坏单例模式!

    这里举例看一看(我只能举我知道的哈!其他的感兴趣,可以去探究一下!)
    使用反射:这种办法是非常有用的,通过反射即使是私有的属性和方法也可以访问了,因此反射破坏了类的封装性,所以使用反射还是要多多小心。但是反射也有许多其他的用途,这是一项非常有趣的技术(我也只是会一点点)。

    使用反射破坏单例模式测试类

    这里使用的还是前面的 Dog 实体类。注意我这里的**包名:**com。
    所有的类都是在 com包 下面的。

    import java.lang.reflect.Constructor;
    import java.lang.reflect.InvocationTargetException;
    
    public class Client {
    	public static void main(String[] args) throws 
    	ClassNotFoundException, 
    	NoSuchMethodException, 
    	SecurityException, 
    	InstantiationException, 
    	IllegalAccessException, 
    	IllegalArgumentException, 
    	InvocationTargetException {
    	
    		Class clazz = Class.forName("com.Dog");
    		Constructor con = clazz.getDeclaredConstructor();
    		//设置可访问权限
    		con.setAccessible(true);
    		Dog dog1 = (Dog) con.newInstance();
    		Dog dog2 = (Dog) con.newInstance();
    		System.out.println(dog1 == dog2);
    	}
    }

    说明:反射的功能是很强大的,从这里既可以看出来,正是有了反射,才使得Java 语言具有了更多的特色,这也是Java的强大之处。

    使用对象序列化破坏单例模式

    测试实体类:Dog(增加一个对象序列化接口实现)

    import java.io.Serializable;
    //懒汉式
    public class Dog implements Serializable{
    	private static final long serialVersionUID = 1L;
    	
    	private static Dog dog;
    	private String name;
    	private int age;
    	
    	//私有的构造器
    	private Dog() {}
    	
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    
    	public int getAge() {
    		return age;
    	}
    
    	public void setAge(int age) {
    		this.age = age;
    	}
    
    	//静态工厂方法
    	public synchronized static Dog getInstance() {
    		if (dog == null) {
    			dog = new Dog();
    		}
    		return dog;
    	}
    
    	@Override
    	public String toString() {
    		return "Dog [name=" + name + ", age=" + age + "]";
    	}
    }

    对象序列化测试类

    import java.io.ByteArrayInputStream;
    import java.io.ByteArrayOutputStream;
    import java.io.IOException;
    import java.io.ObjectInputStream;
    import java.io.ObjectOutputStream;
    
    public class Client {
    	public static void main(String[] args) throws IOException, ClassNotFoundException {
    		Dog dog1 = Dog.getInstance();
    		dog1.setName("小黑");
    		dog1.setAge(2);
    		System.out.println(dog1.toString());
    		
    		ByteArrayOutputStream bos = new ByteArrayOutputStream();
    		ObjectOutputStream oos = new ObjectOutputStream(bos);
    		oos.writeObject(dog1);
    		
    		
    		ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray());
    		ObjectInputStream ois = new ObjectInputStream(bis);
    		Dog dog2 = (Dog) ois.readObject();
    		System.out.println(dog2.toString());
    		System.out.println("dog1 == dog2: "+(dog1 == dog2));
    		
    	}
    }

    运行结果

    java单例模式和线程安全问题怎么解决

    说明
    这里可以看出来通过对象序列化(这里也可以说是对象的深拷贝或深克隆),
    同样也可以实现类的实例的不唯一性。这同样也算是破坏了类的封装性。对象序列化和反序列化的过程中,对象的唯一性变了。

    这里具体的原因很复杂,我最近看了点深拷贝的知识,所以只是知其然不知其之所以然。(所以学习是需要不断进行的!加油诸位。)
    这里我贴一下别的经验吧:(感兴趣的可以实现一下!)

    为什么序列化可以破坏单例了?
    答:序列化会通过反射调用无参数的构造方法创建一个新的对象。

    这个东西目前超出了我的能力范围了,但也是去查看源码得出来的,就是序列化(serializable)和反序列化(externalizable)接口的详细情况了。但是有一点,它也是通过反射来做的的,所以可以看出**反射(reflect)**是一种非常强大和危险的技术了。

    本文内容来源于互联网,如有侵权请联系删除。
    作者最新文章
    编程开发
    相关文章 更多
    谷歌浏览器Mac版入口
    谷歌浏览器Mac版入口

    谷歌浏览器Mac版官方安装指南 谷歌浏览器Mac版官方安装入口是https://www.google.com/chrome/,需macOS 12+系统、500MB空间,下载.dmg后拖入应用程序安装,支持多设备同步、性能优化与隐私保护功能。 苹果电脑Chrome的安装入口究竟在哪里?这个问题最近可是

    Chrome浏览器JS脚本不运行怎么办
    Chrome浏览器JS脚本不运行怎么办

    Chrome中JavaScript未执行需依次检查:一、移除站点级禁用并添加允许域名;二、开启全局JavaScript开关;三、禁用干扰扩展;四、在开发者工具中启用JavaScript;五、重置内容设置为默认。 有时在Chrome里打开网页,会发现交互按钮点了没反应,数据加载不出来,页面仿佛“静止”

    IE浏览器怀旧版在线网址
    IE浏览器怀旧版在线网址

    IE浏览器怀旧版在线网址:一次精准的技术时光回溯 最近,不少老用户和怀旧爱好者在反复搜索一个问题:那个经典的Internet Explorer,如今还能在哪里原汁原味地体验到?答案指向一个特定的地址:https://ie.microsoft.com/legacy/。 这个网站远不止是一个简单的“皮肤

    火狐浏览器有哪些设置功能
    火狐浏览器有哪些设置功能

    火狐浏览器五大核心设置功能:解锁高效、安全与个性化体验 火狐浏览器功能强大,但如果不仔细挖掘,很多能大幅提升效率和安全性的设置可能就“藏着掖着”了。这就好比拥有一台高性能设备,却只用了基础模式。那么,如何把它调整到最顺手、最安全的状态?接下来,我们就聚焦于当前版本(截至2025年末)最关键的五大设置

    chrome搜索免验证入口
    chrome搜索免验证入口

    Chrome官方免验证入口为https://www.google.cn/chrome/,提供全平台安装包、免登录即用、本地化安全机制及引擎级性能优化。 到底该去哪里找正版、免费且无需繁琐验证的Chrome浏览器入口?这个问题困扰了不少网友。今天,我们就来直通核心,为大家详细拆解Chrome引擎的官方

    java heap space 选型思路:使用场景与区别整理
    java heap space 选型思路:使用场景与区别整理

    Java堆是JVM存储对象的核心内存区域,配置需结合场景:单体应用适中设置;大数据处理需大堆并关注GC停顿;微服务强调快速启动;高并发需精细划分堆区域。关键参数-Xms和-Xmx建议等值以稳定性能。垃圾回收器选择影响效率,如G1适用于大堆,ZGC可实现低停顿。内存错误时需监控堆状态。

    java heap space 使用中遇到的问题怎么解决
    java heap space 使用中遇到的问题怎么解决

    Java堆内存溢出错误通常因内存泄漏、数据处理需求过大或JVM参数配置不当引起。排查时可借助jmap、堆转储及MAT等工具定位问题。解决方案包括调整JVM内存参数(如-Xmx)、修复代码中的内存泄漏、优化大数据处理逻辑,并建立持续监控与预防机制,以保障应用稳定运行。

    java xml 选型思路:使用场景与区别整理
    java xml 选型思路:使用场景与区别整理

    XML在Java开发中用于配置、数据交换等场景。解析方式主要有DOM、SAX、StAX及第三方库。DOM适合操作小文件,SAX/StAX适合处理大文件流,JAXB用于对象与XML映射。选型需结合数据大小、内存、性能及团队熟悉度,现代框架常封装底层解析。

    using namespace 使用中遇到的问题怎么解决
    using namespace 使用中遇到的问题怎么解决

    命名空间的基本概念与常见引入问题在C++等编程语言中,命名空间(namespace)是一种将代码标识符(如变量、函数、类名)封装在特定名称下的机制,其主要目的是避免命名冲突,尤其是在大型项目或使用多个第三方库时。使用“using namespace”指令可以将指定命名空间中的所有名称引入当前作用域,

    c语言函数递归 实操经验总结:这些技巧很实用
    c语言函数递归 实操经验总结:这些技巧很实用

    理解递归的基本原理在C语言中,递归是一种函数调用自身的编程技术。要掌握它,首先需要理解其核心思想:将一个复杂的大问题,分解为一个或几个与原问题相似但规模更小的子问题,直到子问题足够简单,可以直接求解。这个过程通常包含两个关键部分:递归出口和递归体。递归出口定义了问题何时不再继续分解,即最简单、可直接

    查看更多
    精品专题 更多
    装机必备
    装机必备

    正软商城装机必备专区,精选办公、浏览器、安全防护、影音播放、压缩解压、设计创作和系统工具等电脑常用正版软件,帮助用户快速完成新电脑软件配置。

    Windows
    Windows

    正软商城Windows软件专区,汇集适用于Windows电脑的办公、设计、安全防护、影音播放、开发工具和系统优化软件,提供软件介绍、系统要求、正版授权及购买下载服务。

    macOS软件
    macOS软件

    正软商城macOS软件专区,精选适用于Mac电脑的办公、设计、影音、效率、开发和系统工具,提供软件功能介绍、macOS兼容版本、正版授权及购买下载服务。

    Mac软件 更多
    灵活计算器
    灵活计算器
    macOS/iOS/Android

    灵活计算器是一款笔记式算数应用,支持实时计算、动态关联和云端同步功能。记录、整理和输出之间的过渡会更自然,适合长期写作、做笔记或持续沉淀个人内容。

    赤友清理大师
    赤友清理大师
    macOS

    赤友清理大师是一款为 Mac 设计的智能清理优化工具,可精准扫描垃圾、大文件、重复文件等,释放磁盘空间。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

    极度公式
    极度公式
    Windows/macOS/Linux

    极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

    WINDOWS 更多
    Windows 10
    Windows 10
    Windows

    Windows 10 是一款微软推出的经典操作系统,拥有硬件兼容性与多任务处理能力。它更偏向把系统状态查看和常用调节动作放在一起,适合需要持续观察和微调设备状态的场景。

    极度公式
    极度公式
    Windows/macOS/Linux

    极度公式是一款跨平台专业LaTeX公式识别编辑软件,支持OCR公式识别和多平台编辑。和使用说明,避免使用,享受完整功能与稳定支持。做扫描整理、文字提取和表格转换时,它能把识别后的处理步骤接得更顺,资料录入这类场景会省下不少时间。

    密码键盘
    密码键盘
    Windows/macOS/iOS/Android

    密码键盘是一款兼具安全性与便捷性的高效密码管理器。日常使用里的持续防护和信息管理会更突出,适合把安全控制放进长期使用流程中的场景。