java设计模式之单例模式完整版
单例模式的核心就是只能有一个实例
所以没什么好说的 构造方法不能公开 ,不然谁都能创建实例对象了---直接private
第一种平平无奇
直接再类加载的时候初始化对象
好处就很明显,首先简单,不涉及并发安全问题,因为类只加载一次,而且实例是再类加载的时候就被加载了,不管你用没用,这就是坏处,但是我觉的无伤大雅,类总是要用的 不然你加载他干嘛
public class Single01 {
private static final Single01 INSTANCE=new Single01();
/**
* 私有化
*/
private Single01(){}
public static Single01 getInstance(){
return INSTANCE;
}
}
当然这样也是可以的,利用static代码块,再程序初始化的时候加载。
public class Single01 {
private static final Single01 INSTANCE;
static {
INSTANCE=new Single01();
}
/**
* 私有化
*/
private Single01(){}
public static Single01 getInstance(){
return INSTANCE;
}
}
第二中开始着手解决问题我不想直接加载类
当调用的时候再加载就好了....
public class Single02 {
private Single02(){}
private static Single02 INSTANCE;
public static Single02 getINSTANCE() {
if (INSTANCE==null){
INSTANCE=new Single02();
return INSTANCE;
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i=0;i<100;i++){
new Thread(()->{
System.out.println(Single02.getINSTANCE().hashCode());
}).start();
}
}
}
当我运行程序时,好像结构也没啥问题,是因为线程执行的太快了
但是实际上问题大了,因为线程可以抢在if后同时生产实例,那么问题就很麻烦了, 举个例子。
public class Single02 {
private Single02(){}
private static Single02 INSTANCE;
public static Single02 getINSTANCE() {
if (INSTANCE==null){
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
INSTANCE=new Single02();
return INSTANCE;
}
return INSTANCE;
}
public static void main(String[] args) {
for (int i=0;i<100;i++){
new Thread(()->{
System.out.println(Single02.getINSTANCE().hashCode());
}).start();
}
}
}
再原来的基础上,我继续加了sleep 为了让更多的线程进来,有机可趁。结果
就没几个一样的哎,还不如第一个呢 ,我比较推荐第一个。
但这个是可以改进的加个锁嘛
public synchronized static Single02 getINSTANCE() {
if (INSTANCE==null){
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
INSTANCE=new Single02();
return INSTANCE;
}
return INSTANCE;
}
加了锁以后 结果就一样了
但是效率太低了,整个太拉跨了。可以尝试再代码块上加锁。
public static Single02 getINSTANCE() {
if (INSTANCE==null){
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
synchronized (Single02.class){
INSTANCE=new Single02();
return INSTANCE;
// if (INSTANCE==null){
// INSTANCE=new Single02();
// return INSTANCE;
// }
}
// try {
// Thread.sleep(1000);
// }catch (Exception e){
// e.printStackTrace();
// }
}
return INSTANCE;
}
这个代码输出是有问题的,因为锁至少保证一次只运行一个,但是我们的要求是创建实例代码只执行一次,所以要加二次判断,不然的话,还是出问题
现在加上二次判断
public static Single02 getINSTANCE() {
if (INSTANCE==null){
try {
Thread.sleep(1000);
}catch (Exception e){
e.printStackTrace();
}
synchronized (Single02.class){
if (INSTANCE==null){
INSTANCE=new Single02();
return INSTANCE;
}
}
}
return INSTANCE;
}
可能你会觉得第一次判断没用用,但实际上很有用,可以筛选掉大部分的线程,不然去拿锁, 还有一点要提醒的是变量INSTANCE要加上volatile 不然又可能编译本地代码的时候会乱序,可以回执行出两个实例。
还有一种比较完美的方法
利用内部类完美解决懒加载,因为静态内部类使用他的时候在加载
public class Single03 {
private Single03(){}
static class SingleHolder{
private static final Single03 INSTANCE=new Single03();
public static Single03 getInstance(){
return INSTANCE;
}
}
那么完美就可以在内部类里面做第一种的事情就好了。
还有一种方法就比较有趣了
public enum Single04 {
INSTANCE
}
对 你没看错 就是一行代码就好了, 且这个还是private static final的,且不能被反序列化,不会被反射搞到手,因为枚举类没用构造方法,且他还帮你实例化了 你说爽不爽
转载自:https://juejin.cn/post/6977739186925535240