定义:在一个jvm中只能存在一个实例,保证对象唯一性。
应用场景:servlet、struts2、springMVC、连接池、线程池、枚举、常量。
优点:节约内存、方便管理、重复利用。
缺点:线程不安全
单例本质上分为4类,分别是懒汉式,饿汉式,注册式和ThreadLocal式:
一、饿汉模式
1.金典款:类初始化的时候会立即创建该对象(存放在方法区中,不会被垃圾回收机制回收),线程天生安全(因为final修饰的全局变量),调用效率高,但是占内存:
/**
* 优点:执行效率高,性能高,没有任何的锁
* 缺点:某些情况下,可能会造成内存浪费
*/
public class Singleton {
public static final Singleton signleton = new Singleton();
private Singleton(){
}
public static Singleton getInstance(){
return signleton;
}
}
2.装逼款:
/*
和经典款一个意思,就是专门写了个static块
*/
public class HungryStaticSingleton {
//先静态后动态
//先上,后下
//先属性后方法
private static final HungryStaticSingleton hungrySingleton;
//装个B
static {
hungrySingleton = new HungryStaticSingleton();
}
private HungryStaticSingleton(){}
public static HungryStaticSingleton getInstance(){
return hungrySingleton;
}
}
二、懒汉模式
1.经典款:类初始化的时候不会创建该对象,只有当需要使用该对象的时候才会创建,具有懒加载功能,但是线程不安全需要自己加锁,所以效率低,会阻塞、等待:
/**
* 优点:节省了内存,线程安全
* 缺点:性能低
*/
public class Singleton {
private static Singleton signleton;
private Singleton(){
}
public static synchronized Singleton getInstance(){
if(null == signleton) {
signleton = new Singleton();
}
return signleton;
}
}
2.静态内部类:
/*
优点:写法优雅,利用了Java本身语法特点,性能高,避免了内存浪费,不能被反射破坏
缺点:不优雅
*/
class Singleton {
private Singleton() {
if (SingletonInstance.singleton != null) {
throw new RuntimeException("不允许非法访问");
}
}
public static Singleton getInstance() {
return SingletonInstance.singleton;
}
private static class SingletonInstance {
public static final Singleton singleton = new Singleton();
}
}
3.双重检验锁:因为jvm本质重排序,可能会导致多次初始化,所以要加上volatile关键字:
/**
* 优点:性能高了,线程安全了
* 缺点:可读性难度加大,不够优雅
*/
public class Singleton {
public volatile static Singleton signleton;
private Singleton(){
}
public static Singleton getInstance(){
if(signleton == null){
synchronized (Singleton.class){
if (signleton == null){
signleton = new Singleton();
}
}
}
return signleton;
}
}
三:注册式
1.枚举单例:实现简单,调用效率高,枚举本身就是单例:
public enum EnumSingleton {
INSTANCE;
public static EnumSingleton getInstance(){return INSTANCE;}
}
2.容器单例
public class ContainerSingleton {
private ContainerSingleton(){}
private static Map<String,Object> ioc = new ConcurrentHashMap<String, Object>();
public static Object getInstance(String className){
Object instance = null;
if(!ioc.containsKey(className)){
try {
synchronized (ContainerSingleton .class){
if(!ioc.containsKey(className)){
instance = Class.forName(className).newInstance();
ioc.put(className, instance);
}
}catch (Exception e){
e.printStackTrace();
}
return instance;
}else{
return ioc.get(className);
}
}
}
四:ThreadLocal式
/**
* 在数据源切换的时候很有用
*/
public class ThreadLocalSingleton {
private static final ThreadLocal<ThreadLocalSingleton> threadLocaLInstance =
new ThreadLocal<ThreadLocalSingleton>(){
@Override
protected ThreadLocalSingleton initialValue() {
return new ThreadLocalSingleton();
}
};
private ThreadLocalSingleton(){}
public static ThreadLocalSingleton getInstance(){
return threadLocaLInstance.get();
}
}
单例模式的核心思想就是控制创建实例对象,通过将构造方法私有化来防止多次创建对象的操作,但是有三种可能破坏单例,第一种就是通过反射,第二种是序列化与反序列化,第三种就是多线程。
防止反射破坏单例:通过反射机制来访问私有构造器,那么我们可以在私有构造器上加一个判断,如果对象已经被创建则抛出异常信息(或者其他处理),记得加锁,保证只能有一个访问对象。代码如下:
Class Sigleton{
private static boolean FLAG = false;
private Sigleton(){
synchronied(Sigleton.Class){
if(!FLAG){
FLAG = !FLAG;
//创建对象
}ELSE{
throw new RuntimeException("该对象是单例,不允许重复创建“);
}
}
}
}
也可以使用枚举单例来解决这个问题,因为底层代码已经做了判断,如果是枚举类型,则不允许使用反射来创建枚举对象:
//在Constructor类中
if ((clazz.getModifiers() & Modifier.ENUM) != 0)
throw new IllegalArgumentException("Cannot reflectively create enum objects");
防止序列化、反序列化破坏单例:
public class SeriableSingleton implements Serializable {
//序列化
//把内存中对象的状态转换为字节码的形式
//把字节码通过IO输出流,写到磁盘上
//永久保存下来,持久化
//反序列化
//将持久化的字节码内容,通过IO输入流读到内存中来
//转化成一个Java对象
public final static SeriableSingleton INSTANCE = new SeriableSingleton();
private SeriableSingleton(){}
public static SeriableSingleton getInstance(){
return INSTANCE;
}
//只需要加上这个方法即可,主要是重写了这个方法,如果没有这个方法就会去调用newInstance()方法
//底层源码:
/*Object obj;
try {
obj = desc.isInstantiable() ? desc.newInstance() : null;
} catch (Exception ex) {
throw (IOException) new InvalidClassException(
desc.forClass().getName(),
"unable to create instance").initCause(ex);
}
*/
private Object readResolve(){ return INSTANCE;}
}
看了《设计模式之禅》发现了单例模式的一种扩展形式,可以限制指定生成对象的数量,个人理解是不是有点像“池”的概念,比如数据库连接池设置的最大连接数,扩展代码如下:
public class Singleton {
//定义最多能产生的实例数量
private static int maxNumOfSingleton = 2;
//每个实例都有名字,使用一个ArrayList来容纳,每个对象的私有属性
private static ArrayList<String> nameList = new ArrayList<String>();
//定义一个列表,容纳所有的实例
private static ArrayList<Singleton> emperorList = new ArrayList<Singleton>();
//当前实例序列号
private static int countNumOfSingleton = 0;
//产生所有的对象
static {
for (int i = 0; i < maxNumOfSingleton; i++) {
emperorList.add(new Singleton( (i + 1) + ""));
}
}
private Singleton(String name){
nameList.add(name);
}
public static Singleton getInstance(){
Random random = new Random();
//随机获取一个实例
countNumOfSingleton = random.nextInt(maxNumOfSingleton);
return emperorList.get(countNumOfSingleton);
}
public static void say() {
System.out.println(nameList.get(countNumOfSingleton));
}
}
这里可以随机指定一个实例进行处理,当然“池”的实现肯定比这个要复杂的多。