单例模式属于对象创建型模式,其意图是保证一个类仅有一个实例,并提供一个访问它的全局访问点。(一般有3种形式a:饥汉,b:懒汉,c:登记)
创建模式分类:A:工厂模式(Factory Model)B:单例模式(singleton pattern)C:原型模式(prototype)D:Builder模式
引:单例模式,我个人理解是对类的实例的创建的一种方式(手段),java中对象可以被显 示的或者是隐含的创建,即构造一个类的实
例.以下就以创建类的实例来说明单例的形式.
package com.model.singleton;
import java.lang.reflect.Constructor;
/**
* 单例 (单例常使用的两种方式(饱汉,饥汉))
* @author sw
*
*/
//简单的单例(饥汉:在类第一次加载实例化一个实例,所以在使用时,花销的时间比较少,比较快。)
public class Singleton {
private static Singleton instance = new Singleton();
private String id;
//私有的构造方法
private Singleton(){}
public static Singleton getInstance(){
return instance;
}
@SuppressWarnings("unchecked")
public static void main(String[] args) {
//创建两个实例s1 s2
Singleton s1=Singleton.getInstance();
Singleton s2=Singleton.getInstance();
Singleton s3=new Singleton();
//判断两个实例是否相同
System.out.println(s1==s2);
s1.setId("singleton");
//如果s1与s2不是同一个实例那么s2.getId()时是null即s3
System.out.println("s2:"+s2+" "+s2.getId()+" "+"s1:"+s1);
System.out.println("s3:"+s3+s3.getId());
/**
* 这里需要注意一个问题,就是通过反射每次都能获得类的新的实例,所以如果要使用单例,
* 就不要使用反射创建类的实例对象。
*/
try {
Class c = Class.forName(Singleton.class.getName());
//构造器
Constructor ct = c.getDeclaredConstructor();
ct.setAccessible(true);
Singleton s4 = (Singleton)ct.newInstance();
System.out.println("s4:"+s4+s4.getId());
} catch (Exception e) {
System.out.println(e.getMessage());
}
//接着测试两种方式的性能问题
Long startTime1 = System.currentTimeMillis();
for(int i = 0 ;i<100000000;i++){
Singleton instance = Singleton.getInstance();
}
Long endTime1 = System.currentTimeMillis();
System.out.println(endTime1 - startTime1);
//可以明显的看出饥汉比懒汉式单例性能上好,所以你使用了单例,如果在乎性能的话,请使用饥汉式。
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}
单例模式的要点:从项目角度出发A:是某个类只能有一个实例;B:是它必须自行创建这个实例;C:是它必须自行向整个系统提供这个实
例.从代码出发D:私有的构造方法E:指向自己实例的私有静态引用 F:已自己实例为返回值的静态的共有方法
优点:单例模式的优点:A:在内存中只有一个对像,节省内存空间B:避免频繁的创建销毁对象.C:避免对共享资源的多重调用D:可以全局
访问 使用场合(自定义,不大准确):A:需要频繁实例化然后销毁的对象。B:创建对象时耗时过多或者耗资源过多,但又经常用到的对
象。C:有状态的工具类对象(项目中有个Tools类,没有状态,不是singleton,类中的方法采用Static修饰直接是Tools.方法(),我个人
倾向static好,请大家各抒己见)。D:频繁访问数据库或文件的对象。以及其他我没用过的所有要求只有一个对象的场景.使Singleton
的缺陷:A:如何销毁这个实例,何时销毁,或者是解除它的职责,假设添加一个distory()方法,将this.singleton置为null,但此时系统
中的其它模块可能正持有这个实例的引用.随后在调用getInstance()方法,就 会产生例外一个新的实例,就会造成同时存在两个实例
的现象.B:效率问题,每次调用都会进行if()条件语句的判断C:不能继承,从Singleton派生出的类不是Singleton,如果其想要成为
Singleton就必须增加所必须的static函数和变量
package com.model.singleton;
/**
*
* @author sw
*
*/
//懒汉模式(该单例方式,只有在第一次调用getInstance()时才会初始化一个实例,
//所以在第一次使用时会花销一定的时间,同时需要同步,同样会花销一定的时间,这样性能上会比第一种要差点)
public class SingletonModel {
private static SingletonModel singletonModel = null;
private String name;
//私有的构造方法
private SingletonModel() { }
//加上了synchronized()
public synchronized static SingletonModel getInstance(){
if(null == singletonModel) {
singletonModel = new SingletonModel();
}
return singletonModel;
}
public static void main(String[] args) {
//测试两种方式性能问题(时间长的很长的原因的是加了synchronized(线程安全随后会讲解
//单例模式的线程安全性))去掉synchronized时间也不如饥汉方式
Long startTime = System.currentTimeMillis();
for(int i = 0 ;i<100000000;i++){
SingletonModel instance = SingletonModel.getInstance();
}
Long endTime = System.currentTimeMillis();
System.out.println(endTime - startTime);
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.model.singleton;mport java.util.HashMap;import java.util.Map;
/**
* 登记式单例类
* @author sw
*
*/
public class SingletonRegister {
private static Map<String, SingletonRegister> map=new HashMap<String, SingletonRegister>();
static{
SingletonRegister single = new SingletonRegister();
map.put(single.getClass().getName(), single);
}
private String test;
//保护的默认构造函数
protected SingletonRegister(){}
//静态工厂方法,返还此类惟一的实例
public static SingletonRegister getInstance(String name) {
if(name == null) {
name = SingletonRegister.class.getName();
System.out.println("name == null"+"--->name="+name);
}
if(map.get(name) == null) {
try {
map.put(name, (SingletonRegister) Class.forName(name).newInstance());
} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
}
System.out.println(map.get(name));
return map.get(name);
}
//一个任意的方法
public String about() {
return "Hello, I am RegSingleton.";
}
public static void main(String[] args) {
SingletonRegister s1 = SingletonRegister.getInstance(null);
//方法可能不准确
System.out.println(s1.about());
//再次用属性去校验
SingletonRegister s2 = SingletonRegister.getInstance(null);
s2.setTest("wo shi s2 set");
SingletonRegister s3 = SingletonRegister.getInstance(null);
System.out.println(s3.getTest());
}
public String getTest() {
return test;
}
public void setTest(String test) {
this.test = test;
}
}
package com.model.singleton;
/**
* 单例在多线程的环境下
* @author sw
*
*/
public class SingletonThread {
private static SingletonThread sThread=new SingletonThread();
private String name;
private SingletonThread(){ }
public static SingletonThread getSingletonThread(){
return sThread;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
package com.model.singleton;
public class Test {
public static void main(String[] args) {
ThreadTest t1=new ThreadTest();Thread ta=new Thread(t1);
ta.setName("ta");Thread tb=new Thread(t1); tb.setName("tb"); ta.start(); tb.start();
}
}
package com.model.singleton;
/**
* 创建两个线程
* @author sw
*
*/
public class ThreadTest implements Runnable {
//3个线程
@SuppressWarnings("static-access")
@Override
public void run() {
SingletonModel s3=SingletonModel.getInstance();
Thread t1=Thread.currentThread();
SingletonModel s4 =SingletonModel.getInstance();
s3.setName("李四"); System.out.println(s4.getName());
}
public synchronized void method(){
//饥汉方式是线程安全的
/**原因是:当多个线程同时去访问该类的getInstance()方法时
* 不会初始化多个不同的对象,这是因为,JVM在加载此类时对于static属性的
* 初始化只能由一个线程执行而且仅一次(static属性和static初始化块的初始化
* 是串行的)
*/
SingletonThread s1=SingletonThread.getSingletonThread();
SingletonThread s2=SingletonThread.getSingletonThread();
s1.setName("张三");
System.out.println(s2.getName());
//懒汉方式;//此处我没有想出来两个线程如何同时执行run()
//假设两个线程同时执行的话,Thread1与Thread2都会进入创建单例的代码块分别创建实例,在
/**
* if(null == singletonModel){
singletonModel = new SingletonModel();
}
*/
/*处Thread1创建了一个实例对象,Thread2此时无法知道,于是它也会创建一个实例,最终两个线程所持有的
* 实例不是同一个,如果java没有垃圾回收机制的话,会因为忽略其它线程创建的实例.而引起内存的泄漏
* 解决方案是在Instance方法加上synchronized此时的性能问题刚才大家都看到了
* "很差"!!!!
* 原因:只要保证创建实例的逻辑代码被一个线程执行就ok而返回引用的那段代码是没有必要同步的,
* 根据这种思路,以下代码可以改为:SingletonSyThread
* public synchronized static SingletonModel getInstance(){
if(null == singletonModel){
singletonModel = new SingletonModel();
}
return singletonModel;
}
*
*/
SingletonModel s3=SingletonModel.getInstance();
SingletonModel s4=SingletonModel.getInstance();
s3.setName("李四");
System.out.println(s4.getName());
}
}
package com.model.singleton;
/**
*
* @author sw
*此程序只有在java5及以上版本才能正常执行,因为java平台的内存模式容许out-of-oorder writes引起
*假定有两个线程Thread1和Thread2,他们的执行步骤如下:
*
*还有另外一种方式LazyLoadedSingleton
*/
public class SingletonSyThread {
private volatile static SingletonSyThread singletonSyThread=null;
public static SingletonSyThread getInstance(){
if(singletonSyThread==null){//在此处不管哪一个线程先占据同步锁创建实例对象,都不会组织另外一个线程继续进入实
例代码块重新创建实例对象,这样
//同样会生成两个实例对象.所以我们需要在同步的代码块里,进行第二次的判断,此时是同步的
synchronized (SingletonSyThread.class) {//同步创建块
if (singletonSyThread==null) {//再次检查,如果它是否被创建Double_Check Locking
//因为属性singletonSyThread是被volatile修饰的.因为volatile具有synchronized的可见性特点
//也就是说随后进来的线程能够发现volatile变量的最新值,
singletonSyThread=new SingletonSyThread();
}
}
}
return singletonSyThread;
}
}
package com.model.singleton;import java.io.Serializable;
/**
* Singleton 的序列化
* @author sw
*注意事项:如果单例实现了Serializable 接口.在默认的情况下,每次反序列化都会产生一个新的实例对象,
*解决方式:readResolve()在反序列化出来执行之前执行的方法,我们在这个方法中,将我们创建的实例替换掉
*反序列化出来的那个实例,让它指向内存中的那个单例对象即可
*/
public class SingletonSeria implements Serializable {
private static final long serialVersionUID = -5569630403365107116L;
private static SingletonSeria seria=new SingletonSeria();
private SingletonSeria(){
}
private Object readResolve(){
return seria;
}
public static SingletonSeria getInstance(){
return seria;
}
}