设计模式-Singleton的8种方式2种完美无瑕.
发布于 2021-01-29 01:44
应用场景:只需要有一个实例
各种Mgr
各种Factory
单例第一步把构造方法设置成私有的,无法被new出来,要想用必须调用它的getInstance();
1. 第一种:
public class Mgr01 {
private static final Mgr01 INSTANCE = new Mgr01();
private Mgr01() {};
public static Mgr01 getInstance() { return INSTANCE;}
public void m() {System.out.println("m");}
public static void main(String[] args) {
Mgr01 m1 = Mgr01.getInstance();
Mgr01 m2 = Mgr01.getInstance();
System.out.println(m1 == m2);
}
}
运行发现永远为true因为,getInstance出来的永远是static INSTANCE;
这个比较简单,实际上在工作中想不到其它时候就用这种特简单,类加载到内存后,就实例化了一个单例,JVM保证线程安全,推荐大家使用.
唯一缺点:不管用到与否,类加载的时候完成实例化,饿汉式.
2. 第二种:
public class Mgr02 {
private static final Mgr02 INSTANCE;
static {
INSTANCE = new Mgr02();
}
private Mgr02() {};
public static Mgr02 getInstance() {
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
Mgr01 m1 = Mgr01.getInstance();
Mgr01 m2 = Mgr01.getInstance();
System.out.println(m1 == m2);
}
}
本质上和第一种没区别
3. 第三种:懒汉式加载
public class Mgr03 {
private static Mgr03 INSTANCE;
private Mgr03() {
}
public static Mgr03 getInstance() {
if(INSTANCE == null) {
// try {
// Thread.sleep(1);
// } catch (InterruptedException e) {
// // TODO Auto-generated catch block
// e.printStackTrace();
// }
INSTANCE = new Mgr03();
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=0;i<100;i++) {
new Thread(()->{
System.out.println(Mgr03.getInstance().hashCode());
}).start();
}
}
}
类加载时候没有new没有被实例化,只有在getInstance时候才实例化,称为“懒加载”,但是这里存在一个线程不安全的问题,多线程情况下,在if(INSTANCE == null)之后还没有new的时候,另外一个线程判断仍然为null,所以也进if里面了,那么就new出来两个线程,可以通过打印hashcode判断是否为同一个对象,不同对象的hashcode是不同的。由于线程执行较快,如果测试不出来可以加上sleep个1毫秒,就可以看出结果。
虽然比之前的饿汉式相比,修复了无伤大雅的初始化加载问题,但是但来了更严重的问题,随意不推荐这种。
4. 加锁synchronized
public class Mgr04 {
private static Mgr04 INSTANCE;
private Mgr04() {}
public static synchronized Mgr04 getInstance() {
if(INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
INSTANCE = new Mgr04();
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=0;i<100;i++) {
new Thread(()->{
System.out.println(Mgr04.getInstance().hashCode());
}).start();
}
}
加锁后发现虽然sleep线程,但hashcode始终一样,保证了线程的安全,但是效率下降。(synchronized 所的是对象)
5. 减小synchronized内容,试图提高效率
public class Mgr05 {
private static Mgr05 INSTANCE;
private Mgr05() {}
public static Mgr05 getInstance() {
if(INSTANCE == null) {
synchronized(Mgr05.class) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
INSTANCE = new Mgr05();
}
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=0;i<100;i++) {
new Thread(()->{
System.out.println(Mgr05.getInstance().hashCode());
}).start();
}
}
}
不在整个方法上加synchronized 只在需要的代码块加上它。
查看运行结果发现,没有保证线程的安全,出现多个hashcode值,原因在于 线程1 if(INSTANCE == null) 后还没有执行加锁,线程2 if(INSTANCE == null) 这时候实例为空也进来了,然后加锁,new 实例,释放锁后,线程1加锁new实例,导致多个实例产生。
6. 双层检查
public class Mgr06 {
private static Mgr06 INSTANCE;
private Mgr06() {
}
public static Mgr06 getInstance() {
if(INSTANCE == null) {
synchronized(Mgr06.class) {
if(INSTANCE == null) {
try {
Thread.sleep(1);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
INSTANCE = new Mgr06();
}
}
}
return INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=0;i<100;i++) {
new Thread(()->{
System.out.println(Mgr06.getInstance().hashCode());
}).start();
}
}
}
两个或者多个线程进入第6行 if(INSTANCE == null) { 里面后,先执行的线程加锁判断是否为空,实例化,释放锁;之后的线程判断instance就不为空了,所以不会再次实例化.
有人会有疑问问 第一个if(INSTANCE == null) { 有没有必要,答案是有,因为可以减少加锁的次数。
很多人认为这种很完美,但是其实真的没必要,第一种挺好,但是生活中总有一些人追求完美。所以用第6种,除此之外呢,又诞生了另外两种写法。另外两种更好。
7. 最完美的写法之一
public class Mgr07 {
private Mgr07() {
}
private static class Mgr07Holder{
private final static Mgr07 INSTANCE = new Mgr07();
}
public static Mgr07 getInstance() {
return Mgr07Holder.INSTANCE;
}
public void m() {
System.out.println("m");
}
public static void main(String[] args) {
// TODO Auto-generated method stub
for(int i=0;i<100;i++) {
new Thread(()->{
System.out.println(Mgr07.getInstance().hashCode());
}).start();
}
}
}
新增了静态内部类,一个类的静态内部类,在外层类被加载时候不会被加载。
调用getInstance时候,内部类才会被加载,不但实现了懒加载,还实现了只有一个实例。JVM保证的线程安全,虚拟机加载class时候只加载一次,所以内部类也只加载一次,所以INSTANCE也只加载一次。因为永远只有一个对象。不用加锁。
8. 完美中的完美- 万花筒写轮眼开!
public enum Mgr08 {
INSTANCE;
public void m() {}
public static void main(String[] args) {
for(int i=0;i<100;i++) {
new Thread(()->{
System.out.println(Mgr08.INSTANCE.hashCode());
}).start();
}
}
}
不仅解决线程同步,还可以防止反序列化。不止心中有剑,手中也有剑~!
本文来自网络或网友投稿,如有侵犯您的权益,请发邮件至:aisoutu@outlook.com 我们将第一时间删除。
相关素材