单例模式
介绍
在编程开发中经常会遇到这样一种场景,那就是需要保证一个类只有一个实例哪怕多线程同时访问,并需要提供一个全局访问此实例的点。单例模式主要解决的是,一个全局使用的类频繁的创建和消费,从而提升提升整体的代码的性能。
例如:日志系统的对象被反复创建打印
public class Logger {
private FileWriter writer;
public Logger() {
File file = new File("../log.txt");
writer = new FileWriter(file, true); //true表示追加写入
}
public void log(String message) {
writer.write(mesasge);
}
}
// Logger类的应用示例:
public class UserController {
private Logger logger = new Logger();
public void login(String username, String password) {
logger.log(username + " logined!");
}
}
public class OrderController {
private Logger logger = new Logger();
public void create(OrderVo order) {
logger.log("Created an order: " + order.toString());
}
}
每次打印日志都会生成log对象,造成了资源浪费,日常开发中大致上会出现如上这些场景中使用到单例模式,虽然单例模式并不复杂但是使用面却比较广。
单例模式的实现
要实现一个单例,需要关注的点无外乎下面几个:
- 构造函数需要是 private 访问权限的,这样才能避免外部通过 new 创建实例
- 考虑对象创建时的线程安全问题
- 考虑是否支持延迟加载
- 考虑 getInstance() 性能是否高(是否加锁)
静态类使用
public class Singleton_00 {
public static Map<String,String> cache = new ConcurrentHashMap<String, String>();
}
在不需要维持任何状态下,仅仅用于全局访问,这个使用使用静态类的方式更加方便
懒汉式(线程不安全)
第一次被引用才会将自己实例化
public class Singleton {
private static Singleton singleton =null;//prvite堵死外界利用new创造实例的可能
public Singleton(){
}
public static Singleton getInternece(){//全局中唯一能访问到实例的方法
if(singleton ==null){//实例不存在就new一个
singleton = new Singleton();
}
return singleton;
}
}
如果有多个访问者访问,就会造成多个同样的实例并存,从而没有达到单例的要求
懒汉式加锁(线程安全)
public class Singleton {
private static Singleton singleton =null;//prvite堵死外界利用new创造实例的可能
public Singleton(){
}
public static synchronized Singleton getInternece(){//全局中唯一能访问到实例的方法
if(singleton ==null){//实例不存在就new一个
singleton = new Singleton();
}
return singleton;
}
}
如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈,这种实现方式就不可取了
饿汉式单例(线程安全)
不管是否调用 先申请
public class Singleton {
private static Singleton singleton =new Singleton();
public Singleton(){
}
public static Singleton getInternece(){
return singleton;
}
}
在类加载的时候,singleton静态实例就已经创建并初始化好了,所以singleton实例的创建过程是线程安全的
静态内部类(线程安全)
它有点类似饿汉式,但又能做到了延迟加载
public class Singleton_04 {
public static class SingletonHolder{
private static Singleton_04 singleton04=new Singleton_04();
}
public Singleton_04() {
}
public static Singleton_04 getInstance(){
return SingletonHolder.singleton04;
}
}
既保证了线程安全有保证了懒加载,同时不会因为加锁的方式耗费性能。非常推荐使用的一种单例模式
双重锁定机制(线程安全)
判断实例是否为空再上锁,volatile 关键字保障能保证线程有序性,禁止JVM以及处理器进行排序,线程安全效率又高
public class Singleton {
private static Singleton singleton =null;//prvite堵死外界利用new创造实例的可能
private static volatile Object object=new Object();
public Singleton(){
}
public static Singleton getInternece(){
if(singleton ==null){//判断是否为空再上锁
synchronized (object)
{
singleton = new Singleton();
}
}
return singleton;
}
}
但是这样如果有两个线程同时调用getInternece方法,都能通过singleton ==null,并且因为synchronized ,一个线程会先创建实例,第二个线程继续等待完毕之后又进入方法创建实例,就达不到单例的目的。
public class Singleton {
private static Singleton singleton = null;
private static volatile Object object = new Object();
public Singleton() {
}
public static Singleton getInternece() {
if (singleton == null) {
synchronized (object) {
if (singleton == null)
singleton = new Singleton();
}
}
return singleton;
}
}
枚举(线程安全 推荐)
上面那些是不考虑反射机制和序列化机制的情况下实现的单例模式,但是如果考虑了反射,则上面的单例就无法做到单例类只能有一个实例这种说法了
@Test
public void Singleton() throws NoSuchMethodException, InvocationTargetException, InstantiationException, IllegalAccessException {
Singleton_04 s1=Singleton_04.getInstance();
Singleton_04 s2=Singleton_04.getInstance();
System.out.println("s1:"+s1+"\n"+"s2:"+s2);
System.out.println("正常模式单例"+(s1==s2));
Constructor<Singleton_04> constructor=Singleton_04.class.getDeclaredConstructor();
constructor.setAccessible(true);
Singleton_04 s3=constructor.newInstance();
System.out.println("s3:"+s3+"\n"+"s1:"+s1);
System.out.println("反射破坏单例"+(s2==s3));
}
实现枚举单例
public enum Singleton_06 {
INSTANCE;
public Singleton_06 getInstance(){
return INSTANCE;
}
}
反射破坏枚举
@Test
void testEnum() throws InvocationTargetException, InstantiationException, IllegalAccessException, NoSuchMethodException {
Singleton_06 singleton06=Singleton_06.INSTANCE;
Constructor<Singleton_06> constructor2=Singleton_06.class.getDeclaredConstructor();
constructor2.setAccessible(true);
Singleton_06 s4=constructor2.newInstance();
System.out.println("s4:"+s4+"\n"+"singleton06:"+singleton06);
System.out.println("反射破坏枚举单例"+(s4==singleton06));
}
破坏失败