单例模式
特点:枚举可以避免单例模式被破坏,构造器私有
饿汉模式
在类装载的时候就完成实例化,上来就把对象创建,用的时候直接使用,但是启动慢,就算不用也创建了,浪费内存。避免了线程同步问题。
public class Hungry {
//饿汉模式
private Hungry(){
}
private static final Hungry HUNGRY = new Hungry();
public Hungry getInstance(){
return HUNGRY;
}
}
懒汉模式
用的时候才会创建
public class Lazy {
//懒汉模式,但是这种方法只适用在单线程,并发不可以
private Lazy(){
}
private static Lazy lazy;
public Lazy getInstance(){
if(lazy==null){
lazy = new Lazy();
}
return lazy;
}
}
DCL双重检测锁懒汉模式,解决并发问题
synchronized保证原子性,volatile保证有序性
public class Lazy {
private Lazy(){
}
private volatile static Lazy lazy;
//第一层保证线程安全,提高效率
public static Lazy getInstance(){
if(lazy==null){
synchronized (Lazy.class){
if(lazy==null){
lazy = new Lazy();
//new包含3步
/*
1.分配内存空间
2.执行构造方法,初始化对象
3.把这个对象指向这个空间
*/
}
}
}
return lazy;
}
}
new的机器码不止一步。也就是不是原子性操作,由于指令的重排序,可能在未完成实例化的时候,对象已经不是null,就是顺序为132,这时候另一个线程就有可能得到未完成实例化的对象。
所以需要在private static Lazy lazy;
加上volatile关键字,就会保证指令不会重排了
静态内部类法
public class Holder {
private Holder(){
}
private static class InnerClass{
private static final Holder HOLDER = new Holder();
}
public Holder getInstance(){
return InnerClass.HOLDER;
}
}
但是这些都是不安全的,可以通过反射来进行单例的破坏
public class Lazy {
private Lazy() {
}
private volatile static Lazy lazy;
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Lazy lazy = Lazy.getInstance();
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Lazy lazy1 = declaredConstructor.newInstance();
System.out.println(lazy);
System.out.println(lazy1);
}
}
/*结果
com.jin.test.Lazy@5cad8086
com.jin.test.Lazy@6e0be858
明显看出是两个对象
*/
反射破坏的解决方案
由于是使用反射进行无参构造来创建对象,所以修改无参构造
public class Lazy {
private Lazy() {
synchronized (Lazy.class){
if(lazy!=null){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
private volatile static Lazy lazy;
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
Lazy lazy = Lazy.getInstance();
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Lazy lazy1 = declaredConstructor.newInstance();
System.out.println(lazy);
System.out.println(lazy1);
}
}
但是会出现另一种破坏方式,当不使用单例模式来进行创建对象,只使用反射来创建对象还是会出现两个对象的问题
public class Lazy {
private Lazy() {
synchronized (Lazy.class){
if(lazy!=null){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
}
private volatile static Lazy lazy;
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// Lazy lazy = Lazy.getInstance();
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Lazy lazy1 = declaredConstructor.newInstance();
Lazy lazy2 = declaredConstructor.newInstance();
System.out.println(lazy1);
System.out.println(lazy2);
}
}
/*结果
com.jin.test.Lazy@5cad8086
com.jin.test.Lazy@6e0be858
*/
这里使用一种红绿灯法,来重新再无参构造中判断,标志位的字段可以加密,什么都可以,除了反编译,无法找到该字段
public class Lazy {
private static boolean fdskfjskd=false;
private Lazy() {
synchronized (Lazy.class){
if(fdskfjskd==false){
fdskfjskd=true;
}
else
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
private volatile static Lazy lazy;
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
// Lazy lazy = Lazy.getInstance();
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
Lazy lazy1 = declaredConstructor.newInstance();
Lazy lazy2 = declaredConstructor.newInstance();
System.out.println(lazy1);
System.out.println(lazy2);
}
}
但是若该字段被破译,仍旧可以被用反射破坏
public class Lazy {
private static boolean fdskfjskd=false;
private Lazy() {
synchronized (Lazy.class){
if(fdskfjskd==false){
fdskfjskd=true;
}
else
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
private volatile static Lazy lazy;
public static Lazy getInstance() {
if (lazy == null) {
synchronized (Lazy.class) {
if (lazy == null) {
lazy = new Lazy();
}
}
}
return lazy;
}
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
// Lazy lazy = Lazy.getInstance();
Constructor<Lazy> declaredConstructor = Lazy.class.getDeclaredConstructor(null);
//得到该字段
Field fdskfjskd = Lazy.class.getDeclaredField("fdskfjskd");
declaredConstructor.setAccessible(true);
Lazy lazy1 = declaredConstructor.newInstance();
fdskfjskd.set(lazy1,false);
Lazy lazy2 = declaredConstructor.newInstance();
System.out.println(lazy1);
System.out.println(lazy2);
}
}
/*
结果
com.jin.test.Lazy@6e0be858
com.jin.test.Lazy@61bbe9ba
仍旧被破坏
*/
通过阅读newInstance()的源码,得出不能使用反射破坏枚举,jdk1.5出来的
枚举本身也是一个class类
枚举方法
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class test{
public static void main(String[]args) throws Exception {
EnumSingle instance = EnumSingle.INSTANCE.getInstance();
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(enumSingle);
}
}
/*
结果
Exception in thread "main" java.lang.NoSuchMethodException:
com.jin.test.EnumSingle.<init>()
*/
用反射创建对象后,并没有出现源码的Cannot reflectively create enum objects
而是Exception in thread "main" java.lang.NoSuchMethodException:
通过反编译软件得出枚举类型的代码
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(EnumSingle, name);
}
private EnumSingle(String s, int i)
{
super(s, i);
}
public EnumSingle getInstance()
{
return INSTANCE;
}
public static final EnumSingle INSTANCE;
private static final EnumSingle $VALUES[];
static
{
INSTANCE = new EnumSingle("INSTANCE", 0);
$VALUES = (new EnumSingle[] {
INSTANCE
});
}
}
可以看出反编译后的代码,并没有无参构造,而是有参构造,修改代码
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class test{
public static void main(String[]args) throws Exception {
EnumSingle instance = EnumSingle.INSTANCE.getInstance();
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
EnumSingle enumSingle = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(enumSingle);
}
}
/*
结果
Exception in thread "main" java.lang.IllegalArgumentException:
Cannot reflectively create enum objects
*/
得到预想的结果
通过枚举反编译代码看出,枚举类型是通过静态代码块实现对象的创建,是安全的,但是静态代码块可能会浪费内存(待议)。
我们可以通过EasySingleton.INSTANCE.工具方法() 的方式来访问实例,这比调用getInstance()方法简单多了。创建枚举默认就是线程安全的,所以不需要担心double checked locking,而且还能防止反序列化和拷贝导致重新创建新的对象。