首先,什么是单例模式。
单例模式,项目中只需要一个实例存在,不需要也不能够通过代码new多个实例。
这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。
这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。
下面看八中单例模式的写法:
方法一:饿汉式
/**
* @Date 2021/11/19 15:21
* @Description 饿汉式
* 类加载到内存后,就实例化一个单例,JVM保证线程安全
* 简单实用,推荐使用
* 缺点:不管用到与否,类装载时就会完成实例化,不用也会装载
*/
public class Singleton1 {
private static final Singleton1 INSTANCE = new Singleton1();
private Singleton1(){}
public static Singleton1 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
Singleton1 singleton1 = Singleton1.getInstance();
Singleton1 singleton2 = Singleton1.getInstance();
System.out.println(singleton1==singleton2);
}
}
这种方式会在类加载到内存后就会实例化一个单例,简单方便。但同时有个缺点,不管你用没用到都会实例化一个单例,就算不用也会装载。
我们看执行结果:
结果是true,说明单例模式创建成功了。
方式二:饿汉式2
/**
* @Date 2021/11/19 15:28
* @Description 跟01一样的,只不过new改为静态块初始化
*/
public class Singleton2 {
private static final Singleton2 INSTANCE;
static {
INSTANCE = new Singleton2();
}
private Singleton2(){};
public static Singleton2 getInstance(){
return INSTANCE;
}
public static void main(String[] args) {
Singleton2 instance1 = Singleton2.getInstance();
Singleton2 instance2 = Singleton2.getInstance();
System.out.println(instance1==instance2);
}
}
我们看执行结果:
结果是true,说明单例模式创建成功了。
方式三:懒汉式
/**
* @Date 2021/11/19 15:45
* @Description 懒汉模式
* 这种模式虽然解决了饿汉模式用不用得到都会加载的问题
* 但是有线程并发问题,并发时会导致返回的实例不止一个
*/
public class Singleton3 {
private static Singleton3 INSTANCE;
private Singleton3(){}
@SneakyThrows
public static Singleton3 getInstance(){
if(INSTANCE==null){
Thread.sleep(1);//睡眠一毫秒,触发更多单例
INSTANCE = new Singleton3();
}
return INSTANCE;
}
public static void main(String[] args) {
//创建100个线程去调这个单例
for (int i = 0; i <100 ; i++) {
new Thread(()->{
System.out.println(Singleton3.getInstance().hashCode());
}).start();
}
}
}
这种模式虽然解决了饿汉模式用不用得到都会加载的问题,但是有线程并发问题,并发时会导致返回的实例不止一个。比如线程1执行到
if(INSTANCE==null){
时获取到的INSTANCE确实是null,然后这时候线程2也开始去判断,同时返回结果也是null,那么这样就会出现返回的实例不止一个了。
我们看执行结果:
singleton的hashcode相同说明是同一个实例,不同说明是不同实例。(其实hashcode相同也不一定说明是同一个实例,只不过这里我们不讨论这种小概率事件)。
方式四:懒汉式优化
package com.wangs.designpatterns.singleton;
import lombok.SneakyThrows;
/**
* @Author 王硕
* @Date 2021/11/19 15:45
* @Description 懒汉模式
* 这种模式虽然解决了饿汉模式用不用得到都会加载的问题
* 但是有线程并发问题,并发时会导致返回的实例不止一个
* 可以通过加锁 synchronized解决,但是也会降低效率
*/
public class Singleton4 {
private static Singleton4 INSTANCE;
private Singleton4(){}
@SneakyThrows
public static synchronized Singleton4 getInstance(){
if(INSTANCE==null){
Thread.sleep(1);//睡眠一毫秒,触发更多单例
INSTANCE = new Singleton4();
}
return INSTANCE;
}
public static void main(String[] args) {
//创建100个线程去调这个单例
for (int i = 0; i <100 ; i++) {
new Thread(()->{
System.out.println(Singleton4.getInstance().hashCode());
}).start();
}
}
}
我们可以使用synchronized关键字,这样我们访问getInstance()方法就是原子性的了,但多线程访问的时候就会出现等待,会降低效率。
我们看执行结果:
没问题,都是同样的hashCode。
方式五:懒汉式优化2
import lombok.SneakyThrows;
/**
* @Author 王硕
* @Date 2021/11/19 15:45
* @Description 懒汉模式
* 这种模式虽然解决了饿汉模式用不用得到都会加载的问题
* 但是有线程并发问题,并发时会导致返回的实例不止一个
* 可以通过加锁 synchronized解决,但是也会降低效率
* 如果我们不锁整个方法,只锁定里面内容呢
* 这时候也不行,同样也会出现并发问题
*/
public class Singleton5 {
private static Singleton5 INSTANCE;
private Singleton5(){}
@SneakyThrows
public static Singleton5 getInstance(){
if(INSTANCE==null){
synchronized (Singleton5.class){
Thread.sleep(1);//睡眠一毫秒,触发更多单例
INSTANCE = new Singleton5();
}
}
return INSTANCE;
}
public static void main(String[] args) {
//创建100个线程去调这个单例
for (int i = 0; i <100 ; i++) {
new Thread(()->{
System.out.println(Singleton5.getInstance().hashCode());
}).start();
}
}
}
我们由锁方法改为锁判断呢?答案类似于方式三,同样会出现线程并发问题。
我们来看结果:
方式六:懒汉式优化3
package com.wangs.designpatterns.singleton;
import lombok.SneakyThrows;
/**
* @Author 王硕
* @Date 2021/11/19 15:45
* @Description 懒汉模式
* 这里需要加双重判断,即可解决并发问题
*/
public class Singleton6 {
private static volatile Singleton6 INSTANCE;
private Singleton6(){}
@SneakyThrows
public static Singleton6 getInstance(){
if(INSTANCE==null){
synchronized (Singleton6.class){
if(INSTANCE==null){
Thread.sleep(1);//睡眠一毫秒,触发更多单例
INSTANCE = new Singleton6();
}
}
}
return INSTANCE;
}
public static void main(String[] args) {
//创建100个线程去调这个单例
for (int i = 0; i <100 ; i++) {
new Thread(()->{
System.out.println(Singleton6.getInstance().hashCode());
}).start();
}
}
}
增加一层判断,这样就可以避免判断过程中造成线程并发问题了。
我们看执行结果:
懒汉式优化到此结束,我们来看下一种方式
方式七:静态内部类方式
/**
* @Author 王硕
* @Date 2021/11/19 16:42
* @Description 静态内部类方式
* JVM保证单例
* 加载外部类时,不会加载内部类,这样可以实现懒加载
*/
public class Singleton7 {
private Singleton7(){}
private static class SingletonHolder{
private final static Singleton7 INSTANCE = new Singleton7();
}
public static Singleton7 getInstance(){
return SingletonHolder.INSTANCE;
}
public static void main(String[] args) {
//创建100个线程去调这个单例
for (int i = 0; i <100 ; i++) {
new Thread(()->{
System.out.println(Singleton7.getInstance().hashCode());
}).start();
}
}
}
加载外部类的时候,内存不会加载内部类,这样同样可以实现懒加载。
我们来看结果:
方式八:枚举方式
/**
* @Author 王硕
* @Date 2021/11/19 16:52
* @Description 不仅可以解决线程同步,还可以防止反序列化
* @Param
* @return
**/
public enum Singleton8 {
INSTANCE;
public static void main(String[] args) {
for (int i = 0; i <100 ; i++) {
new Thread(()->{
System.out.println(Singleton8.INSTANCE.hashCode());
}).start();
}
}
}
这种方式最为简单,不仅可以解决线程同步问题,还可以防止反序列化。
当我们实际在写代码的时候,大多不需要我们自己去手写,只需要使用Spring的bean工厂就可以创建单例模型了。