文章目录
单例模式
保证一个类只有一个实例。
特点:构造函数是私有的
饿汉式
饿汉式是线程安全的,因为只会装载一次,在装载的时候是不会发生并发的。
但容易产生垃圾对象,在类加载时,就完成了初始化,浪费内存。
/**
* @description
* 饿汉式
*/
public class HungryMan {
/**
* 预先定义好对象
* 饿汉式是线程安全的,因为虚拟机保证只会装载一次,在装载类的时候是不会发生并发的。
* 但如果并没有使用该类,则造成内存空间的浪费
*/
private static HungryMan hungryMan = new HungryMan();
private HungryMan(){
}
/**等到需要时直接返回*/
public static HungryMan getInstance(){
return hungryMan;
}
}
懒汉式
单线程懒汉
这种方式是最基本的实现方式,这种实现最大的问题就是不支持多线程。
/**
* @description
* 适用于单线程下的懒汉式
*/
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan(){
}
public static LazyMan getInstance(){
if (lazyMan==null){
lazyMan=new LazyMan();
}
return lazyMan;
}
}
synchronized懒汉
通过给方法加上同步锁,使得每次能够进行赋值的只有一个线程
而且对于方法加锁,使得不管怎么样,都得排队等待,效率降低。
虽然保证了线程安全,但仍然存在遭到反射破坏的可能。
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan(){
}
public static synchronized LazyMan getInstance(){
if (lazyMan==null){
lazyMan=new LazyMan();
}
return lazyMan;
}
}
双重校验懒汉式
通过锁对象的方式,提高了执行效率。
是多线程下安全的懒汉模式
虽然保证了线程安全,但仍然存在遭到反射破坏的可能
public class LazyMan {
private volatile static LazyMan lazyMan;
private LazyMan(){
}
public static LazyMan getInstance(){
if (lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
/**再执行该操作时,该操作并不是一个原子性操作*/
//1.分配内存空间
//2.初始化对象
//3.对象的引用指向这个空间
/**即,在多线程下操作,可能进行123,但也可能进行132,
* 当发生132的时候,就会出现问题。
* 当新的线程想去拿引用的时候,会出现拿到的引用里的对象还没有初始化的情况。
* 所以要拿volatile关键词,保证这个对象的一个可见性
*/
}
}
}
return lazyMan;
}
}
内部静态类之懒汉
使用内部静态类的方式,利用了 classloader 机制来保证初始化 instance 时只有一个线程。因为LazyMan加载的时候,LazyManHolder不一定会初始化,只有在调用getInstance方法的时候,才会显式的装载LazyManHolder
虽然保证了线程安全,但仍然存在遭到反射破坏的可能
public class LazyMan {
private static class LazyManHolder{
private static final LazyMan lazyMan = new LazyMan();
}
private LazyMan(){
}
public static final LazyMan getInstance(){
return LazyManHolder.lazyMan;
}
}
反射破坏
通过反射,可以重新使用构造,避开了getInstance的方法。
class test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyMan instance1 =LazyMan.getInstance();
LazyMan instance2 =constructor.newInstance();
LazyMan instance3 =constructor.newInstance();
System.out.println("类="+instance1);
System.out.println("反射1="+instance2);
System.out.println("反射2="+instance3);
}
}
简单避免反射破坏单例(但如果判断静态变量泄漏仍有可能遭到反射破坏)
在上述的代码中加入一个判断静态变量,并将构造函数改成下面这样的,就可以避免反射破坏单例。
private static boolean lazy =false;
private LazyMan(){
synchronized (LazyMan.class){
if(lazy==false){
lazy=true;
}else{
throw new RuntimeException("不要试图用反射破坏异常");
}
}
}
比如你的反射这么写,也是可以改掉静态变量的值的
class test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
Field field = LazyMan.class.getDeclaredField("lazy");
field.setAccessible(true);
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredConstructor(null);
constructor.setAccessible(true);
LazyMan instance1 =LazyMan.getInstance();
field.set("lazy",false);
LazyMan instance2 =constructor.newInstance();
field.set("lazy",false);
LazyMan instance3 =constructor.newInstance();
System.out.println("类="+instance1);
System.out.println("反射1="+instance2);
System.out.println("反射2="+instance3);
}
}
枚举类之懒汉(也可避免反射)
jdk1.5推出的枚举类,可以很简单的实现多线程下安全的懒汉式单例模式
而且其本身就避免了反射造成的破坏。
public enum LazyEnum {
LAZY_ENUM;
public LazyEnum getInstance(){
return LAZY_ENUM;
}
}
class test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
LazyEnum instance1 = LazyEnum.LAZY_ENUM;
//注意枚举类本身的构造器,是带有两个参数的
Constructor<LazyEnum> constructor = LazyEnum.class.getDeclaredConstructor(String.class,int.class);
constructor.setAccessible(true);
LazyEnum instance2 =constructor.newInstance();
LazyEnum instance3 =constructor.newInstance();
System.out.println("类="+instance1);
System.out.println("反射1="+instance2);
System.out.println("反射2="+instance3);
}
}