面试题目
请使用UML类图画出原型模式的核心角色;
原型设计模式的深拷贝和浅拷贝是什么,并写出深拷贝的两种方式的原码(重写clone方法实现深拷贝,使用序列化来实现深拷贝);
单例模式哪些是线程安全的;
本章目标
单例模式;(单子模式)
原型模式;
本章内容
单例模式
概念:
在任何情况下,该类的实例对象只能有一个;
1.1饿汉方式两种
第一种:静态变量
class Single{
//1.构造方法私有化,防止在类的外部可以创建该类
private Single(){}
//2.建立静态变量
private final static Single instance = new Single();
//3.建立对外共有的方法提供访问
public static Single GetInstance(){
return instance;
}
}
调用:
public class SingleType1 {
public static void main(String[] args) {
Single s1 = Single.GetInstance();
Single s2 = Single.GetInstance();
System.out.println(s1 == s2);
}
}
第二种:静态代码块
//使用静态代码块
class Single{
//1构造方法私有化,防止外部创建该类
private Single(){}
private static Single instance;
//2.建立静态代码块
static{
instance = new Single();
}
//3.提供对外共有的模式
public static Single GetInstance(){
return instance;
}
}
优缺点:
1.优点:这种写法比较简单,在类加载的时候就完成了实例化,避免了线程同步的问题,线程安全的模式;
2.缺点:在类装载的时候就完成实例化,没有达到Lazy Loading 的效果。如果从始至终从未使用过这个实例的话,则会造成内存的浪费;
3.这种方式基于classloader机制避免了多线程的同步问题,不过,instance在类装载的时候就实例化,在单例模式中大多数都是带哦用getInstance方法,但是导致类装载的原因有很多种,因为不能确定有其他的方式(或者其他的静态方法)导致类装载,这时候初始化instance就没有达到lazy loading的效果;
4.结论:这种单例模式可用,但是会造成内存资源的浪费;
1.2懒汉方式三种
第一种线程不安全
//使用懒汉,线程不安全
class Single{
//创建静态变量
private static Single instance;
//方法私有化
private Single(){}
//在使用该实例的时候创建该对象的实例
public static Single GetInstance(){
if(null == instance){
instance = new Single();
}
return instance;
}
}
测试
public class TManyTh implements Runnable {
@Override
public void run() {
//获取单例
Single s=Single.GetInstance();
System.out.println(s);
}
public static void main(String[] args) {
//
for (int i = 0; i <30 ; i++) {
TManyTh t=new TManyTh();
Thread thread=new Thread(t);
thread.start();
}
}
}
我们创建多个线程,发现并不能实现实例对象的唯一性;
这是因为如果两个线程都执行到 if(null == instance){的时候,如果其中有一个由于没有抢到时间片则不能向下执行,另一个线程抢到则检测没有对象,则又创建一个实例对象;
优点:实现了lazy loading 但是线程不安全,只能用在单线程中,不可用;
第二种线程安全,方法同步
//使用懒汉,线程安全
class Single{
//创建静态变量
private static Single instance;
//方法私有化
private Single(){}
//在使用该实例的时候创建该对象的实例
public synchronized static Single GetInstance(){
if(null == instance){
instance = new Single();
}
return instance;
}
}
优缺点:
1.解决了线程不安全问题;
2.效率太低了,每个线程在想获类的实例的时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例,直接return 就行了,方法同步效率太低;
3.结论:在实际开发中,不推荐使用这种方式;
第三种同步代码块
class Single{
//创建静态变量
private static Single instance;
//方法私有化
private Single(){}
//在使用该实例的时候创建该对象的实例
public static Single GetInstance(){
if(null == instance){
synchronized(Single.class) {
instance = new Single();
}
}
return instance;
}
}
测试
public class TManyTh implements Runnable {
@Override
public void run() {
//获取单例
Single s=Single.GetInstance();
System.out.println(s);
}
public static void main(String[] args) {
//
for (int i = 0; i <30 ; i++) {
TManyTh t=new TManyTh();
Thread thread=new Thread(t);
thread.start();
}
}
}
1.3双重检查
class Single{
//创建静态变量
private static Single instance;
//方法私有化
private Single(){}
//在使用该实例的时候创建该对象的实例
public static Single GetInstance(){
//先检查实例是否存在,如果不存在才进入下面的同步块
if(null == instance){
// //同步块,线程安全地创建实例
synchronized(Single.class) {
// //再次检查实例是否存在,如果不存在才真正地创建实例
if(null == instance){
instance = new Single();
}
}
}
return instance;
}
}
优点:即实现了懒加载,又保证了线程安全;
这种实现方式可以实现既线程安全地创建实例,而又不会对性能造成太大的影响。它只是在第一次创建实例的时候同步,以后就不需要同步了,从而加快了运行速度。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-otwXjHbk-1588994372597)(E:\AAA\新乡\java高级特性\设计模式\assets/QQ截图20191216195425.png)]
1.4静态内部类
class Single{
//方法私有化
private Single(){}
//静态内部类
private static class SingleInstance{
private static Single instance = new Single();
}
public static Single GetInstance(){
return SingleInstance.instance;
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-UifdaNzW-1588994372600)(E:\AAA\新乡\java高级特性\设计模式\assets/QQ截图20191216200324.png)]
1.5枚举
enum Single{
instance;
}
测试
public class SingleType1 {
public static void main(String[] args) {
for (int i = 0; i <50 ; i++) {
new Thread(new Runnable() {
@Override
public void run() {
Single s1 = Single.instance;
System.out.println(s1.hashCode());
}
}).start();
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kHsPE0Eo-1588994372602)(E:\AAA\新乡\java高级特性\设计模式\assets/QQ截图20191216200926.png)]
三、原型模式
思考:解决克隆狗狗的问题:
有一只狗狗,一岁了,黑白色,名字叫做tom,向克隆十只狗狗怎么做?
传统案例:
概念
原型模式,是用原型实例指定创建对象的种类,并且通过拷贝这些原型创建新的对象,其实就是从一个对象再创建另外一个可定制的对象,而且不需要知道任何创建的细节(实际上就是copy)。
使用原型模型解决格隆狗狗的问题:
package design.prototype;
public class Dog implements Cloneable{
private String name;
private int age;
private String color;
private String Address;
private Dog friend;
public Dog getFriend() {
return friend;
}
public Dog(String name, int age, String color, String address, Dog friend) {
this.name = name;
this.age = age;
this.color = color;
Address = address;
this.friend = friend;
}
public void setFriend(Dog friend) {
this.friend = friend;
}
public Dog(String name, int age, String color, String address) {
this.name = name;
this.age = age;
this.color = color;
Address = address;
}
public String getAddress() {
return Address;
}
public void setAddress(String address) {
Address = address;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
public String getColor() {
return color;
}
public void setColor(String color) {
this.color = color;
}
@Override
protected Object clone() throws CloneNotSupportedException {
return super.clone();
}
@Override
public String toString() {
return "Dog{" +
"name='" + name + '\'' +
", age=" + age +
", color='" + color + '\'' +
", Address='" + Address + '\'' +
'}';
}
public Dog(String name, int age, String color) {
this.name = name;
this.age = age;
this.color = color;
}
public Dog() {
}
}
测试代码
package design.prototype;
public class Test {
public static void main(String[] args) throws Exception {
Dog dog = new Dog("tom",1,"黑白色","德国牧羊犬");
Dog friend = new Dog("herry",2,"白色");
dog.setFriend(friend);
for (int i = 0; i <10 ; i++) {
Dog d = (Dog)dog.clone();
System.out.println(d+"--->"+d.getFriend()+"-->"+d.getFriend().hashCode());
}
}
}
只能实现值的复制则是浅拷贝;
深度拷贝:
第一种方式:
使用克隆每一个引用对象实现;
第二种范式:
package design.prototype;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
public class ObjClone {
public static <T> T cloneObj(T obj){
T retVal =null;
try {
// 将对象写入流中
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
// 从流中读出对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
retVal = (T) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return retVal;
}
}
rrayOutputStream();
ObjectOutputStream oos = new ObjectOutputStream(baos);
oos.writeObject(obj);
// 从流中读出对象
ByteArrayInputStream bais = new ByteArrayInputStream(baos.toByteArray());
ObjectInputStream ois = new ObjectInputStream(bais);
retVal = (T) ois.readObject();
} catch (Exception e) {
e.printStackTrace();
}
return retVal;
}
}