设计模式并不局限于某种语言,java,python,c++ 都有设计模式.。设计模式主要分为三种类型,一共23种。
- 创建型模式:单例模式、抽象工厂模式、原型模式、建造者模式、工厂模式
- 结构型模式:适配器模式、桥接模式、装饰模式、组合模式、外观模式、享元模式、代理模式
- 行为型模式:模版方法模式、命令模式、访问者模式、迭代器模式、观察者模式、中介者模式、备忘录模式、解释器模式(Interpreter模式)、状态模式、策略模式、职责链模式(责任链模式)
单例模式
所谓类的单例设计模式,就是采取一定的方法保证在整个的软件系统中,对某个类只能存在一个对象实例,并且该类只提供一个取得其对象实例的方法(静态方法)。
单例模式实现的规则:
- 把构造方法声明为
private
,确保只能由自己创建,避免外部创建实例或被子类继承从而创建额外实例。 - 定义一个私有静态的该类的实例作为该类的数据域,确保一个类只有一个实例。
- 定义一个静态工厂方法,外部类不能实例化一个该类的对象,所以只能用
static
的方法提供给其它类调用,并返回此单例的唯一实例。
通过单例模式可以确保一个类只有一个实例而且该实例易于被外界访问,从而方便对实例个数的控制并节约系统资源。如果系统中某个类的对象只能存在一个,那么单例模式是最好的解决方案。
比如Windows整个系统中,回收站就只有一个,无论怎样双击“回收站”图标,打开的回收站始终的唯一的。可以看出,回收站就是单例模式的应用。下面通过模拟Windows回收站的创建来说明单例模式的实现。
使用单例模式模拟Windows回收站的创建,其UML类图如下:
饿汉式写法
public class RecycleBin {
/*private final static RecycleBin instance;
static { // 静态代码块写法
instance = new RecycleBin();
}*/
private final static RecycleBin instance = new RecycleBin(); // 静态常量写法
public static RecycleBin getInstance() {
return instance;
}
private RecycleBin() { } // 私有化构造器
public void clearBin() {
System.out.println("清空回收站");
}
}
class SingletonTest {
public static void main(String[] args) {
RecycleBin r1 = RecycleBin.getInstance();
r1.clearBin();
RecycleBin r2 = RecycleBin.getInstance();
System.out.println(r1 == r2);
System.out.println(r1.hashCode());
System.out.println(r2.hashCode());
}
}
优点:写法简单,在类装载时就完成了实例化。避免了线程同步问题
缺点:在类装载时就完成了实例化,没有达到Lazy Loading
的效果;如果自始至终从未使用过这个实例,则会造成内存的浪费
懒汉式写法
写法一(线程不安全):
public class RecycleBin {
private RecycleBin() {}
private static RecycleBin instance;
public static RecycleBin getInstance() {
if (instance == null) {
instance = new RecycleBin();
}
return instance;
}
public void clearBin() {
System.out.println("清空回收站");
}
}
/*class SingletonTest { // 单线程下运行
public static void main(String[] args) {
RecycleBin r1 = RecycleBin.getInstance();
r1.clearBin();
RecycleBin r2 = RecycleBin.getInstance();
System.out.println(r1 == r2);
System.out.println(r1.hashCode());
System.out.println(r2.hashCode());
}
}*/
class MyThread implements Runnable {
@Override
public void run() {
RecycleBin r = RecycleBin.getInstance();
System.out.println(r + ": " + r.hashCode());
}
}
class SingletonTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
for (int i = 0; i < 10; i++) {
Thread t = new Thread(myThread);
t.start();
}
}
}
在单线程下运行结果是没问题的,但是在多线程下运行就会出问题,运行结果如下图,我们看到在程序的整个运行期间,是有两个RecycleBin的实例存在的
run :
这种写法的优点是起到了懒加载的效果;但缺点也很明显,只能在单线程下使用,多线程下不安全,可能导致产生多个实例
改进一:(同步方法)
在获取实例的静态方法上加上关键字synchronized就可以解决线程不安全的问题
public static synchronized RecycleBin getInstance() {
if (instance == null) {
instance = new RecycleBin();
}
return instance;
}
改进一虽然解决了解决了线程不安全的问题,但是效率太低了,每个线程在想获得类的实例时候,执行getInstance()方法都要进行同步。而其实这个方法只执行一次实例化代码就够了,后面的想获得该类实例, 直接return就行了。方法进行同步效率太低
改进二:(同步代码块)
在获取实例的静态方法里加上同步代码块
public static RecycleBin getInstance() {
if (instance == null) {
synchronized (RecycleBin.class) {
instance = new RecycleBin();
}
}
return instance;
}
运行结果:
改进二本意是想对改进一实现方式的进一步改进,因为前面同步方法效率太低, 改为同步产生实例化的的代码块。但是通过运行结果发现这种同步并不能起到线程同步。这是因为假如一个线程进入了if (instance== null)判断语句块,还未来得及往下执行, 另一个线程也通过了这个判断语句,这是就会有产生多个if语句块,因此便会产生多个实例
改进三:(双重检查)
对getInstance()方法做如下改进
public static RecycleBin getInstance() {
if (instance == null) {
synchronized (RecycleBin.class) {
if (instance == null) {
instance = new RecycleBin();
}
}
}
return instance;
}
进行多次测试后,结果都没问题
Double-Check概念是多线程开发中常使用到的,如代码中所示,我们进行了两 次if (instance== null)检查,这样就可以保证线程安全了。双重检查的改进中实例化代码只用执行一次,后面再次访问时,判断if (instance== null), 直接return实例化对象,也避免的反复进行方法同步。双重检查的优点是解决了懒加载问题,同时线程安全、效率较高。
其实结合改进二和改进三我们可以明显发现,在改进二中,我们的同步代码块放错了地方,把同步代码块放到 if 代码块的外面即可,即改进三的外层 if 是多于的。最终的改进如下:
public class RecycleBin {
private RecycleBin() {}
private static RecycleBin instance = null;
public static RecycleBin getInstance() {
// if (instance == null) {
synchronized (RecycleBin.class) {
if (instance == null) {
instance = new RecycleBin();
}
}
// }
return instance;
}
public void clearBin() {
System.out.println("清空回收站");
}
}
静态内部类写法
public class RecycleBin {
private RecycleBin() {}
public static RecycleBin getInstance() {
return RecycleBinInstance.INSTANCE;
}
private static class RecycleBinInstance { // 静态内部类写法
private static final RecycleBin INSTANCE = new RecycleBin();
}
}
class MyThread implements Runnable {
@Override
public void run() {
RecycleBin r = RecycleBin.getInstance();
System.out.println(r + ": " + r.hashCode());
}
}
class SingletonTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
for (int i = 0; i < 10; i++) {
Thread t = new Thread(myThread);
t.start();
}
}
}
在多线程下测试,发现结果没有任何问题
这种方式采用了类装载的机制来保证初始化实例时只有一个线程,静态内部类方式在RecycleBin类被装载时并不会立即实例化,而是在需要实例化 时,调用getInstance方法,才会装载RecycleBinInstance类,从而完成RecycleBin的 实例化。类的静态属性只会在第一次加载类的时候初始化,所以在这里,JVM帮助我们 保证了线程的安全性,在类进行初始化时,别的线程是无法进入的。
静态内部类的写法避免了线程不安全,利用静态内部类特点实现延迟加载(懒加载),效率高。
枚举写法
public enum RecycleBin {
INSTANCE;
public void clearBin() {
System.out.println("清空回收站");
}
}
class MyThread implements Runnable {
@Override
public void run() {
// RecycleBin r = RecycleBin.getInstance();
RecycleBin r = RecycleBin.INSTANCE;
System.out.println(r + ": " + r.hashCode());
}
}
class SingletonTest {
public static void main(String[] args) {
MyThread myThread = new MyThread();
for (int i = 0; i < 10; i++) {
Thread t = new Thread(myThread);
t.start();
}
}
}
多线程下测试,结果没有任何问题
这借助JDK1.5中添加的枚举来实现单例模式,不仅能避免多线程同步问题,而 且还能防止反序列化重新创建新的对象。
上述的几种方法,其中饿汉式
写法在类装载时就完成了实例化,线程安全,达不到懒加载的效果;懒汉式
的前两种改进都不推荐使用,推荐使用第三种的双重检查,既解决了线程不安全的问题,又达到了懒加载的效果;静态内部类
和枚举写法
也都能解决了线程不安全的问题,并达到了懒加载的效果。
实例分析——单例模式在网站系统开发中的应用
网站计数器是Web应用程序的一项基本功能,用于统计使用网站或者应用程序的人数,比如网站中“当前在线人数”这一功能的开发,可以反映出网站或者应用程序的受欢迎度,对于网站的可信度研究有一定的参考价值。可以不用把每次刷新记录都记录到数据库,使用单例模式即可保持计数器的值,并确保是线程安全的。
如果我们不使用单例模式,网站代码中凡是用到计数器的地方,都要new一个计数器对象,然后从数据库获取数据,对数据进行增加或减少,显示当前在线人数,然后再保存到数据库中。但是,这样实现是有问题的,如果多个用户同时登录,那么在这个时刻,通过计数器拿到的在线人数是相同的,然后将他们各自使用的计数器加1保存数据库中,此时在线人数相对于这些用户登录前就增加了1,显然和实际人数不符。所以,我们可以使用单例模式,把计数器设计为一个全局对象,所有人都共用同一份数据,这样就可以避免上述问题了。
使用单例模式设计网站计数器的UML类图:
单例类——Counter.java
public class Counter {
private static Counter instance = null;
private int currentCount = 0;
private Counter() {}
public int getCurrentCount() {
this.currentCount++;
return this.currentCount;
}
public static Counter getInstance() {
synchronized (Counter.class) {
if (instance == null) {
instance = new Counter();
}
}
return instance;
}
}
测试网页——index.jsp
<%@ page import="com.test.Counter" %>
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
<title>$Title$</title>
</head>
<body>
<%
Counter counter = Counter.getInstance();
out.print("您是本站的第" + counter.getCurrentCount() + "访客");
%>
</body>
</html>
使用Chrome进行测试:
再打开Edge测试: