单例模式是设计模式之一
设计模式,就像古代打仗,我们都听过孙子兵法,把计谋概括下来弄成一种模式,形成一种套路。
软件开发中也有很多场景,多数类似的问题场景,解决方案就形成固定的模式,单例模式就是其中一种
单例模式就是保证在某个类中只存在唯一一份实例,不会创建出多个实例
单例模式实现方式有很多,最常见的就是饿汉与懒汉两种模式,区别就是创建实例的时机不同
饿汉模式
饿汉模式是在类加载的时候创建
class Singleton{
private static Singleton singleton=new Singleton();
private Singleton(){}
public static Singleton GetInstance(){
return singleton;
}
}
写一个具体的示例来看:
public class ThreadDemo1 {
public static void main(String[] args) throws InterruptedException {
Thread[] thread=new Thread[10];
for(int i=0;i<5;i++){
thread[i]=new Thread(()->{
System.out.println(Singleton.GetInstance());
});
thread[i].start();
}
for (int i = 0; i < 5; i++) {
thread[i].join();
}
}
}
class Singleton{
private static Singleton singleton=new Singleton();
private Singleton(){}
public static Singleton GetInstance(){
return singleton;
}
}
根据运行结果我们能看到不同线程都是同一个实例
懒汉模式
懒汉模式是在第一次使用的时候创建实例
单线程
class Singleton{
private static Singleton instance=null;
private Singleton (){}
public static Singleton GetInstance(){
if(instance==null) instance=new Singleton();
return instance;
}
}
多线程
在多线程情况下再使用单线程的懒汉模式是可能会出现线程不安全的,如果多个线程同时调用GetInstance 方法,可能就会出现多个实例
例如:
public class ThreadDemo2 {
public static void main(String[] args) throws InterruptedException {
Thread[] thread=new Thread[40];
for(int i=0;i<40;i++){
thread[i]=new Thread(()->{
System.out.println(Singleton.GetInstance());
});
thread[i].start();
}
for(int i=0;i<40;i++){
thread[i].join();
}
}
}
class Singleton{
private static Singleton instance=null;
private Singleton (){}
public static Singleton GetInstance(){
if(instance==null) instance=new Singleton();
return instance;
}
}
在GetInstance 方法中加一个 synchronized 就能解决问题
public class ThreadDemo2 {
public static void main(String[] args) throws InterruptedException {
Thread[] thread=new Thread[40];
for(int i=0;i<40;i++){
thread[i]=new Thread(()->{
System.out.println(Singleton.GetInstance());
});
thread[i].start();
}
for(int i=0;i<40;i++){
thread[i].join();
}
}
}
class Singleton{
private static Singleton instance=null;
private Singleton (){}
public static synchronized Singleton GetInstance(){
if(instance==null) instance=new Singleton();
return instance;
}
}
改进
加 synchronized 关键字确实解决了出现多个实例的问题,但是加锁与解锁是开销比较大的事,这里出现的线程不安全只发生在第一次创建实例时,在经过第一次创建实例后,后面就不需要加锁了
因此我们可以再加一个 if 判断一下
在给 instance 变量加上 volatile 关键字避免出现内存可见性的线程不安全
public class ThreadDemo2 {
public static void main(String[] args) throws InterruptedException {
Thread[] thread=new Thread[40];
for(int i=0;i<40;i++){
thread[i]=new Thread(()->{
System.out.println(Singleton.GetInstance());
});
thread[i].start();
}
for(int i=0;i<40;i++){
thread[i].join();
}
}
}
class Singleton{
private static volatile Singleton instance=null;
private Singleton (){}
public static Singleton GetInstance(){
if(instance==null){
synchronized (Singleton.class){
if(instance==null) instance=new Singleton();
}
}
return instance;
}
}
在 thread[1] 刚判断是否为空以后,thread[2] 也调用此方法并执行完,再执行thread[1] 后续加锁操作,这样也会创建多个实例