本文共 5608 字,大约阅读时间需要 18 分钟。
关于单例模式,主要是为了一个类每次在使用它的实例时,都是同一个实例对象。
单例模式一
public class MySingleton { private static MySingleton instance = new MySingleton(); private MySingleton(){} public static MySingleton getInstance() { return instance; } }
上述的单例模式代码一定是线程安全的,并且每次调用都会返回同一个对象,线程安全的原因是,静态变量是在类的初始化过程当中赋值,这个类在调用的时候,如果没有对类进行初始化,就会进行初始化。唯一的缺点就是,如果我们程序当中一直没有调用getInstance方法,但是我们对这个类进行了初始化,会占用一定的内存空间,因此这种实现方式显然不是最优的,
单例模式二
public class MySingleton{ private static MySingleton instance = null; private MySingleton(){} static{ instance = new MySingleton(); } public static MySingleton getInstance() { return instance; } }
这个方式的实现和上面第一个的方式实现方式基本上一样,都是属于在类初始化时候进行了对象的实例化,但是有可能初始化的时候,我们并没有使用getInstance方法,因此就造成了空间上的浪费。注:静态变量和静态代码块都会在类初始化的时候尽心加载。
单例三
由于上面两个方式都存在内存浪费的一种情况,那我们就设计一种,在使用到这个对象的时候,在进行实例化的方式
public class MySingleton { private static MySingleton instance = null; private MySingleton(){} public static MySingleton getInstance() { if(instance == null){//懒汉式 instance = new MySingleton(); } return instance; } }
很明显这种方式最大的问题就是线程不安全,考虑到线程安全,就有了实例四的实现方式,直接在方法上加synchronious
单例四
public class MySingleton { private static MySingleton instance = null; private MySingleton(){} public synchronized static MySingleton getInstance() { try { if(instance == null){//懒汉式 instance = new MySingleton(); } } catch (InterruptedException e) { e.printStackTrace(); } return instance; } }
这种方式呢,即实现了线程安全,又省了空间,但是呢,也有它的问题synchronous这个锁加的范围太大了,那么接下来就有了下面的,同步代码块,只对需要加锁的地方进行加锁,用不到的都不加锁。
单例五
public class MySingleton { private static MySingleton instance = null; private MySingleton(){} public static MySingleton getInstance() { try { if(instance == null){//懒汉式 //创建实例之前可能会有一些准备性的耗时工作 Thread.sleep(300); synchronized (MySingleton.class) { instance = new MySingleton(); } } } catch (InterruptedException e) { e.printStackTrace(); } return instance; } }
这个看起来已经很完美了,写个代码测试下
class MyThread extends Thread{ @Override public void run() { System.out.println(Singleton.getInstance().hashCode()); }}--------------------------------------------- public static void main(String[] args) { MyThread[] thread = new MyThread[10]; for(int i = 0; i < thread.length; i++ ){ thread[i] = new MyThread(); } for(int i = 0; i < thread.length; i++ ){ thread[i].start(); }}
上述打印出来的hash值并不相同,原因在于虽然我们对创建部分进行了加锁,但是需要二次检查
单例六
public class MySingleton { //使用volatile关键字保其可见性 volatile private static MySingleton instance = null; private MySingleton(){} public static MySingleton getInstance() { try { if(instance == null){//懒汉式 //创建实例之前可能会有一些准备性的耗时工作 Thread.sleep(300); synchronized (MySingleton.class) { if(instance == null){//二次检查 instance = new MySingleton(); } } } } catch (InterruptedException e) { e.printStackTrace(); } return instance; } }
这种情况下才能保证每次打印出来的对象都是同一个对象。但是以上我们介绍的所有的实现方式,在序列化和反序列化的时候,是否还能保证对象相同,我们看下测试代码
public static void main(String[] args) { Singleton singleton = Singleton.getInstance(); File file = new File("C:\\Users\\Administrator\\Desktop\\MySingleton.txt"); try { FileOutputStream fos = new FileOutputStream(file); ObjectOutputStream oos = new ObjectOutputStream(fos); oos.writeObject(singleton); fos.close(); oos.close(); System.out.println(singleton.hashCode()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } try { FileInputStream fis = new FileInputStream(file); ObjectInputStream ois = new ObjectInputStream(fis); Singleton rSingleton = (Singleton) ois.readObject(); fis.close(); ois.close(); System.out.println(rSingleton.hashCode()); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); } catch (ClassNotFoundException e) { e.printStackTrace(); } }
这时候打印出来结果hash值并不相同,所以之前的单例实现还需继续修改,在MySingleton方法当中实现readResolve方法即可
//该方法在反序列化时会被调用,该方法不是接口定义的方法,有点儿约定俗成的感觉 protected Object readResolve() throws ObjectStreamException { System.out.println("调用了readResolve方法!"); return MySingletonHandler.instance; }
单例七
使用静态内部类
public class MySingleton implements Serializable { private static final long serialVersionUID = 1L; //内部类 private static class MySingletonHandler{ private static MySingleton instance = new MySingleton(); } private MySingleton(){} public static MySingleton getInstance() { return MySingletonHandler.instance; } }
静态内部类不需要加锁,同时也能保证线程安全而且也不会浪费内存空间,原因在于,外部类在初始化的时候,如果没有使用到内部类,是不会对内部类进行初始化,只有当我们显示调用静态内部的静态变量或者静态方法时,才会对静态内部类进行初始化。
这里没有给出枚举的实现方式。
转载地址:http://cmvti.baihongyu.com/