您好,欢迎来到三六零分类信息网!老站,搜索引擎当天收录,欢迎发信息

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

2024/3/9 10:23:59发布45次查看
单例模式、多实例模式、和线程安全单例模式单例模式是指确保一个类仅有一个唯一的实例,并且提供了一个全局的访问点。
分类: 懒汉式、饿汉式
为什么需要单例模式?
再某些特殊的情况下,存在一个类仅能用来产生一个唯一对象的必要性。例如:打印机室有许多打印机,但是它的打印管理系统只有一个打印任务控制对象,该对象管理打印排队并分配打印任务给各个打印机。单例模式正是为了解决这样的需求而产生的。
实现思路:
为了防止客户端利用构造器创建多个对象,将构造方法声明为 private 类型。但这样会使得这个类不可用,所以必须提供一个可以获得实例的静态方法,通常称为 getinstance 方法, 该方法返回一个实例。这个方法必须是静态的,因为静态方法是根据类名调用的,否则也是无法使用的。
类图:懒汉式
类图:饿汉式
先来看一个简单的例子:
测试单例类: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)); }}
运行结果
懒汉式和饿汉式对比创建区别
懒汉式是在第一次调用静态方法 getinstance() 时创建单例对象。
饿汉式是在类加载时创建单例对象,即在声明静态单例对象时实例化单例类。
线程安全
懒汉式是线程不安全的,而饿汉式是线程安全的(下面会测试)。
资源占用
懒汉式是等到使用时才会创建,而饿汉式是在类加载时创建。所以懒汉式没有饿汉式快,但是饿汉式比较占用资源,如果一直不使用,会很占据资源。
多线程模式下的安全性多线程类
import java.util.hashset;import java.util.set;public class dogthread extends thread{ private dog dog; private set<dog> 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()); }}
运行结果
注意:多线程的结果是很难预测的,这里涉及线程的竞争,可能多次运行结果是一样的(多次一样并不代表是绝对正确),但是只要多次测试,就能看到不一样的结果。
说明
这里我使用一点集合的技巧,利用 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<multiinstance> 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()); }}
运行结果
注意:如果在多线程环境下使用,也是要考虑线程安全的。感兴趣的可以自己实现一下。
单例模式一定是安全的吗?
不一定,有很多方法可以破坏单例模式!
这里举例看一看(我只能举我知道的哈!其他的感兴趣,可以去探究一下!)
使用反射:这种办法是非常有用的,通过反射即使是私有的属性和方法也可以访问了,因此反射破坏了类的封装性,所以使用反射还是要多多小心。但是反射也有许多其他的用途,这是一项非常有趣的技术(我也只是会一点点)。
使用反射破坏单例模式测试类
这里使用的还是前面的 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)); }}
运行结果
说明
这里可以看出来通过对象序列化(这里也可以说是对象的深拷贝或深克隆),
同样也可以实现类的实例的不唯一性。这同样也算是破坏了类的封装性。对象序列化和反序列化的过程中,对象的唯一性变了。
这里具体的原因很复杂,我最近看了点深拷贝的知识,所以只是知其然不知其之所以然。(所以学习是需要不断进行的!加油诸位。)
这里我贴一下别的经验吧:(感兴趣的可以实现一下!)
为什么序列化可以破坏单例了?
答:序列化会通过反射调用无参数的构造方法创建一个新的对象。
这个东西目前超出了我的能力范围了,但也是去查看源码得出来的,就是序列化(serializable)和反序列化(externalizable)接口的详细情况了。但是有一点,它也是通过反射来做的的,所以可以看出**反射(reflect)**是一种非常强大和危险的技术了。
以上就是java单例模式和线程安全问题怎么解决的详细内容。
该用户其它信息

VIP推荐

免费发布信息,免费发布B2B信息网站平台 - 三六零分类信息网 沪ICP备09012988号-2
企业名录 Product