分类: Java
2020-05-07 17:00:37
概念
单例模式可以说是设计模式里面最好理解的一个模式了,它的意思就是一个类只创建一个对象,所有的引用都只向该对象去操作。
单例模式满足的要求:
1.构造函数私有化
2.类的内部创建实例
3.提供静态的唯一获取实例的方法
单例模式之饿汉式
@Data
public class Singleton {
private static Singleton singleton=new Singleton(); //内部创建实例
private Integer data=0; //成员变量
private Singleton(){} //构造函数私有化
//本身线程安全
public static Singleton getTarget(){
return singleton;
}
}
单例模式之懒汉式
@Data
public class Singleton {
private static Singleton singleton=null; //内部创建实例
private Integer data=0; //成员变量
private Singleton(){} //构造函数私有化
//使用同步方法保证线程安全
public static synchronized Singleton getTarget(){
if (singleton==null) {
singleton = new Singleton();
}
return singleton;
}
}
单例模式之双重检测懒汉式(DCL)
@Data
public class Singleton {
private volatile static Singleton singleton=null; //内部创建实例,并使用volatile修饰保证其可见性
private Integer data=0; //成员变量
//构造函数私有化
private Singleton(){}
//对外提供获取实例方法,并且通关双重判空,为什么要使用双重判空而不是单重?
//1.外判内不判:如果同时两个线程先判空进入方法内后,一个线程拿到锁进去
//创建对象,另一个线程再拿到锁进去又创建对象
//2.内判外不判:外判可以先判是否有对象,不需要进去同步代码块提高效率
//同步代码块加锁的方式保证线程安全,只创建出一个实例,对比懒汉式效率更高
public static Singleton getTarget(){
if (singleton==null){
synchronized (Singleton.class){
if (singleton==null){
singleton=new Singleton();
}
}
}
return singleton;
}
}
单例模式之内部类
@Data
public class Singleton {
private Integer data=0; //成员变量
private Singleton(){} //构造函数私有化
//静态内部类
private static class Target{
private static final Singleton singleton = new Singleton();//内部类创建实例
}
//当任何一个线程第一次调用getTarget()时,都会使Target被
//加载和被初始化,此时静态初始化器将执行Singleton的初始化操作。
// (被调用时才进行初始化!)初始化静态数据时,Java提供了的线程安全性保证。
public static final Singleton getTarget(){
return Target.singleton;
}
}
单例模式之枚举
public enum Singleton {
JAVA_Singleton,
}
//简单,安全,即使是在面对复杂的序列化或者反射攻击的时候
单例是线程安全的吗?
并不是,虽然说对于创建单例,我们有很多办法去保证其只创建出一个实例(线程安全),但是,假如多个线程来操作该单例对象,该单例对象就相当于一个共享变量,所以并不是线程安全的,如下图,应该是2000000。
public class SingletonThread implements Runnable{
@Override
public void run() {
for (int i=0;i<1000000;i++){
Singleton singleton = Singleton.getTarget();
singleton.setData(singleton.getData()+1);
System.out.println(Thread.currentThread().getName()+":"+singleton.getData());
}
}
}
@GetMapping("/singletonDemo")
public String singletonDemo(){
SingletonThread run=new SingletonThread();
Thread thread1 =new Thread(run);
Thread thread2 =new Thread(run);
thread1.start();
thread2.start();
return ReturnJson.success();
}
}
解决:
public class SingletonThread implements Runnable{
private static final Lock lock=new ReentrantLock();
@Override
public void run() {
for (int i=0;i<1000000;i++){
try {
lock.lock();
Singleton singleton = Singleton.getTarget();
singleton.setData(singleton.getData()+1);
System.out.println(Thread.currentThread().getName()+":"+singleton.getData());
}catch (Exception e){
System.out.println(e);
}finally {
lock.unlock();
应用场景
好处:我们为什么要使用单例模式呢?它有什么好处?
1、第一个单例模式可以让我们只创建一个对象从而避免了频繁创建对象导致的内存消耗和垃圾回收,一个对象可以搞定的事何乐而不为,除非是特殊情况(Struts2是多例的)。
2、Servlet是单例模式,我们只需要创建一个Servlet,然后接收请求并处理,这样我们可以很省内存。spring的bean默认也是单例模式,springMVC是单例模式,所以我们可以发现我们的service层,dao层,web层都是使用单例模式,单例无处不在。线程的线程池的设计一般也是采用单例模式,这是由于线程池要方便对池中的线程进行控制。mysql,redis等的连接对象使用单例模式例模式的另外一个好处是可以让我们操作同一个共享变量来保证同步。网站的计数器,一般也是采用单例模式实现,来保证其同步。