单例模式
基本整理
单例模式普通为8种,
- 1饿汉,3懒汉
- 饿汉
- 就是 变量后 就直接new
- 懒汉
- 就是 在方法里new
- 第二种很简答,方法上加synchronized,如HashTable
- 第三种叫:双重检查锁
- 饿汉
- 静态变量,枚举,内部类,CAS
1. 极简 饿汉模式
public class Hungry {
private static Hungry h = new Hungry();
private Hungry() {
}
public static Hungry getInstance() {
return h;
}
}
2 和 3. 极简 懒汉模式
- 因为 不想直接加载,所以 就放在方法里加载。
- 所以就 新增了 bug,
- 所以我们加锁去解决bug,
- 加了锁,所以又出现了性能的问题,
- 所以去解决性能的问题,有了下面的4,
- 那么 我们为什么不用 懒汉模式呢?
- 因为 不想直接加载,所以 就放在方法里加载。
- 那别放在方法里啊,不想直接加载,就延迟加载。是不是就 没有什么 饿汉式,加锁饿汉,双重检查锁饿汉。
public class SimpleIdlerOrSync {
private static SimpleIdlerOrSync s;
private SimpleIdlerOrSync() {
}
public static SimpleIdlerOrSync getInstance() {
//对象不为空,返回
if (null != s) {
return s;
}
s = new SimpleIdlerOrSync();
return s;
}
//加锁
public static synchronized SimpleIdlerOrSync getInstanceSync() {
return getInstance();
}
}
4. 饿汉之双重检查锁
-
就是 把锁加在整个方法上,别人不满意
-
CPU为了性能,进行了指令重排,这一纳秒,恰好有多个人同时获得对象。
- 没指令重排没问题,有了就可能出现。对象 指向堆内存的空间 后,我们就获取
- 此时:对象还没有初始化,的问题。
-
锁里面,锁外面都判断一下null
public class DoubleLockIdler {
//保证 可见性。1,开辟内存空间,2,实例化参数,3,把对象 指向堆内存的空间。
//jvm存在 指令重排。指向内存空间的时候,还没有初始化,dcl失效,拿对象 拿不到。
//volatile禁止指令重排, 不是原子性,要 结合 synchronized关键字
private static volatile DoubleLockIdler d;
private DoubleLockIdler() {
}
public static DoubleLockIdler getInstance() {
if (null != d) {
//不为null,立刻返回
return d;
}
synchronized (DoubleLockIdler.class) {
//进入锁,再次校验
if (null == d) {
d = new DoubleLockIdler();
}
}
return d;
}
}
5. 定义一个 全局静态变量
public class SimpleStaticArgs {
//jvm初始化的过程中,实例化属性。保存全局的对象
public static Map<String, String> cache = new ConcurrentHashMap<String, String>();
}
6. 常用的枚举,都是单例
public enum SingletonEnum {
INSTANCE;
public void test() {
System.out.println("hi");
}
}
枚举统一继承Enum类
public abstract class Enum<E extends Enum<E>> implements Comparable<E>, Serializable
javap -c xx.class //编译可看到
7. 内部类,都是单例
- 提供一个内部类的返回
public class InnerClass {
private InnerClass() {
}
//使用 内部类 点 返回外部类
public static InnerClass getInstance() {
return Holder.i;
}
//创建内部类
private static class Holder {
//提供 外部类的访问
private static InnerClass i = new InnerClass();
}
}
8. 使用CAS
(Compare-And-Swap)是比较并交换的意思,它是一条 CPU 并发原语,用于判断内存中某个值是否为预期值,如果是则更改为新的值,这个过程是原子的。
对应代码,Compare And Set
所谓原子操作类,
- 指的是java.util.concurrent.atomic包下,
- 一系列以Atomic开头的包装类。
- 如AtomicBoolean,AtomicInteger,AtomicLong。
- 它们分别用于Boolean,Integer,Long类型的原子性操作。
public class SingletonCas {
private static final AtomicReference<SingletonCas> INSTANCE = new AtomicReference<>();
//private static SingletonCas i;
private SingletonCas() {
}
public static final SingletonCas getInstance() {
for (; ; ) {
//获取
SingletonCas i = INSTANCE.get();
//不为null返回
if (null != i) {
return i;
}
//为null设置
INSTANCE.compareAndSet(null, new SingletonCas());
//返回
return INSTANCE.get();
}//for循环
}
}
1. 建造者 各种装修款式任你选
- 方法参数和返回都是 同一个接口
基本材料,接口和实现
public interface Matter {
/**
* 场景;地板、地砖、涂料、吊顶
*/
String scene();
/**
* 品牌
*/
String brand();
/**
* 平米报价
*/
BigDecimal price();
}
public class DerFloor implements Matter {
@Override
public String scene() {
return "地板";
}
@Override
public String brand() {
return "德尔(Der)";
}
@Override
public BigDecimal price() {
return new BigDecimal(119);
}
}
public class OneCeiling implements Matter {
@Override
public String scene() {
return "吊顶";
}
@Override
public String brand() {
return "自家设计";
}
@Override
public BigDecimal price() {
return new BigDecimal(260);
}
}
菜单接口和实现,参数和返回相同
public interface IMenu {
/**
* 吊顶
*/
IMenu appendCeiling(Matter matter);
/**
* 地板
*/
IMenu appendFloor(Matter matter);
/**
* 明细
*/
String getDetail();
}
public class DecorationMenu implements IMenu {
private List<Matter> list = new ArrayList<>(); // 装修清单
private BigDecimal price = BigDecimal.ZERO; // 装修价格
private BigDecimal area; // 面积
private String grade; // 装修等级;豪华欧式、轻奢田园、现代简约
//私有构造方法
private DecorationMenu() {
}
//构造传递,面积 和 风格
public DecorationMenu(Double area, String grade) {
this.area = new BigDecimal(area.toString());
this.grade = grade;
}
//添加吊顶。参数是:基本装修物料。返回是本类
@Override
public IMenu appendCeiling(Matter matter) {
list.add(matter);
//区域 乘 区域 价格
price = price.add(area.multiply(matter.price()));
return this;
}
//添加地板
@Override
public IMenu appendFloor(Matter matter) {
list.add(matter);
price = price.add(area.multiply(matter.price()));
return this;
}
@Override
public String getDetail() {
list.stream().forEach(v -> System.out.println(v.brand() + v.scene() + ":" + v.price() + "元"));
System.out.println(grade + "共计:" + price + "元");
return "1";
}
}
建造者类,创建菜单接口的实现对象
- 并且 进行测试
public class Builder {
//参数为区域,创建 装饰包 类,多次调用其方法。
public IMenu levelOne(Double area) {
return new DecorationMenu(area, "豪华欧式")
.appendCeiling(new OneCeiling())
.appendFloor(new DerFloor());
}
public static void main(String[] args) {
Builder builder = new Builder();
// 豪华欧式
builder.levelOne(10D).getDetail();
}
}
自家设计吊顶:260元
德尔(Der)地板:119元
豪华欧式共计:3790.0元
2. 原型模式 试卷复制,题和选项打乱
- 因为只有一题,所以 打乱暂时无意义
- 答案为 JAVA2 HE,在每题中的选项不一样。
考生:花花[ChoiceQuestion(name=JAVA所定义的版本中不包括, option={A=JAVA2 HE, B=JAVA2 SE, C=JAVA2 Card, D=JAVA2 EE, E=JAVA2 ME}, key=A)]
考生:豆豆[ChoiceQuestion(name=JAVA所定义的版本中不包括, option={A=JAVA2 SE, B=JAVA2 Card, C=JAVA2 HE, D=JAVA2 EE, E=JAVA2 ME}, key=C)]
存题的类
@AllArgsConstructor
@Data
public class ChoiceQuestion {
private String name; // 题目
private Map<String, String> option; // 选项;A、B、C、D
private String key;
}
- option里的 Map的 key,会被打乱。如原来是:
- A:1 B:2 C:3 D:4 ,答案为:C:3,key为 C
- A:4 B:2 C:1 D:3,答案为:D:3,key变为 D
选择 乱序工具类
public class TopicRandomUtil {
@Data
@NoArgsConstructor
@AllArgsConstructor
public static class Topic {
//这里name,题目并没有存储
private Map<String, String> option; // 选项;A、B、C、D
private String key; // 答案;B
}
/**
* 乱序Map元素,记录对应答案key
*
* @param option 题目的选项。如:A——>1,B——>2,C——>3,D——>4 。
* @param key 答案。如:C
* @return Topic 乱序后的题 和 答案
*/
static public Topic random(Map<String, String> option, String key) {
//获取到。如:A B C D
Set<String> keySet = option.keySet();
//创建成 list,就是乱序后的
ArrayList<String> keyList = new ArrayList<String>(keySet);
//随机置换。如:ABCD,洗牌 变成:BACD
Collections.shuffle(keyList);
//创建 hashMap。因为是A B C D 的string,会自动排序
HashMap<String, String> optionNew = new HashMap<String, String>();
//乱序后选项,一个一个的取
int idx = 0;
//新的答案
String keyNew = "";
//遍历原来的 选项
for (String next : keySet) {
//洗牌后的选项,BACD,取第一个为 B
String randomKey = keyList.get(idx++);
//如果参数的 key(原本的答案) 和 next相同。原来的答案 遍历到了。
//那么新的 答案,就是:现在这一个选项。原来 A B C D,答案为B。现在 BACD,答案A。
//再次解释:只是 乱序了 key,key的选项没有乱序。那key乱序后,如何排好的,string 类型的A B C 自动排的
if (key.equals(next)) {
//新的正确答案,给记录上
keyNew = randomKey;
}
//放入 到 新的map,用 新的选项 ,对应为:原来 A->1 ,现在要把 1 取出来。
//原来的 选项一个一个遍历。现在放入是 用置换过的 一个一个遍历。
//randomKey是 置换过得,结果key自动排序的时候,value 也响应的改变
optionNew.put(randomKey, option.get(next));
}
return new Topic(optionNew, keyNew);
}
}
public static void main(String[] args) throws Exception {
//创建 hashMap。因为是A B C D 的 string,会自动排序
HashMap<String, String> h = new HashMap<String, String>();
h.put("C", "11");
h.put("B", "dfd");
h.put("b", "dfd");
h.put("D", "33");
h.put("A", "gf");
h.put("a", "dfd");
System.out.println(h.toString());
//{A=gf, a=dfd, B=dfd, b=dfd, C=11, D=33} 。A B C D ,key会自动排序好
}
供拷贝的题库类
@Setter
public class QuestionBank implements Cloneable {
private String candidate; // 考生
private String number; // 考号
//选择
private ArrayList<ChoiceQuestion> choiceQuestionList = new ArrayList<>();
//选择题,加入到 选择题的列表。类似建造者
public QuestionBank append(ChoiceQuestion choiceQuestion) {
choiceQuestionList.add(choiceQuestion);
return this;
}
@Override
public Object clone() throws CloneNotSupportedException {
//获取一个 clone对象
QuestionBank questionBank = (QuestionBank) super.clone();
//选择题 重新赋值。依然用 clone 方法
questionBank.choiceQuestionList = (ArrayList<ChoiceQuestion>) choiceQuestionList.clone();
// 题目乱序
Collections.shuffle(questionBank.choiceQuestionList);
// 获取所有的 选择题
ArrayList<ChoiceQuestion> cList = questionBank.choiceQuestionList;
//遍历 所有选择题
for (ChoiceQuestion question : cList) {
//使用 乱序方法
TopicRandomUtil.Topic random = TopicRandomUtil.random(question.getOption(), question.getKey());
//乱序后的 选项赋值
question.setOption(random.getOption());
//乱序后的 答案赋值
question.setKey(random.getKey());
}
return questionBank;
}
@Override
public String toString() {
return "考生:" + candidate + choiceQuestionList.toString();
}
}
题库管理,类似于建造者
public class QuestionManage {
private QuestionBank questionBank = new QuestionBank();
//构造 创建
public QuestionManage() {
questionBank.append(new ChoiceQuestion("JAVA所定义的版本中不包括", new HashMap<String, String>() {{
put("A", "JAVA2 EE");
put("B", "JAVA2 Card");
put("C", "JAVA2 ME");
put("D", "JAVA2 HE");
put("E", "JAVA2 SE");
}}, "D"))/*.append(new ChoiceQuestion("下列说法正确的是", new HashMap<String, String>() {{
put("A", "JAVA程序的main方法必须写在类里面");
put("B", "JAVA程序中可以有多个main方法");
put("C", "JAVA程序中类名必须与文件名一样");
put("D", "JAVA程序的main方法中如果只有一条语句,可以不用{}(大括号)括起来");
}}, "A"))*/;
}
public static void main(String[] args) throws Exception {
//当前类
QuestionManage q = new QuestionManage();
System.out.println(q.createPaper("花花", "1000001921032"));
System.out.println(q.createPaper("豆豆", "1000001921051"));
}
public String createPaper(String candidate, String number) throws CloneNotSupportedException {
//调用 clone的方法
QuestionBank q = (QuestionBank) questionBank.clone();
//考生姓名赋值
q.setCandidate(candidate);
q.setNumber(number);
return questionBankClone.toString();
}
}