目录
一.什么是单例模式
单例模式是校招中最常考的设计模式之一
什么是设计模式:软件开发过程中会遇到很多常见的问题场景。针对这些问题场景,大佬们总结了一套固定的套路,按照这个套路实现代码,不会吃亏。
单例模式能保证某个类在程序中只存在唯一一份实例,而不会创建出多个实例。
单例模式具体的实现方式有很多.最常见的是"饿汉"和"懒汉"两种。
二.饿汉模式
类加载的同时就创建实例,并且将构造方法私有化(尽量早创建)
java
class Singleton{
private static Singleton instance=new Singleton();//类加载初始化,JVM保证线程安全
public static Singleton getInstance(){
return instance;
}
private Singleton(){
}
}
静态成员初始化是在类加载的初始化阶段 触发的;类加载不是程序一启动就触发 ,而是第一次使用该类时 才触发;return 只是读操作,不涉及修改;静态变量只会创建并初始化一次 ;静态变量 / 静态代码块的初始化由 JVM 保证线程安全 ,JVM 在类初始化时会自动加锁 ,确保多线程下只会执行一次,不会产生线程安全问题。
三.懒汉模式
3.1.单线程版
类加载的时候不创建实例.第一次使用的时候才创建实例(尽量晚创建)
java
class Singleton {
private static Singleton instance = null;
private Singleton() {}
public static Singleton getInstance() {
if (instance == null) {
instance = new Singleton();
}
return instance;
}
}
如果是单线程的话,完全是可以实现单例模式的,但是多线程就不行了,会产生线程安全问题
3.2.多线程版

这里需要进行加锁操作,以防线程调度

导致这种现象产生(不再是只实例化一个对象)
java
class SingletonLazy{
private static SingletonLazy instance=null;
private static Object loker=new Object();
public static SingletonLazy getInstance(){
synchronized (loker) {
if (instance == null) {
instance = new SingletonLazy();
}
System.out.println(Thread.currentThread().getName()+"解锁");
}
return instance;
}
private SingletonLazy(){
}
}
虽然这个代码已经线程安全了,但是还是有问题,我们可以看到,这个加锁操作并不是按需求加锁,如果实例创建了,就不涉及到线程安全的问题了,按理说就不需要加锁了,过度的加锁会导致后续线程阻塞等待,降低效率,所以我们可以在加锁之前做一个判断
java
class SingletonLazy{
private static SingletonLazy instance=null;
private static Object loker=new Object();
public static SingletonLazy getInstance(){
if(instance==null) {
System.out.println(Thread.currentThread().getName()+"判断instance是null的,加锁操作,避免过度枷锁");
synchronized (loker) {
System.out.println(Thread.currentThread().getName()+"被加锁");
if (instance == null) {
System.out.println(Thread.currentThread().getName()+"判断instance是null的,创建新对象");
instance = new SingletonLazy();
}
System.out.println(Thread.currentThread().getName()+"解锁");
}
}
return instance;
}
private SingletonLazy(){
}
}
第一个if判断是为了避免过度加锁,第二个if是为了判断是否已经实例化对象。
但是这就完了吗?nonono,当然没有,我们上一张讲到的JVM 为了提高效率,会自作主张将长期未变,并且用的次数很多的数据存储在寄存器中直接拿取,其实JVM 为了优化效率还会进行指令重排的操作。
创建实例的步骤:1.创建内存。2.构造对象,初始化。3.把内存地址赋值给引用。如果不加上限制的话可能会导致指令重排,可能顺序为1,3,2.就会导致得到的是一个半初始化的对象,得到的是一个空格子,没有具体内容。
java
class SingletonLazy{
private static volatile SingletonLazy instance=null;
private static Object loker=new Object();
public static SingletonLazy getInstance(){
if(instance==null) {
System.out.println(Thread.currentThread().getName()+"判断instance是null的,加锁操作,避免过度枷锁");
synchronized (loker) {
System.out.println(Thread.currentThread().getName()+"被加锁");
if (instance == null) {
System.out.println(Thread.currentThread().getName()+"判断instance是null的,创建新对象");
instance = new SingletonLazy();
}
System.out.println(Thread.currentThread().getName()+"解锁");
}
}
return instance;
}
private SingletonLazy(){
}
}
public class Demo1{
public static void main(String[] args) {
Thread t1=new Thread(()->{
SingletonLazy.getInstance();
});
Thread t2=new Thread(()->{
SingletonLazy.getInstance();
});
t1.start();
t2.start();
}
}
