关于单例模式,主要是为了一个类每次在使用它的实例时,都是同一个实例对象。
单例模式一
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;
}
}
静态内部类不需要加锁,同时也能保证线程安全而且也不会浪费内存空间,原因在于,外部类在初始化的时候,如果没有使用到内部类,是不会对内部类进行初始化,只有当我们显示调用静态内部的静态变量或者静态方法时,才会对静态内部类进行初始化。
这里没有给出枚举的实现方式。