前语
初识享元形式(Flyweight Pattern)的时分觉得没啥弯弯绕
属于结构型形式,是一种目标池技术,首要用于削减创立目标数量,以削减内存占用和进步功能
享,即同享;元,即目标
再看它的关键完成:用HashMap
存储这些同享目标
很好理解嘛
形式初识
在某鸟教程中了解了享元形式
把它的比如简化下:“用2种颜色来画出分布于不同方位的圆”
(声明:本文不是说某鸟的比如不好啊,只是想经过该例渐进学习享元形式)
定义一个IShape
并完成它
// 笼统享元人物(Flyweight)
public interface IShape {
void draw();
}
// 具体享元(Concrete Flyweight)
public class Circle implements IShape {
private String color;
private int num, x, y;
public Circle(String color) {
this.color = color;
}
public void setNum(int num) {
this.num = num;
}
public void setX(int x) {
this.x = x;
}
public void setY(int y) {
this.y = y;
}
@Override
public void draw() {
System.out.printf("画第%d个圆: %s色, [x, y] = [%d, %d]\n", num, color, x, y);
}
}
创立享元工厂
public class ShapeFactory {
private static Map<String, IShape> circleMap = new HashMap<>();
public static IShape getCircle(String color) {
Circle circle;
if (circleMap.containsKey(color)) {
circle = (Circle) circleMap.get(color);
} else {
circle = new Circle(color);
System.out.println("*创立" + color + "色圆*");
circleMap.put(color, circle);
}
return circle;
}
}
OK,调用一下
public class FlyweightPatternDemo {
public static void main(String[] args) {
Circle circle1 = (Circle) ShapeFactory.getCircle("红");
circle1.setNum(1);
circle1.setX(1);
circle1.setY(1);
circle1.draw();
Circle circle2 = (Circle) ShapeFactory.getCircle("绿");
circle2.setNum(2);
circle2.setX(2);
circle2.setY(2);
circle2.draw();
// 复用
Circle circle3 = (Circle) ShapeFactory.getCircle("红");
circle3.setNum(3);
circle3.setX(3);
circle3.setY(3);
circle3.draw();
}
}
运转结果
内部状况 & 外部状况
能够看到Circle3
的确没有创立新的circle
目标,完成了复用
但感觉怪怪的
再一看
这circle3
把circle1
的序号(num)和方位(xy)也改了呀
本来想复制圆,整成了剪切圆
所以,不是所有的部分都能同享
享元形式的确也做了定义,它把一个目标的状况分为内部状况和外部状况
内部状况:不变的能够同享的部分
外部状况:随环境改变、不能同享的部分
想的还挺周到
改造下代码,把num、x、y
抽取到新增的Location
目标
public class Location {
private int num, x, y;
public Location(int num, int x, int y) {
this.num = num;
this.x = x;
this.y = y;
}
public int getNum() {
return num;
}
public int getX() {
return x;
}
public int getY() {
return y;
}
}
改造IShape
和Circle
public interface IShape {
// 传入外部状况location
void draw(Location location);
}
public class Circle implements IShape {
private String color;
public Circle(String color) {
this.color = color;
}
@Override
public void draw(Location location) {
System.out.printf("画第%d个圆: %s色, [x, y] = [%d, %d]\n", location.getNum(), color, location.getX(), location.getY());
}
}
OK,调用一下
public class FlyweightPatternDemo {
public static void main(String[] args) {
Circle circle1 = (Circle) ShapeFactory.getCircle("红");
circle1.draw(new Location(1, 1, 1));
Circle circle2 = (Circle) ShapeFactory.getCircle("绿");
circle2.draw(new Location(2, 2, 2));
// 复用
Circle circle3 = (Circle) ShapeFactory.getCircle("红");
circle3.draw(new Location(3, 3, 3));
}
}
运转结果
线程安全问题
诶嘿,又发现了一个点
HashMap
这玩意线程不安全啊
线程不安全的影响
用10个线程获取红色圆,测验上面经过HashMap
完成的ShapeFactory
public class FlyweightPatternThreadDemo {
public static void main(String[] args) {
for (int i = 0; i < 10; i++) {
new Thread(() -> {
Circle circle1 = (Circle) ShapeFactory.getCircle("红");
Circle circle2 = (Circle) ShapeFactory.getCircle("红");
System.out.println(circle1 == circle2);
}).start();
}
}
}
// 输出:
// true
// true
// true
// true
// false -- 阐明目标不一样了
// false -- 阐明目标不一样了
// true
// false -- 阐明目标不一样了
// false -- 阐明目标不一样了
// true
因为线程不安全,所以会存在重复创立红色圆的状况
经过如下时序图阐明:
对策
Java中的String常量池、数据库连接池都运用了享元形式
咋保证的线程安全呢?
挑个了解的柿子捏下吧:Java String
哦~字符串常量池是一个固定大小的Hashtable
已然Hashtable
能够那ConcurrentHashMap
也能一战咯?
可是,把ShapeFactory
里的circleMap
完成换成这俩都不可
public class ShapeFactory {
private static Map<String, IShape> circleMap = new ConcurrentHashMap<>();
// private static Map<String, IShape> circleMap = new Hashtable<>();
public static IShape getCircle(String color) {
Circle circle;
if (circleMap.containsKey(color)) {
circle = (Circle) circleMap.get(color);
} else {
circle = new Circle(color);
circleMap.put(color, circle);
}
return circle;
}
}
// 输出:
// true
// true
// true
// true
// false -- 阐明目标不一样了
// true
// true
// false -- 阐明目标不一样了
// true
// true
因为containsKey()
和put()
不是原子操作?
ConcurrentHashMap.putIfAbsent()
是原子操作,整一个:
public class ShapeFactory {
private static Map<String, IShape> circleMap = new ConcurrentHashMap<>();
public static IShape getCircle(String color) {
return circleMap.putIfAbsent(color, new Circle(color));
}
}
// 输出:
// true
// true
// true
// true
// false -- 还是不可
// true
// true
// true
// true
// true
为啥还是不可呢?
因为 Thread1 和 Thread2 还是能够同时进ShapeFactory.getCircle()
:
只能给ShapeFactory.getCircle()
加锁了
public class ShapeFactory {
private static Map<String, IShape> circleMap = new ConcurrentHashMap<>();
public static synchronized IShape getCircle(String color) {
return circleMap.putIfAbsent(color, new Circle(color));
}
}
这下总行了吧?运转下
OMG!还!是!不!行!
为啥呢?!!!
哈哈哈synchronized
没毛病,问题出在ConcurrentHashMap.putIfAbsent()
putIfAbsent
方法在向ConcurrentHashMap
中增加键值对的时分,它会先判断该键值对是否现已存在
假如不存在(新的entry),那么会向map中增加该键值对,并返回null
假如已存在,那么不会覆盖已有的值,直接返回现已存在的值
这样改下就OK了
public class ShapeFactory {
// getCircle()加锁了,那么HashMap、Hashtable也是能够的
private static Map<String, IShape> circleMap = new ConcurrentHashMap<>();
public static synchronized IShape getCircle(String color) {
IShape circle = circleMap.putIfAbsent(color, new Circle(color));
if (circle == null) {
return circleMap.get(color);
}
return circle;
}
}
与单例形式的差异
简略来说
单例形式:一个 class 只能有一个目标
享元形式:一个 class 能够创立多个目标
总结
享元形式比较简略,首要用于削减创立目标数量,以削减内存占用和进步功能
运用享元形式要注意线程安全问题,个人认为线程不安全会造成重复创立目标,与享元形式削减创立目标数量的理念相悖
感谢阅览~不喜勿喷。欢迎评论、纠正