第一、什么是单例模式
单例模式确保一个类只有一个实例,并提供一个全局访问点,实现单例模式的方法是私有化构造函数,通过getInstance()方法实例化对象,并返回这个实例
保证在JVM中只有一个实例 幂等
JVM中如何保证实例的幂等问题 保证唯一性
饿汉、懒汉 双重检验
第二、单例模式的特点
1、单例类只有一个实例
2、共享资源,全局使用
3、节省创建时间,提高性能
缺点:可能存在线程不安全的问题
第三、单例模式的实现
1、饿汉式
该模式的特点是类加载时没有生成单例,只有当第一次调用 getlnstance 方法时才去创建这个单例。
public class HungrySingleTon {
/**
* 饿汉式 优点:先天性线程是安全的,当类初始化的 就会创建该对象 缺点:如果饿汉式使用过多,可能会影响项目启动的效率问题。
*/
private static HungrySingleTon hungrySingleTon = new HungrySingleTon();
//private 避免类在外部被实例化
private HungrySingleTon(){
}
public static HungrySingleTon getIntance(){
return hungrySingleTon;
}
}
public class TestMain01 {
public static void main(String[] args) {
HungrySingleTon intance = HungrySingleTon.getIntance();
HungrySingleTon intance1 = HungrySingleTon.getIntance();
System.out.println(intance==intance1);
}
}
饿汉式单例在类创建的同时就已经创建好一个静态的对象供系统使用,以后不再改变,所以是线程安全的,可以直接用于多线程而不会出现问题。
2、懒汉式(线程不安全)
public class LazySingleton {
/**
* 线程不安全
*/
private static LazySingleton lazySingleton = null;
/**
* 防止外部实例化
*/
private LazySingleton(){
}
/**
* 在真正需要创建对象的时候使用...
* @return
*/
public static LazySingleton getInstance(){
if (lazySingleton == null) {
try {
//模拟线程不安全
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
lazySingleton = new LazySingleton();
}
return lazySingleton;
}
}
public class TestMain {
public static void main(String[] args) {
LazySingleton02 intance1 = LazySingleton02.getInstance();
LazySingleton02 intance2 = LazySingleton02.getInstance();
System.out.println(intance1==intance2);//true
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
LazySingleton02 intance3 = LazySingleton02.getInstance();
System.out.println(Thread.currentThread()+":"+intance3);
}
}).start();
}
}
}
3、懒汉式(线程安全)
public class LazySingleton01 {
private static LazySingleton01 lazySingleton01;
private LazySingleton01(){}
public static synchronized LazySingleton01 getInstance(){
if (lazySingleton01 == null) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
lazySingleton01 = new LazySingleton01();
}
return lazySingleton01;
}
}
public class TestMain {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
LazySingleton01 intance3 = LazySingleton01.getInstance();
System.out.println(Thread.currentThread()+":"+intance3);
}
}).start();
}
}
}
4、双重检验锁(DCL)
public class LazySingleton02 {
/**
* volatile 禁止重排序和 提高可见性
* 保证 instance 在所有线程中同步
*/
private static volatile LazySingleton02 lazySingleton02;
/**
* 防止外部实例化
*/
private LazySingleton02(){
}
public static LazySingleton02 getInstance(){
// 第一次判断如果没有创建对象 开始上锁...
synchronized (LazySingleton02.class){
// 当用户抢到锁,判断初始化
if (lazySingleton02 == null) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
lazySingleton02 = new LazySingleton02();
}
}
return lazySingleton02;
}
}
public class TestMain {
public static void main(String[] args) {
for (int i = 0; i < 100; i++) {
new Thread(new Runnable() {
@Override
public void run() {
LazySingleton02 intance3 = LazySingleton02.getInstance();
System.out.println(Thread.currentThread()+":"+intance3);
}
}).start();
}
}
}
5、静态内部内形式
public class LazySingleton03 {
/**
* 防止外部实例化
*/
private LazySingleton03(){}
/**
* 静态内部方式能够避免同步带来的效率问题和有能实现延迟加载
*/
public static LazySingleton03 getIntance(){
return SingletonV5Utils.lazySingleton03;
}
public static class SingletonV5Utils{
private static LazySingleton03 lazySingleton03 = new LazySingleton03();
}
}
public class TestMain {
public static void main(String[] args) {
LazySingleton03 singleton03 = LazySingleton03.getIntance();
LazySingleton03 singleton04 = LazySingleton03.getIntance();
System.out.println(singleton03==singleton04);
}
}
6、枚举形式
public enum EnumSingleton {
INSTANCE;
// 枚举能够绝对有效的防止实例化多次,和防止反射和序列化破解
public void add() {
System.out.println("add方法...");
}
}
public class TestMain {
public static void main(String[] args) throws Exception {
EnumSingleton enumSingleton = EnumSingleton.INSTANCE;
EnumSingleton enumSingleton1 = EnumSingleton.INSTANCE;
System.out.println(enumSingleton==enumSingleton1);
//这个代码是报错的,因为枚举不能被破解
Constructor<EnumSingleton> declaredConstructor = EnumSingleton.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
EnumSingleton v3 = declaredConstructor.newInstance();
System.out.println(v3==enumSingleton1);
}
}
7、使用容器管理
public class SingletonManager {
private static Map<String,Object> objectMap = new HashMap<String, Object>();
public static void registerService(String key,Object instance){
if (!objectMap.containsKey(key)) {
objectMap.put(key, instance);
}
}
public static Object getService(String key){
return objectMap.get(key);
}
}
这种使用SingletonManager 将多种单例类统一管理,在使用时根据key获取对象对应类型的对象。这种方式使得我们可以管理多种类型的单例,
并且在使用时可以通过统一的接口进行获取操作,降低了用户的使用成本,也对用户隐藏了具体实现,降低了耦合度。
8、如何防止破坏单例
虽然单例通过私有构造函数,可以实现防止程序猿初始化对象,但是还可以通过反射和序列化技术破解单例。
-
利用反射技术破解单例
// 1. 使用懒汉式创建对象
SingletonV3 instance1 = SingletonV3.getInstance();
// 2. 使用Java反射技术初始化对象 执行无参构造函数
Constructor<SingletonV3> declaredConstructor = SingletonV3.class.getDeclaredConstructor();
declaredConstructor.setAccessible(true);
SingletonV3 instance2 = declaredConstructor.newInstance();
System.out.println(instance1 == instance2);