目录
1.单例模式
1.1饿汉模式
1.2懒汉模式
1.3其他单例模式
2.原型模式
2.1 浅克隆
2.2深克隆
一、单例模式(Singleton)
保证一个类只有一个实例,并且提供一个访问该实例的全局访问点。
优点:由于只生成一个实例,减少了系统性能开销。可以在系统中设置全局的访问点,优化环境共享资源访问。如:可以设置一个单例类,负责所有数据表的映射处理。
常见的单例模式:
- 饿汉模式(线程安全,调用效率高,不能延迟加载)(主要)
- 懒汉模式(线程安全,调用效率不高,可以延迟加载)(主要)
- 双重检测锁(由于JVM底层内部模型原因,偶尔会出现问题,不推荐使用)
- 静态内部类式(线程安全,调用效率高,可以延迟加载)
- 枚举单例(线程安全,调用效率高,不能延迟加载)
单例模式UML图:
(1) 饿汉模式
问题:
- 如果只是加载本类、不调用本类的getInstance()方法,或永远不调用本类。就会造成资源的浪费。
- 线程安全,反射,反序列化,是不安全的。
class eh{
private static final eh texteh = new eh(); //(1)直接创建私有实例对象
private eh() {} //(2)私有无参的构造函数
public static eh getInstance() { //(3)唯一一个公用的调用实例的方法
return texteh;
}
}
为了防止序列化、反序列化对单例模式的破坏。可以使用下面方法,指定返回的是同一个对象。
private Object readResolve(){
return texteh;
}
(2) 懒汉模式
问题:资源利用效率高了。但是,每次调用getInstance()方法都要同步,并发效率较低。
class lh{
private static volatile lh textlh = null;//(1)使用volatile保证所有线程安全
private lh() {} //(2)私有无参构造
public static synchronized lh getInstance() { //(3)添加同步synchronized
//如果是第一次调用的,就创建。否则,使用原来创建的
if(textlh == null) {
textlh = new lh();
}
return textlh;
}
}
也可以使用同步代码块(效率比较低)
public static lh getInstance() { //(3)添加同步synchronized
synchronized(lh.class){
//如果是第一次调用的,就创建。否则,使用原来创建的
if(textlh == null) {
textlh = new lh();
}
}
return textlh;
}
(3)其他单例模式
- 双重检测锁:提高了利用效率,不必每一次都需要进行同步。只是同步第一次。其他的都不用同步。
class scjc{
private static scjc jc = null;
private scjc() {}
private static scjc getInstance() {
if(jc == null) {
scjc jcs;
synchronized (scjc.class) {
jcs = jc;
if(jcs == null) {
synchronized (scjc.class) {
if(jcs == null) {
jcs = new scjc();
}
}
}
}
}
return jc;
}
}
- 静态内部类:静态内部类不会立即加载,只要调用了getInstance()方法才会加载。tt的加载只会创建一次,只能被赋值一次,保证了线程的安全。兼备了高并发调用和延迟加载的优势。
线程安全,防止反射攻击,反序列化不安全。
class text{
private static class text2{ //静态内部类
private static final text tt = new text();
}
private text() {} //私有无参构造
public static text getInstance(){
return text2.tt;
}
}
为了防止反射的破坏,在私有构造器里面可以进行一个判断,检测对象的实例化。
private text() {
if(text2.tt != null){
throw new IllegalStateException();
}
}
- 枚举实现类式:枚举本身就是一个单例模式,有JVM的保障,避免了通过反射和反序列化的漏洞。
线程安全、支持序列化、反序列化安全,防止反射攻击。
public enum text {
tt; //直接调用,就可以获取单例对象
//添加自己的方法
public void getOther() {}
}
二、原型模式(Prototype)
将一个对象作为原型,克隆复制出多个和原型类似的新实例。克隆类似于new,但是不同于new。new创建新的对象属性采用的是默认值。克隆出来的对象属性和原型的完全一样,并且改变新克隆出来的对象不会影响原型对象。克隆的实现需要实现Cloneable接口和重写clone方法。克隆出来的是一个新的对象,不是单例。
克隆分为深克隆、浅克隆。
核心角色:
- (1)抽象原型类:规定抽象原型对象必须实现的接口(Cloneable)
- (2)具体原型类:实现抽象原型类的clone方法,它是可被复制的
- (2)访问类:使用具体原型类中的clone方法来复制新的对象
1、浅克隆:
浅克隆克隆的不是全部的原型,如果原型在克隆的过程中被修改了。那么克隆的是修改后的东西
public class text implements Cloneable{
private String name;
public text(String name) {
super();
this.name = name;
}
//补全get、set方法
@Override
protected Object clone() throws CloneNotSupportedException {
return (text)super.clone();//浅克隆
}
public static void main(String[] args){
text tt = new text("huang");
text clone = (text) tt.clone();//克隆
tt.setName("ling"); //克隆的时候被修改了属性
System.out.println(tt.getName());
System.out.println(clone.getName());
}
}
2、深克隆
深克隆的是对整个对象进行克隆。就算在克隆途中改变了属性,也不会影响克隆体。
public class text implements Cloneable{
private String name;
private Date date;
public text(String name,Date date) {
super();
this.name = name;
this.date = date;
}
//补全get、set方法
@Override
protected Object clone() throws CloneNotSupportedException {
Object obj = super.clone();
text tt = (text)obj;
tt.name = (String)this.name; //如果是一般数据类型。
tt.date = (Date)this.date.clone();//其他类型
return obj;//浅克隆
}
public static void main(String[] args) throws CloneNotSupportedException {
text tt = new text("huang",new Date(2020,1,3));
text clone = (text) tt.clone();//克隆
tt.setName("ling"); //属性被修改
tt.setDate(new Date(2020,1,2));
System.out.println(tt.getName()+","+tt.getDate());
System.out.println(clone.getName()+","+clone.getDate());
}
}