单例模式
1)饿汉式
/**
* 饿汉式单例
*/
public class Hungry {
/**
* 可能会浪费空间
*/
private Hungry(){
}
private final static Hungry hungry = new Hungry();
public static Hungry getInstance(){
return hungry;
}
}
2)DCL懒汉式
懒汉式最基础模型:
class LazyMan{
private static LazyMan lazyMan;
private LazyMan(){}
public static LazyMan getInstrance(){
if(lazyMan==null){
lazyMan=new LazyMan();
}
return lazyMan;
}
}
public class Test{
public static void main(String[] args){
LazyMan lazyMan=LazyMan.getInstrance();
}
}
但是,在多线程情况下会出现问题,会出现多个线程同时抢占的情况,因此加入synchronized锁住类就不会出现这种情况。
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}
//双重检验
public static LazyMan getInstance(){
if (lazyMan == null) { //加锁前都有可能抢占所以做2次检测
synchronized (LazyMan.class) {
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();
}
}
}
但是在这样处理后,我们可以发现 lazyMan = new LazyMan();是个非原子性操作,所以加上volatile字段来解决这个问题。
public class LazyMan {
private volatile static LazyMan lazyMan;
private LazyMan(){
System.out.println(Thread.currentThread().getName()+"ok");
}
//双重检验
public static LazyMan getInstance(){
if (lazyMan == null) {
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan(); //非原子性
/**
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 就有可能出现指令重排问题
* 比如执行的顺序是1 3 2 等
* 我们就可以添加volatile保证指令重排问题
*/
}
}
return lazyMan;
}
}
//多线程并发
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(()->{
LazyMan.getInstance();
}).start();
}
}
}
单例不安全, 因为反射
但是,我们仍然可以通过反射来破解,通过反射构造将私有的构造方法变成公有从而来创建新类。
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan(){ //通过锁住,抛异常来防止反射构造类
synchronized (LazyMan.class){
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
public static LazyMan getIjnstance(){
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
}
public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException {
LazyMan instance = LazyMan.getInstance(); //通过反射创建2个类
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredAnnotation(null);
constructor.setAccessible(true); //公开私有制方法
LazyMan instance2 = constructor.newInstance();
}
}
但是,要是不创建对象,2个对象都由反射创建,则又可以破坏单例模式。
public class LazyMan {
private static LazyMan lazyMan;
private LazyMan(){
synchronized (LazyMan.class){//由于不走类创建,锁住的不是同一个。如上例,一个走这个方法,一个不走,所以能锁住。
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
public static LazyMan getInstance(){
synchronized (LazyMan.class) {
if (lazyMan == null) {
lazyMan = new LazyMan();
}
return lazyMan;
}
}
public static void main(String[] args) throws InvocationTargetException, InstantiationException, IllegalAccessException {
//LazyMan instance = LazyMan.getInstance(); //通过反射创建2个类
Constructor<LazyMan> constructor = LazyMan.class.getDeclaredAnnotation(null);
constructor.setAccessible(true); //公开私有制方法
LazyMan instance1 = constructor.newInstance(); //2个对象都由反射创建
LazyMan instance2 = constructor.newInstance();
}
}
所以,我们可以通过创建一个内部的参数,来防止多个反射,,比如key = false,一个通过后将其置为true,则不能再创建。但是我们还是能够发现问题,要是通过反编译得到这个内部参数,将其重新赋值仍然可以被反射破解。
//懒汉式单例模式(最终版本)
public class LazyMan {
private static boolean key = false;
private LazyMan(){
synchronized (LazyMan.class){
if (key==false){
key=true;
}
else{
throw new RuntimeException("不要试图使用反射破坏异常");
}
}
System.out.println(Thread.currentThread().getName()+" ok");
}
private volatile static LazyMan lazyMan;
//双重检测锁模式 简称DCL懒汉式
public static LazyMan getInstance(){
//需要加锁
if(lazyMan==null){
synchronized (LazyMan.class){
if(lazyMan==null){
lazyMan=new LazyMan();
/**
* 1、分配内存空间
* 2、执行构造方法,初始化对象
* 3、把这个对象指向这个空间
*
* 就有可能出现指令重排问题
* 比如执行的顺序是1 3 2 等
* 我们就可以添加volatile保证指令重排问题
*/
}
}
}
return lazyMan;
}
//单线程下 是ok的
//但是如果是并发的
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException, NoSuchFieldException {
//Java中有反射
// LazyMan instance = LazyMan.getInstance();
Field key = LazyMan.class.getDeclaredField("key");
key.setAccessible(true);
Constructor<LazyMan> declaredConstructor = LazyMan.class.getDeclaredConstructor(null);
declaredConstructor.setAccessible(true); //无视了私有的构造器
LazyMan lazyMan1 = declaredConstructor.newInstance();
key.set(lazyMan1,false);
LazyMan instance = declaredConstructor.newInstance();
System.out.println(instance);
System.out.println(lazyMan1);
System.out.println(instance == lazyMan1);
}
}
3)静态内部类
//静态内部类
public class Holder {
private Holder(){
}
public static Holder getInstance(){
return InnerClass.holder;
}
public static class InnerClass{
private static final Holder holder = new Holder();
}
}
4)枚举
//enum 是什么? enum本身就是一个Class 类
public enum EnumSingle {
INSTANCE;
public EnumSingle getInstance(){
return INSTANCE;
}
}
class Test{
public static void main(String[] args) throws NoSuchMethodException, IllegalAccessException, InvocationTargetException, InstantiationException {
EnumSingle instance1 = EnumSingle.INSTANCE;
Constructor<EnumSingle> declaredConstructor = EnumSingle.class.getDeclaredConstructor(String.class,int.class);
declaredConstructor.setAccessible(true);
//java.lang.NoSuchMethodException: com.ogj.single.EnumSingle.<init>()
EnumSingle instance2 = declaredConstructor.newInstance();
System.out.println(instance1);
System.out.println(instance2);
}
}
使用枚举,我们就可以防止反射破坏了。
枚举类型的最终反编译源码:
public final class EnumSingle extends Enum
{
public static EnumSingle[] values()
{
return (EnumSingle[])$VALUES.clone();
}
public static EnumSingle valueOf(String name)
{
return (EnumSingle)Enum.valueOf(com/ogj/single/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
});
}
}