优点:解决对象的唯一性,确保一个类只有一个实例,节约系统资源。
缺点:由于没有抽象层,单例扩展比较困难,如果实例对象太久不被利用,系统会认为是垃圾而被回收,导致对象丢失。
单例模式主要分为以下两种:
1.饿汉式
2.懒汉式
其他的单例模式:
1.静态内部类式
2.枚举
单例模式怎么用呢?为什么要使用单例模式?解决对象的唯一性,确保一个类只有一个实例,减少内存开销,这就是单例存在的价值,看一个不是单例模式下的对象调用结果:
/**
* 这是一个普通对象
*/
public class OrdinaryObject {
//构造器
public OrdinaryObject() {
}
}
//然后使用调用者把对象new出来,看每一次是不是都是一个新对象
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
/**
* 这是调用者
*/
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//测试普通对象
OrdinaryObject oo1 = new OrdinaryObject();
OrdinaryObject oo2 = new OrdinaryObject();
Log.d("TAG","oo1对象:"+oo1+"\n"+"oo2对象:"+oo2);
}
}
结果每new一次都是一个新对象,如果这个对象只需要有一个就足够应用,还比较消耗资源的时候,这不就造成很大的资源浪费了?所以单例模式就有它存在的价值了,首先看看饿汉式怎么做,以及效果。
饿汉式(特点:调用效率高,线程安全,但是不能懒加载)
/**
* 这是一个饿汉式单例模式
*/
public class SingletonHungry {
//提供一个静态属性,只要类初始化,立即加载对象,不管你需不需要使用,所以被称为饿汉式,上来就要吃饭。
private static SingletonHungry instance = new SingletonHungry();
//私有化构造器
private SingletonHungry(){
}
//提供一个可用于被外部访问的方法
public static SingletonHungry getInstance(){
return instance;
}
}
这样一个饿汉式就写完了,很简单,调用试试
/**
* 这是调用者
*/
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//测试饿汉式单例模式
SingletonHungry s1 = SingletonHungry.getInstance();
SingletonHungry s2 = SingletonHungry.getInstance();
Log.d("TAG","s1对象:"+s1+"\n"+"s2对象:"+s2);
}
}
这样就能保证对象的唯一性,无论整个程序调用多少次,在哪里调,得到的都是同一个对象,缺点也很明显,就是类已初始化就开始加载对象,也不管你是否使用,在一定的业务开发逻辑上可能无法满足,比如,我想等到我使用的时候,再去加载对象,这下可以选择使用懒汉模式。
懒汉模式(特点:调用效率低,线程安全,但是可以懒加载)
/**
* 这是一个懒汉模式
*
* synchronized一定要使用,如果不使用,就有可能造成创建多个对象的情况,
* 比如线程A刚好挂起,B就直接new,B挂起,A又从挂起的地方直接new一个新对象,
* 就会造成建立多个对象的问题
*/
public class SingletonLazy {
//提供一个静态属性,类初始化的时候先不要加载对象,
//等需要的时候再去加载,所以先赋null,
//因此称为懒汉式,你叫我吃饭我才吃,要不我就不吃!
private static SingletonLazy instance = null;
//私有化构造器
private SingletonLazy() {
}
//提供一个可用于被外部访问的方法
public static synchronized SingletonLazy getInstance() {
if (instance == null) {
instance = new SingletonLazy();
}
return instance;
}
}
和饿汉式写法区别不大,只是不要类一初始化就把对象加载,然后多加一个判断去判断对象是否已经存在,如果为空就加载对象,否则直接把对象返回。
测试效果:
/**
* 这是调用者
*/
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//测试懒汉式单例模式
SingletonLazy s1 = SingletonLazy.getInstance();
SingletonLazy s2 = SingletonLazy.getInstance();
Log.d("TAG","s1对象:"+s1+"\n"+"s2对象:"+s2);
}
}
一样能保证对象的唯一性。
这两种方式都有很明显的缺点,比如饿汉模式不可以懒加载,懒汉模式调用效率低,但是我需要的是调用效率高、线程安全、还可以懒加载,这可以使用静态内部类的方式去实现。
静态内部类实现方式(调用效率高,线程安全,可以懒加载)
/**
* 这是一个以静态内部类实现的单例模式
*/
public class SingletonSic {
private static class SingletonClassInstance{
private static SingletonSic instance = new SingletonSic();
}
//构造器私有化
private SingletonSic(){
}
//提供一个可用于被外部访问的方法
public static SingletonSic getInstance(){
return SingletonClassInstance.instance;
}
}
调用测试:
/**
* 这是调用者
*/
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//测试静态内部类单例模式
SingletonSic s1 = SingletonSic.getInstance();
SingletonSic s2 = SingletonSic.getInstance();
Log.d("TAG","s1对象:"+s1+"\n"+"s2对象:"+s2);
}
}
一样可以保证对象的唯一性。
以上三种方式都是可以通过反射和反序列化去破解。
反射破解饿汉式测试:
/**
* 这是调用者
*/
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//通过反射破解单例模式
try {
Class clash = (Class) Class.forName("com.hdc.test.SingletonHungry");
Constructor c = clash.getDeclaredConstructor(null);
c.setAccessible(true);
SingletonHungry s1 = c.newInstance();
SingletonHungry s2 = c.newInstance();
Log.d("TAG","s1对象:"+s1+"\n"+"s2对象:"+s2);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
结果:对象不再是唯一性。
怎么预防(修改下构造器,当尝试第二次初始化对象的时候,直接抛出异常):
/**
* 这是一个饿汉式单例模式
*/
public class SingletonHungry {
//提供一个静态属性,只要类初始化,立即加载对象,不管你需不需要使用,所以被称为饿汉式,上来就要吃饭。
private static SingletonHungry instance = new SingletonHungry();
private static boolean flag = false;
//私有化构造器
private SingletonHungry(){
synchronized(SingletonHungry.class)
{
if(!flag)
{
flag = true;
} else
{
throw new RuntimeException("想侵犯我?给你抛个异常!");
}
}
}
//提供一个可用于被外部访问的方法
public static SingletonHungry getInstance(){
return instance;
}
}
通过反序列化破解饿汉式:
如果需要反序列化破解先实现Serializable或者接口,比如这样:
/**
* 这是一个饿汉式单例模式
*/
public class SingletonHungry implements Serializable {
//提供一个静态属性,只要类初始化,立即加载对象,不管你需不需要使用,所以被称为饿汉式,上来就要吃饭。
private static SingletonHungry instance = new SingletonHungry();
//私有化构造器
private SingletonHungry(){
}
//提供一个可用于被外部访问的方法
public static SingletonHungry getInstance(){
return instance;
}
}
实现了Serializable 接口直接破解:
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
/**
* 这是调用者
*/
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
SingletonHungry s1 = SingletonHungry.getInstance();
File file = null;
try {
//写入
file = new File(this.getFilesDir().getPath() + "test.txt");
ObjectOutputStream oos = new ObjectOutputStream(new FileOutputStream(file));
oos.writeObject(s1);
oos.close();
//读出
ObjectInputStream ois = new ObjectInputStream(new FileInputStream(file));
SingletonHungry s2 = (SingletonHungry) ois.readObject();
ois.close();
Log.d("TAG","s1 = " + s1 + "\ns2 = " + s2);
} catch (IOException | ClassNotFoundException e) {
e.printStackTrace();
}
}
}
结果:对象不再是唯一性。
怎么预防(在类里加个readResolve就好,其它都不需要修改):
import java.io.ObjectStreamException;
import java.io.Serializable;
/**
* 这是一个饿汉式单例模式
*/
public class SingletonHungry implements Serializable {
//提供一个静态属性,只要类初始化,立即加载对象,不管你需不需要使用,所以被称为饿汉式,上来就要吃饭。
private static SingletonHungry instance = new SingletonHungry();
//私有化构造器
private SingletonHungry(){
}
//提供一个可用于被外部访问的方法
public static SingletonHungry getInstance(){
return instance;
}
//防止反序列化访问
private Object readResolve() throws ObjectStreamException{
return instance;
}
}
测试:
这样就能防止反序列化漏洞,保证了对象唯一性。
前面几种方式,虽然私有化构造器了,但是依然可以通过反射和反序列化去访问,但是枚举呢?枚举本身就是一个单例,而且有JVM从根本上提供保障,避免反射和序列化的漏铜,也是写法最简单的一种,如下:
枚举(调用效率高,线程安全,不可用懒加载)
/**
* 这是一个枚举,由JVM从根本提供保障,避免反射和反序列化。
*/
public enum SingtonEnum {
//定义一个静态常量,
INSTANCE
}
调用测试
/**
* 这是调用者
*/
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//测试枚举,直接获取两次枚举对象,看是否相等
System.out.println(SingtonEnum.INSTANCE == SingtonEnum.INSTANCE);
}
}
结果为true,效果是一样的,但是饿汉式,懒汉式,静态内部类式,枚举式效率都怎么样呢?测试一下看看。
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
/**
* 这是调用者
*/
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//饿汉式效率测试
long startTime = System.currentTimeMillis();
int threadNume = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNume);
for (int i = 0; i < threadNume; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
Object o = SingletonHungry.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
Log.d("TAG", "总耗时:" + (endTime - startTime));
}
}
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
/**
* 这是调用者
*/
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//懒汉式效率测试
long startTime = System.currentTimeMillis();
int threadNume = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNume);
for (int i = 0; i < threadNume; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
Object o = SingletonLazy.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
Log.d("TAG", "总耗时:" + (endTime - startTime));
}
}
懒汉式明显比饿汉式效率低很多
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
/**
* 这是调用者
*/
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//静态内部类式效率测试
long startTime = System.currentTimeMillis();
int threadNume = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNume);
for (int i = 0; i < threadNume; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
Object o = SingletonSic.getInstance();
}
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
Log.d("TAG", "总耗时:" + (endTime - startTime));
}
}
静态内部类式跟饿汉式差不多
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.v4.app.FragmentActivity;
import android.util.Log;
import java.util.concurrent.CountDownLatch;
/**
* 这是调用者
*/
public class MainActivity extends FragmentActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//枚举式效率测试
long startTime = System.currentTimeMillis();
int threadNume = 10;
final CountDownLatch countDownLatch = new CountDownLatch(threadNume);
for (int i = 0; i < threadNume; i++) {
new Thread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < 1000000; i++) {
Object o = SingtonEnum.INSTANCE;
}
countDownLatch.countDown();
}
}).start();
}
try {
countDownLatch.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
long endTime = System.currentTimeMillis();
Log.d("TAG", "总耗时:" + (endTime - startTime));
}
}
枚举式跟饿汉式差不多
测试完成了,开发中具体怎么选:
如果占用资源少,不需要延时加载,枚举式好于饿汉式。
如果占用资源大,需要延时加载,静态内部类式好于懒汉式。
已完成测试,有不对的地方欢迎指出,感恩。