单例模式的优点:
- 处理资源访问冲突
- 表示全局唯一类
实现单例的关键:
public class IdGenerator {private AtomicLong id = new AtomicLong(0);private static final IdGenerator instance = new IdGenerator();private IdGenerator() {}public static IdGenerator getInstance() {return instance;}public long getId() {return id.incrementAndGet();}
}
提前初始化占用资源,但是让耗时的操作提前到启动的时候完成,解决初始化导致的性能问题
public class IdGenerator {private AtomicLong id = new AtomicLong(0);private static IdGenerator instance;private IdGenerator() {}public static synchronized IdGenerator getInstance() {if (instance == null) {instance = new IdGenerator();}return instance;}public long getId() {return id.incrementAndGet();}
}
如果频繁地用到,那频繁加锁、释放锁及并发度低等问题,会导致性能瓶颈。
饿汉式不支持延迟加载,懒汉式有性能问题,不支持高并发。
public class IdGenerator {private AtomicLong id = new AtomicLong(0);private static volatile IdGenerator instance;private IdGenerator() {}public static IdGenerator getInstance() {if (instance == null) {synchronized (IdGenerator.class) { // 此处为类级别的锁if (instance == null) {instance = new IdGenerator();}}}return instance;}public long getId() {return id.incrementAndGet();}
}
CPU 指令重排序可能导致在 IdGenerator 类的对象被关键字 new 创建并赋值给 instance 之后,还没来得及初始化(执行构造函数中的代码逻辑),就被另一个线程使用了(volatile 来禁止指令重排序)。
有点类似饿汉式,但又能做到了延迟加载
public class IdGenerator {private AtomicLong id = new AtomicLong(0);private IdGenerator() {}private static class SingletonHolder {private static final IdGenerator instance = new IdGenerator();}public static IdGenerator getInstance() {return SingletonHolder.instance;}public long getId() {return id.incrementAndGet();}
}
SingletonHolder 是一个静态内部类,当外部类 IdGenerator 被加载的时候,并不会创建
SingletonHolder 实例对象。只有当调用 getInstance() 方法时,SingletonHolder 才会被加载,这个时候才会创建 instance。instance 的唯一性、创建过程的线程安全性,都由 JVM 来保证。所以,这种实现方法既保证了线程安全,又能做到延迟加载。
通过 Java枚举类型本身的特性,保证了实例创建的线程安全性和实例的唯一性。
public enum IdGenerator {INSTANCE;private AtomicLong id = new AtomicLong(0);public long getId() {return id.incrementAndGet();}
}
public class IdGenerator {private static AtomicLong id = new AtomicLong(0);public static long getId() {return id.incrementAndGet();}
}// 使用举例long id = IdGenerator.getId();
2.依赖注入(可以解决单例隐藏类之间依赖关系的问题,但是对 OOP 特性、扩展性、可测性不友好等问题,还是无法解决)
public demofunction(IdGenerator idGenerator){long id=idGenerator.getId();}
// 外部调用demofunction()的时候,传入idGeneratorIdGenerator idGenerator=IdGenerator.getInsance();demofunction(idGenerator);}
单例类中对象的唯一性的作用范围是进程内的,在进程间是不唯一的。
如果fork一个线程出来,会在新线程也创建一个单例对象
public class IdGenerator {private AtomicLong id = new AtomicLong(0);private static IdGenerator instance;private static SharedObjectStorage storage = FileSharedObjectStorage(/*入参省略*/);private static DistributedLock lock = new DistributedLock();private IdGenerator() {}public synchronized static IdGenerator getInstance() {if (instance == null) {lock.lock();instance = storage.load(IdGenerator.class);}return instance;}public synchronized void freeInstance() {storage.save(this, IdGeneator.class);instance = null; //释放对象lock.unlock();}public long getId() {return id.incrementAndGet();}
}// IdGenerator使用举例IdGenerator idGeneator = IdGenerator.getInstance();long id = idGenerator.getId();idGenerator.freeInstance();
public class BackendServer {private long serverNo;private String serverAddress;private static final int SERVER_COUNT = 3;private static final Map serverInstances = new HashMap<>()static {serverInstances.put(1L, new BackendServer(1L, "192.134.22.138:8080"));serverInstances.put(2L, new BackendServer(2L, "192.134.22.139:8080"));serverInstances.put(3L, new BackendServer(3L, "192.134.22.140:8080"));}private BackendServer(long serverNo, String serverAddress) {this.serverNo = serverNo;this.serverAddress = serverAddress;}public BackendServer getInstance(long serverNo) {return serverInstances.get(serverNo);}public BackendServer getRandomInstance() {Random r = new Random();int no = r.nextInt(SERVER_COUNT) + 1;return serverInstances.get(no);}
}
多例模式创建的对象都是同一个类的对象,而工厂模式创建的是不同子类的对象
/**
* 根据不同后缀,选择不同的解析器
*/
public class RuleConfigSource {public RuleConfig load(String ruleConfigFilePath) {String ruleConfigFileExtension = getFileExtension(ruleConfigFilePath);IRuleConfigParser parser = RuleConfigParserFactory.createParser(ruleConfigFilif (parser == null) {throw new InvalidRuleConfigException("Rule config file format is not supported: " + ruleConfigFilePath);}String configText = "";
//从ruleConfigFilePath文件中读取配置文本到configText中RuleConfig ruleConfig = parser.parse(configText);return ruleConfig;}private String getFileExtension(String filePath) {
//...解析文件名获取扩展名,比如rule.json,返回jsonreturn "json";}
}public class RuleConfigParserFactory {public static IRuleConfigParser createParser(String configFormat) {IRuleConfigParser parser = null;if ("json".equalsIgnoreCase(configFormat)) {parser = new JsonRuleConfigParser();} else if ("xml".equalsIgnoreCase(configFormat)) {parser = new XmlRuleConfigParser();} else if ("yaml".equalsIgnoreCase(configFormat)) {parser = new YamlRuleConfigParser();} else if ("properties".equalsIgnoreCase(configFormat)) {parser = new PropertiesRuleConfigParser();}return parser;}
}
大部分工厂类都是以“Factory”这个单词结尾的,工厂类中创建对象的方法一般都是 create 开头
我们每次调用 RuleConfigParserFactory 的 createParser() 的时候,都要创建一个新的 parser,所以可以将 parser 事先创建好缓存起来
public class RuleConfigParserFactory {private static final Map cachedParsers = new HashMapcachedParsers.put("json", new JsonRuleConfigParser());cachedParsers.put("xml", new XmlRuleConfigParser());cachedParsers.put("yaml", new YamlRuleConfigParser());cachedParsers.put("properties", new PropertiesRuleConfigParser());}public static IRuleConfigParser createParser(String configFormat) {if (configFormat == null || configFormat.isEmpty()) {return null;//返回null还是IllegalArgumentException全凭你自己说了算}IRuleConfigParser parser = cachedParsers.get(configFormat.toLowerCase());return parser;}
}
尽管简单工厂模式的代码实现中,有多处 if 分支判断逻辑,违背开闭原则,但权
衡扩展性和可读性,这样的代码实现在大多数情况下(比如,不需要频繁地添加 parser,也
没有太多的 parser)是没有问题的。
可以通过多态避免多的if
public interface IRuleConfigParserFactory {IRuleConfigParser createParser();
}public class JsonRuleConfigParserFactory implements IRuleConfigParserFactory {@Overridepublic IRuleConfigParser createParser() {return new JsonRuleConfigParser();}
}public class XmlRuleConfigParserFactory implements IRuleConfigParserFactory {@Overridepublic IRuleConfigParser createParser() {return new XmlRuleConfigParser();}
}public class YamlRuleConfigParserFactory implements IRuleConfigParserFactory {@Overridepublic IRuleConfigParser createParser() {return new YamlRuleConfigParser();}
}public class PropertiesRuleConfigParserFactory implements IRuleConfigParserFactor {@Overridepublic IRuleConfigParser createParser() {return new PropertiesRuleConfigParser();}
}public class RuleConfigParserFactoryMap { //工厂的工厂private static final Map cachedFactories = nestatic {cachedFactories.put("json", new JsonRuleConfigParserFactory());cachedFactories.put("xml", new XmlRuleConfigParserFactory());cachedFactories.put("yaml", new YamlRuleConfigParserFactory());cachedFactories.put("properties", new PropertiesRuleConfigParserFactory());}public static IRuleConfigParserFactory getParserFactory(String type) {if (type == null || type.isEmpty()) {return null;}IRuleConfigParserFactory parserFactory = cachedFactories.get(type.toLowerCasereturn parserFactory;}
}
工厂方法模式比起简单工厂模式更加符合开闭原则。
当对象的创建逻辑比较复杂,不只是简单的 new 一下就可以,而是要组合其他类对象,做各种初始化操作的时候,我们推荐使用工厂方法模式,将复杂的创建逻辑拆分到多个工厂类中,让每个工厂类都不至于过于复杂。而使用简单工厂模式,将所有的创建逻辑都放到一个工厂类中,会导致这个工厂类变得很复杂。
在简单工厂和工厂方法中,类只有一种分类方式。如果还用简单工厂或者工厂方法就会创建过多的工厂类。抽象工厂可以让一个工厂负责创建多个不同类型的对象,有效地减少工厂类的个数。
public interface IConfigParserFactory {IRuleConfigParser createRuleParser();ISystemConfigParser createSystemParser();
//此处可以扩展新的parser类型,比如IBizConfigParser
}
public class JsonConfigParserFactory implements IConfigParserFactory {@Overridepublic IRuleConfigParser createRuleParser() {return new JsonRuleConfigParser();}@Overridepublic ISystemConfigParser createSystemParser() {return new JsonSystemConfigParser();}
}
public class XmlConfigParserFactory implements IConfigParserFactory {@Overridepublic IRuleConfigParser createRuleParser() {return new XmlRuleConfigParser();}@Overridepublic ISystemConfigParser createSystemParser() {return new XmlSystemConfigParser();}
}
一个工厂类只负责某个类对象或者某一组相关类对象(继承自同一抽象类或者接口的子类)的创建,而 DI 容器负责的是整个应用中所有类对象的创建。
DI 容器的核心功能一般有三个:配置解析、对象创建和对象生命周期管理。
当创建对象要传递很多的参数,容易传递错误的参数,并且影响可读性
使用set方法可以解决创建对象参数列表过多的参数,但是不能解决各个参数设置直接的依赖关系,或者约束条件就很难解决(比如maxIdle要大于minIdle)
如果对象在创建好之后,就不能再修改内部的属性值,不能暴露ResourcePoolConfig 的set方法
这个时候就可以使用建造者模式(把校验逻辑放置到 Builder 类中,然后在使用 build() 方法真正创建对象之前,我们把 ResourcePoolConfig 的构造函数改为 private 私有权限,ResourcePoolConfig 没有提供任何 set() 方法,这样我们创建出来的对象就是不可变对象了)
public class ResourcePoolConfig {private String name;private int maxTotal;private int maxIdle;private int minIdle;private ResourcePoolConfig(Builder builder) {this.name = builder.name;this.maxTotal = builder.maxTotal;this.maxIdle = builder.maxIdle;this.minIdle = builder.minIdle;}//...省略getter方法...
//我们将Builder类设计成了ResourcePoolConfig的内部类。
//我们也可以将Builder类设计成独立的非内部类ResourcePoolConfigBuilder。public static class Builder {private static final int DEFAULT_MAX_TOTAL = 8;private static final int DEFAULT_MAX_IDLE = 8;private static final int DEFAULT_MIN_IDLE = 0;private String name;private int maxTotal = DEFAULT_MAX_TOTAL;private int maxIdle = DEFAULT_MAX_IDLE;private int minIdle = DEFAULT_MIN_IDLE;public ResourcePoolConfig build() {// 校验逻辑放到这里来做,包括必填项校验、依赖关系校验、约束条件校验等if (StringUtils.isBlank(name)) {throw new IllegalArgumentException("...");}return new ResourcePoolConfig(this);}public Builder setName(String name) {this.name = name;return this;}public Builder setMaxTotal(int maxTotal) {this.maxTotal = maxTotal;return this;}public Builder setMaxIdle(int maxIdle) {this.maxIdle = maxIdle;return this;}public Builder setMinIdle(int minIdle) {this.minIdle = minIdle;return this;}}
}// 这段代码会抛出IllegalArgumentException,因为minIdle>maxIdleResourcePoolConfig config = new ResourcePoolConfig.Builder().setName("dbconnectionpool").setMaxTotal(16).setMaxIdle(10).setMinIdle(12).build();
工厂模式是用来创建不同但是相关类型的对象(继承同一父类或者接口的一组子类),由给定的参数来决定创建哪种类型的对象。建造者模式是用来创建一种类型的复杂对象,通过设置不同的可选参数,“定制化”地创建不同的对象。
如果对象的创建成本比较大,而同一个类的不同对象之间差别不大(大部分字段都相同),在这种情况下,我们可以利用对已有对象(原型)进行复制(或者叫拷贝)的方式来创建新对象,以达到节省创建时间的目的。这种基于原型来创建对象的方式就叫作原型设计模式(Prototype Design Pattern),简称原型模式。
(如果对象中的数据需要经过复杂的计算才能得到(比如排序、计算哈希值),或者需要从 RPC、网络、数据库、文件系统等非常慢速的 IO 中读取)可以使用原型模式。
如果一次有10W条数据需要查询,使用原型模式,不断增加
public class Demo {private HashMap currentKeywords=new HashMap<>();private long lastUpdateTime = -1;public void refresh() {
// 原型模式就这么简单,拷贝已有对象的数据,更新少量差值HashMap newKeywords = (HashMap) currentKeywords.clone();
// 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中List toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);long maxNewUpdatedTime = lastUpdateTime;for (SearchWord searchWord : toBeUpdatedSearchWords) {if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {maxNewUpdatedTime = searchWord.getLastUpdateTime();}if (newKeywords.containsKey(searchWord.getKeyword())) {SearchWord oldSearchWord = newKeywords.get(searchWord.getKeyword());oldSearchWord.setCount(searchWord.getCount());oldSearchWord.setLastUpdateTime(searchWord.getLastUpdateTime());} else {newKeywords.put(searchWord.getKeyword(), searchWord);}}lastUpdateTime = maxNewUpdatedTime;currentKeywords = newKeywords;}private List getSearchWords(long lastUpdateTime) {
// TODO: 从数据库中取出更新时间>lastUpdateTime的数据return null;}
}
浅拷贝得到的对象跟原始对象共享数据,而深拷贝得到的是一份完完全全独立的对象
public class Demo {private HashMap currentKeywords = new HashMap<>();private long lastUpdateTime = -1;public void refresh() {
// Deep copyHashMap newKeywords = new HashMap<>();for (HashMap.Entry e : currentKeywords.entrySet()) {SearchWord searchWord = e.getValue();SearchWord newSearchWord = new SearchWord(searchWord.getKeyword(), searchWord.getCount(), searchWord.getLastUnewKeywords.put(e.getKey(), newSearchWord);}
// 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中List toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);long maxNewUpdatedTime = lastUpdateTime;for (SearchWord searchWord : toBeUpdatedSearchWords) {if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {maxNewUpdatedTime = searchWord.getLastUpdateTime();}if (newKeywords.containsKey(searchWord.getKeyword())) {SearchWord oldSearchWord = newKeywords.get(searchWord.getKeyword());oldSearchWord.setCount(searchWord.getCount());oldSearchWord.setLastUpdateTime(searchWord.getLastUpdateTime());} else {newKeywords.put(searchWord.getKeyword(), searchWord);}}lastUpdateTime = maxNewUpdatedTime;currentKeywords = newKeywords;}private List getSearchWords(long lastUpdateTime) {
// TODO: 从数据库中取出更新时间>lastUpdateTime的数据return null;}
}
public Object deepCopy(Object object) {ByteArrayOutputStream bo = new ByteArrayOutputStream();ObjectOutputStream oo = new ObjectOutputStream(bo);oo.writeObject(object);ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());ObjectInputStream oi = new ObjectInputStream(bi);return oi.readObject();}
深拷贝都要比浅拷贝耗时、耗内存空间,我们可以先采用浅拷贝的方式创建 newKeywords。对于需要更新的 SearchWord 对象,我们再使用深度拷贝的方式创建一份新的对象,替换 newKeywords 中的老对象。
public void refresh() {// Shallow copyHashMap newKeywords = (HashMap) currentKeywords.clone();// 从数据库中取出更新时间>lastUpdateTime的数据,放入到newKeywords中List toBeUpdatedSearchWords = getSearchWords(lastUpdateTime);long maxNewUpdatedTime = lastUpdateTime;for (SearchWord searchWord : toBeUpdatedSearchWords) {if (searchWord.getLastUpdateTime() > maxNewUpdatedTime) {maxNewUpdatedTime = searchWord.getLastUpdateTime();}if (newKeywords.containsKey(searchWord.getKeyword())) {newKeywords.remove(searchWord.getKeyword());}newKeywords.put(searchWord.getKeyword(), searchWord);}lastUpdateTime = maxNewUpdatedTime;currentKeywords = newKeywords;}
如果要拷贝的对象是不可变对象,浅拷贝共享不可变对象是没问题的,但对于可变对象来说,浅拷贝得到的对象和原始对象会共享部分数据,就有可能出现数据被修改的风险,