当前位置:首页 > 芯闻号 > 充电吧
[导读]在上一篇中,介绍完了学习设计模式的预备知识,现在开始,便正式学习23种设计模式了。根据《设计模式的艺术------软件开发人员内功修养之道》一书作者刘伟老师的观点,对于软件开发人员来说,c、c++、j

在上一篇中,介绍完了学习设计模式的预备知识,现在开始,便正式学习23种设计模式了。

根据《设计模式的艺术------软件开发人员内功修养之道》一书作者刘伟老师的观点,对于软件开发人员来说,c、c++、java语言、开发环境等等都是一些招式,而内功则是数据结构、算法、设计模式、软件工程等等。由此可见,想成为高手,学习设计模式(也就是深厚的内功)是必不可少的。

经典的23种设计模式可分为三类:创建型模式,结构型模式,行为型模式。

本文就介绍创建型模式的第一种模式:单例模式,由此作为23种设计模式的开始。套用作者刘伟老师的一句话,“从它开始为大家逐一展现设计模式的魅力”。哈哈

单例模式可以用于系统中只需要创建一个对象的类,比如windows下的任务管理器。单例模式可以确保对象的唯一性。

单例模式的定义如下:确保某个类只有一个实例,而且自行实例化并向系统提供这个实例,这个类成为单例类,它提供全局访问的方法。

以下是一个典型的单例模式的例子。

class TaskManager{
	private static TaskManager tm;
	
	private TaskManager(){
		
	}
	
	public static TaskManager getInstance(){
		if(tm == null)
			tm = new TaskManager();
		return tm;
	}
}


以下是单例模式的一个应用。


/*
 * 负载均衡器是一个单例类,用于处理并发请求时将请求分派给各个服务器
 * */
class LoadBalancer{
	private static LoadBalancer lb;
	private Vectorserverlist;
	
	private LoadBalancer(){
		serverlist = new Vector();
	}
	
	public static LoadBalancer getLoadBalancer(){
		if(lb == null){
			lb = new LoadBalancer();
		}
		return lb;
	}
	
	/*
	 * 获取一个服务器用于处理请求
	 * */
	public String getServer(){
		return serverlist.get(new Random().nextInt(serverlist.size()));
	}
	
	/*
	 * 添加一个服务器
	 * */
	public void addServer(String server){
		serverlist.add(server);
	}
	
	public void removeServer(String server){
		serverlist.remove(server);
	}
}


注释讲的还算清楚吧。接下来是这个单例类的测试代码:

public static void main(String[] args) {
		// TODO Auto-generated method stub
		LoadBalancer lb1 = LoadBalancer.getLoadBalancer();
		LoadBalancer lb2 = LoadBalancer.getLoadBalancer();
		
		System.out.println("lb1==lb2?"+(lb1==lb2));//测试取得的是否为同一个对象
		
		lb1.addServer("server1");
		lb2.addServer("server2");
		lb1.addServer("server3");
		lb2.addServer("server4");
		
		for(int i = 0;i < 20;i++){
			String server = lb1.getServer();
			System.out.println("请求分发至:"+server);
		}
	}
	/*输出:
	 *  lb1==lb2?true
		请求分发至:server1
		请求分发至:server3
		请求分发至:server1
		请求分发至:server2
		请求分发至:server4
		请求分发至:server3
		请求分发至:server3
		请求分发至:server2
		请求分发至:server2
		请求分发至:server3
		请求分发至:server2
		请求分发至:server4
		请求分发至:server1
		请求分发至:server4
		请求分发至:server4
		请求分发至:server3
		请求分发至:server1
		请求分发至:server3
		请求分发至:server2
		请求分发至:server3
	 * */


其实,以上的代码还是存在问题的,如:在多线程的情况下,当A线程调用getLoadBalancer()时,执行到new LoadBalancer()时由于创建工作比较耗时导致B线程对lb静态变量做出判断时仍然为null,此后A和B线程便都创建了一个LoadBalancer对象。此时,JVM中就有了两个LoadBalancer对象,那么LoadBalancer也就不在是单例类了。

那么,为了防止多线程的同步,可以使用synchronized关键字,getLoadBalancer()代码改为:


public static synchronized LoadBalancer getLoadBalancer(){
		if(lb == null){
			lb = new LoadBalancer();
		}
		return lb;
	}



这样又引发新的问题了,在多线程的环境下,每次调用该方法都要进行线程锁定判断,实在白费功夫。因此可以再改:

public static synchronized LoadBalancer getLoadBalancerImprove(){
		if(lb == null){
			synchronized(LoadBalancer.class){
				if(lb == null)
					lb = new LoadBalancer();
			}
		}
		return lb;
	}


需注意,这里要进行两次的null判断,道理和上面的同步差不多。另外,由于两次的null判断,所以需要将lb静态变量加个volatile关键字,阻止编译器代码优化。这样的设计叫做双重检查锁定。

以上讨论有一个共同点,那就是单例对象的创建是在需要使用时才创建的,也就是懒汉式单例了,或者说饱汉式单例。相对应的,则是饿汉式单例了:


class LoadBalancer{
	private static LoadBalancer lb = new LoadBalancer();
	private Vectorserverlist;
	
	private LoadBalancer(){
		serverlist = new Vector();
	}
	
	public static LoadBalancer getLoadBalancer(){
		if(lb == null){			
			lb = new LoadBalancer();			
		}
		return lb;
	}
	
	/*
	 * 获取一个服务器用于处理请求
	 * */
	public String getServer(){
		return serverlist.get(new Random().nextInt(serverlist.size()));
	}
	
	/*
	 * 添加一个服务器
	 * */
	public void addServer(String server){
		serverlist.add(server);
	}
	
	public void removeServer(String server){
		serverlist.remove(server);
	}
}



这样的设计,使得单例类在被加载时就创建一个单例对象,这样虽然增加了加载的时间,但是也省去了同步的工作。相比于饿汉式单例类,饱汉式单例类是使用时才加载并创建对象,这样的技术称为延迟加载技术。关于类加载的详细细节可以看看JVM知识或其他相关的知识,会有一个比较清晰的认识。



以上两种方式实现单例模式各有优缺点,一是同步,一是加载时间。那么有没有方式可以既解决同步的问题,又可以在加载时不创建对象,直到调用getLoadBalancer()时才创建呢?是的,有的。这就是IoDH(Initialization on Demand Holder)技术了,且看代码:

class LoadBalancer{
	private static LoadBalancer lb;
	private Vectorserverlist;
	
	private static class HolderClass{
		private final static  LoadBalancer instance = new LoadBalancer();
	}
	
	private LoadBalancer(){
		serverlist = new Vector();
	}
	public static LoadBalancer getLoadBalancer(){
		return HolderClass.instance;
	}
	
	/*
	 * 获取一个服务器用于处理请求
	 * */
	public String getServer(){
		return serverlist.get(new Random().nextInt(serverlist.size()));
	}
	
	/*
	 * 添加一个服务器
	 * */
	public void addServer(String server){
		serverlist.add(server);
	}
	
	public void removeServer(String server){
		serverlist.remove(server);
	}
}

巧妙地使用了静态内部类,使得单例类被加载时并没有创建对象,而调用getLoadBalancer()也不用做同步工作。


IoDH技术也是有缺陷的,它需要语言的支持。啊,缺陷真是无穷无尽,从头到尾都没有一个解决方案是完美的。可见,世间之事,难有十全十美。


讨论了这么多,也该对单例模式做一个思考与总结了。

首先,需要注意的是,单例到底是在哪个范围内只有一个对象。想了想,对于java,我的回答是,在JVM中单例类只有一个实例。由于构造方法是私有的,所以反射是完不成单例对象的创建了。另外,类加载器的双亲委托机制使得即使用不同的类加载器来加载单例类,最终也是只加载一次该单例类,而不会多次加载。

本站声明: 本文章由作者或相关机构授权发布,目的在于传递更多信息,并不代表本站赞同其观点,本站亦不保证或承诺内容真实性等。需要转载请联系该专栏作者,如若文章内容侵犯您的权益,请及时联系本站删除。
换一批
延伸阅读

9月2日消息,不造车的华为或将催生出更大的独角兽公司,随着阿维塔和赛力斯的入局,华为引望愈发显得引人瞩目。

关键字: 阿维塔 塞力斯 华为

加利福尼亚州圣克拉拉县2024年8月30日 /美通社/ -- 数字化转型技术解决方案公司Trianz今天宣布,该公司与Amazon Web Services (AWS)签订了...

关键字: AWS AN BSP 数字化

伦敦2024年8月29日 /美通社/ -- 英国汽车技术公司SODA.Auto推出其旗舰产品SODA V,这是全球首款涵盖汽车工程师从创意到认证的所有需求的工具,可用于创建软件定义汽车。 SODA V工具的开发耗时1.5...

关键字: 汽车 人工智能 智能驱动 BSP

北京2024年8月28日 /美通社/ -- 越来越多用户希望企业业务能7×24不间断运行,同时企业却面临越来越多业务中断的风险,如企业系统复杂性的增加,频繁的功能更新和发布等。如何确保业务连续性,提升韧性,成...

关键字: 亚马逊 解密 控制平面 BSP

8月30日消息,据媒体报道,腾讯和网易近期正在缩减他们对日本游戏市场的投资。

关键字: 腾讯 编码器 CPU

8月28日消息,今天上午,2024中国国际大数据产业博览会开幕式在贵阳举行,华为董事、质量流程IT总裁陶景文发表了演讲。

关键字: 华为 12nm EDA 半导体

8月28日消息,在2024中国国际大数据产业博览会上,华为常务董事、华为云CEO张平安发表演讲称,数字世界的话语权最终是由生态的繁荣决定的。

关键字: 华为 12nm 手机 卫星通信

要点: 有效应对环境变化,经营业绩稳中有升 落实提质增效举措,毛利润率延续升势 战略布局成效显著,战新业务引领增长 以科技创新为引领,提升企业核心竞争力 坚持高质量发展策略,塑强核心竞争优势...

关键字: 通信 BSP 电信运营商 数字经济

北京2024年8月27日 /美通社/ -- 8月21日,由中央广播电视总台与中国电影电视技术学会联合牵头组建的NVI技术创新联盟在BIRTV2024超高清全产业链发展研讨会上宣布正式成立。 活动现场 NVI技术创新联...

关键字: VI 传输协议 音频 BSP

北京2024年8月27日 /美通社/ -- 在8月23日举办的2024年长三角生态绿色一体化发展示范区联合招商会上,软通动力信息技术(集团)股份有限公司(以下简称"软通动力")与长三角投资(上海)有限...

关键字: BSP 信息技术
关闭
关闭