所谓单例,指的就是单实例,有且仅有一个类实例,该类提供了一个全局访问点供外部获取该实例,这个单例不应该由人来控制,而应该由代码来限制,强制单例。
单例有其独有的使用场景,一般是对于那些业务逻辑上限定不能多例只能单例的情况,例如:类似于计数器之类的存在,一般都需要使用一个实例来进行记录,若多例计数则会不准确。
/**
* 饿汉式单例
*/
public class Hungry {
/**
* 很明显,一进来就加载对象,存在浪费空间
* 这时候我们需要一个用的时候才加载对象,不用不加载
* 这个时候可以使用懒汉式单例
*/
private Hungry(){
}
public final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
/**
* 懒汉式单例
*/
public class LazyMan {
/**
* 这种方式,单线程是OK的,多线程就不行了
*/
private LazyMan(){
System.err.println(Thread.currentThread().getName()+"-----");
}
public static LazyMan lazyman;
public static LazyMan getInstance(){
if(lazyman==null){
lazyman = new LazyMan();
}
return lazyman;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
可以看到,在多线程的情况下就不行了
使用双重检查锁模式
public static LazyMan getInstance(){
if(lazyman==null){
synchronized (LazyMan.class){
/**
* 但是也有一个问题,就是不是一个原子性操作
* 初始化对象正常顺序
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 那么这个过程中可能会产生指令重排的现象
* 132 B
* 此时LazyMan还没有完成构造 可以是用volatile关键字来解决
* volatile:可以防止指令重排
*
*/
if(lazyman==null){
lazyman = new LazyMan();
}
}
}
return lazyman;
}
使用volatile关键字
/**
* 懒汉式单例
*/
public class LazyMan {
/**
* 这种方式,单线程是OK的,多线程就不行了
*/
private LazyMan(){
System.err.println(Thread.currentThread().getName()+"-----");
}
public volatile static LazyMan lazyman;
public static LazyMan getInstance(){
if(lazyman==null){
synchronized (LazyMan.class){
/**
* 但是也有一个问题,就是不是一个原子性操作
* 初始化对象正常顺序
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 那么这个过程中可能会产生指令重排的现象
* 132 B
* 此时LazyMan还没有完成构造 可以是用volatile关键字来解决
* volatile:可以防止指令重排
*
*/
if(lazyman==null){
lazyman = new LazyMan();
}
}
}
return lazyman;
}
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
OK,没问题。
但是我们Java学过一个特别暴力的获取对象的方式那就是反射!!!!
使用反射以上方法也不安全了!!!
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance2 = declaredConstructor.newInstance();
System.err.println(instance1);
System.err.println(instance2);
}
可以看到两个对象不一致!完了,又被破坏了
在构造方法中判断一下对象是否已经存在
private LazyMan(){
synchronized (LazyMan.class){
if(lazyman!=null){
throw new RuntimeException("不要试图使用反射进行破环!");
}
}
}
但是,之前有一个对象是我们手动创建的,那如果两个对象都使用反射进行创建呢?
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
// LazyMan instance1 = LazyMan.getInstance();
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
LazyMan instance1 = declaredConstructor.newInstance();
LazyMan instance2 = declaredConstructor.newInstance();
System.err.println(instance1);
System.err.println(instance2);
}
完了,又被破解~~~
可以定义一个布尔类型的变量进行判断
private static boolean isinstance =false;
private LazyMan(){
synchronized (LazyMan.class){
if(isinstance==false){
isinstance=true;
}else{
throw new RuntimeException("不要试图使用反射进行破坏!");
}
}
}
ok,成功,以上方法已经可以解决一大部分安全问题了
注意:如果你定义的变量被暴露了,那么还是可以通过反射进行破坏
那么这个万恶的反射到底如何才可以不被破坏呢?查看反射中的newInstance()源码
通过源码可知,如果是个枚举类那么反射就破解不了
枚举类
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class, int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance();
System.err.println(instance1);
System.err.println(enumSingle);
}
}
果然跟源码中的一样,枚举类你不能通过反射破坏
/**
* 静态内部类
*/
public class Hodel {
private Hodel(){
}
public static Hodel getInstance(){
return InnerClass.hodel;
}
public static class InnerClass{
private final static Hodel hodel = new Hodel();
}
}