目录:
- 什么是单例模式
- 为什么要使用单例模式
- 如何实现单例模式
什么是单例模式
一个类只允许创建一个实例或对象,那么这个类就叫做单例类,这种设计模式就叫做叫做单例模式。
为什么要使用单例模式
1、它可以解决资源访问冲突:
首先我们先看一段代码
1 public class Logger { 2 3 private FileWriter fileWriter; 4 5 public Logger() throws IOException { 6 File file = new File("E:\\codeTest\\log.txt"); 7 // true >>> 写入的字符追加到文件末尾 8 fileWriter = new FileWriter(file, true); 9 } 10 11 public void log(String message) throws IOException { 12 fileWriter.write(message); 13 } 14 }
1 public class UserController { 2 3 private Logger log = new Logger(); 4 5 public UserController() throws IOException { 6 } 7 8 public void login(String username, String password) throws IOException { 9 log.log(MessageFormat.format("login username={0} password={1}", username, password)); 10 } 11 }
1 public class OrderController { 2 3 private Logger log = new Logger(); 4 5 public OrderController() throws IOException { 6 } 7 8 public void find(long orderId) throws IOException { 9 log.log(MessageFormat.format("find orderId={0}", orderId)); 10 } 11 }
乍一看正常情况下的确是没有问题的,但是如果是在高并发场景下,便可能会造成OrderController或UserController中追加到文件末尾的日志会覆盖掉另一方的输入。
比如order和user同时竞争日志文件的资源,然后他们两个拿到的文件又是同一份;此时order向要在偏移量为100的位置添加文字“A”,user也要在偏移量为100的位置添加文字“B”。
当order执行完后,偏移量为100的数据便为A,紧接着B也执行了同样的操作,所以便把偏移量100的数据给覆盖掉了。
———————————————————————————————————————————————————————
解决问题的办法很多,最简单也可能最先想到的便是给log方法加锁了吧(因为是不同的对象,所以我们加类锁)。
1 public class Logger { 2 3 private FileWriter fileWriter; 4 5 public Logger() throws IOException { 6 File file = new File("E:\\codeTest\\log.txt"); 7 // true >>> 写入的字符追加到文件末尾 8 fileWriter = new FileWriter(file, true); 9 } 10 11 public void log(String message) throws IOException { 12 synchronized (Logger.class) { 13 fileWriter.write(message); 14 } 15 } 16 }
当然加锁是可以达到目的的,但使用单例模式的话解决思路更简单。
也不用创建那么多的Logger类,一方面可以节省内存空间,另一方面可以节省系统文件句柄。
1 public class LoggerBuff { 2 3 private FileWriter fileWriter; 4 private static LoggerBuff INSTANCE; 5 6 static { 7 try { 8 INSTANCE = new LoggerBuff(); 9 } catch (IOException e) { 10 e.printStackTrace(); 11 } 12 } 13 14 public LoggerBuff() throws IOException { 15 File file = new File("E:\\codeTest\\log.txt"); 16 // true >>> 写入的字符追加到文件末尾 17 fileWriter = new FileWriter(file, true); 18 } 19 20 public LoggerBuff getInstance() { 21 return INSTANCE; 22 } 23 24 public void log(String message) throws IOException { 25 synchronized (LoggerBuff.class) { 26 fileWriter.write(message); 27 } 28 } 29 }
———————————————————————————————————————————————————————
2、它可以表示全局唯一的类:
从业务概念上,如果有些数据在系统中只应保存一份,那就比较适合设计为单例类。
再比如,唯一递增ID号码生成器,如果程序中有两个对象,那就会存在生成重复ID的情况。所以,我们应该将ID生成器类设计为单例。
如何实现单例模式
- 构造函数需要是private访问权限的,这样才能避免外部通过new创建实例。
- 考虑对象创建时的线程安全问题。
- 考虑是否支持延迟加载。
- 考虑getInstance()性能是否高(是否加锁)。
1、饿汉:实例对象为静态,启动程序时加载。
1 public class HungryMan { 2 3 private AtomicLong id = new AtomicLong(0); 4 private static final HungryMan INSTANCE = new HungryMan(); 5 6 private HungryMan() { 7 } 8 9 public static HungryMan getInstance() { 10 return INSTANCE; 11 } 12 13 public long getId() { 14 return id.incrementAndGet(); 15 } 16 }
适用初始化占用资源较多或消耗时间过长,因为如果是这两种情况还是用的懒加载的话便会影响系统性能。
———————————————————————————————————————————————————————
2、懒汉:懒加载。
1 public class LazyMan { 2 3 private AtomicLong id = new AtomicLong(0); 4 private static LazyMan instance; 5 6 private LazyMan() { 7 } 8 9 public static synchronized LazyMan getInstance() { 10 if (instance == null) { 11 instance = new LazyMan(); 12 } 13 return instance; 14 } 15 16 public long getId() { 17 return id.incrementAndGet(); 18 } 19 }
支持懒加载,但并发度低。
———————————————————————————————————————————————————————
3、双重检测:既支持懒加载,又支持高并发的单例实现。
1 public class Double { 2 3 private AtomicLong id = new AtomicLong(0); 4 private static Double instance; 5 6 private Double() { 7 } 8 9 public static Double getInstance() { 10 if (instance == null) { 11 synchronized (Double.class) { 12 if (instance == null) { 13 instance = new Double(); 14 } 15 } 16 } 17 return instance; 18 } 19 20 public long getId() { 21 return id.incrementAndGet(); 22 } 23 }
———————————————————————————————————————————————————————
4、静态内部类。
1 public class Inner { 2 3 private AtomicLong id = new AtomicLong(0); 4 5 private Inner() { 6 } 7 8 private static class SingletonHolder { 9 private static final Inner INSTANCE = new Inner(); 10 } 11 12 public static Inner getInstance() { 13 return SingletonHolder.INSTANCE; 14 } 15 16 public long getId() { 17 return id.incrementAndGet(); 18 } 19 }
———————————————————————————————————————————————————————
5、枚举。
1 public enum Enum { 2 3 INSTANCE; 4 5 private AtomicLong id = new AtomicLong(0); 6 7 public long getId() { 8 return id.incrementAndGet(); 9 } 10 }