详解Java_Singleton(单例)模式的好处.docx
Singleton模式看起来简单,使用方法也很方便,但是真正用好,是非常不容易,需要对Java的类,线程,内存等概念有相当的了解。本文介绍了Singleton模式的使用方法及好处。 Java Singleton模式主要作用是保证在Java应用程序中,一个类Class只有一个实例存在。 在很多操作中,比如建立目录 数据库连接都需要这样的单线程操作。 还有, singleton能够被状态化; 这样,多个单态类在一起就可以作为一个状态仓库一样向外提供服务,比如,你要论坛中的帖子计数器,每次浏览一次需要计数,单态类能否保持住这个计数,并且能synchronize的安全自动加1,如果你要把这个数字永久保存到数据库,你可以在不修改单态接口的情况下方便的做到。 另外方面,Singleton也能够被无状态化。提供工具性质的功能, Java Singleton模式就为我们提供了这样实现的可能。使用Singleton的好处还在于可以节省内存,因为它限制了实例的个数,有利于Java垃圾回收(garbage collection)。 我们常常看到工厂模式中类装入器(class loader)中也用Singleton模式实现的,因为被装入的类实际也属于资源。 如何使用? 一般,Java Singleton模式通常有几种形式: 1. public class Singleton 2. 3. private Singleton() 4. 5. /在自己内部定义自己一个实例,是不是很奇怪? 6. /注意这是private 只供内部调用 7. 8. private static Singleton instance = new Singleton(); 9. 10. /这里提供了一个供外部访问本class的静态方法,可以直接访问 11. public static Singleton getInstance() 12. return instance; 13. 14. 第二种形式: 1. public class Singleton 2. 3. private static Singleton instance = null; 4. 5. public static synchronized Singleton getInstance() 6. 7. /这个方法比上面有所改进,不用每次都进行生成对象,只是第一次 8. /使用时生成实例,提高了效率! 9. if (instance=null) 10. instancenew Singleton(); 11. return instance; 12. 13. 使用Singleton.getInstance()可以访问单态类。 上面第二中形式是lazy initialization,也就是说第一次调用时初始Singleton,以后就不用再生成了。 注意到lazy initialization形式中的synchronized,这个synchronized很重要,如果没有synchronized,那么使用 getInstance()是有可能得到多个Singleton实例。关于lazy initialization的Singleton有很多涉及double-checked locking (DCL)的讨论,有兴趣者进一步研究。 一般认为第一种形式要更加安全些。 使用Java Singleton模式注意事项: 有时在某些情况下,使用Singleton并不能达到Singleton的目的,如有多个Singleton对象同时被不同的类装入器装载;在EJB这样的分布式系统中使用也要注意这种情况,因为EJB是跨服务器,跨JVM的。 我们以SUN公司的宠物店源码(Pet Store 1.3.1)的ServiceLocator为例稍微分析一下: 在Pet Store中ServiceLocator有两种,一个是EJB目录下;一个是WEB目录下,我们检查这两个ServiceLocator会发现内容差不多,都是提供EJB的查询定位服务,可是为什么要分开呢?仔细研究对这两种ServiceLocator才发现区别:在WEB中的 ServiceLocator的采取Singleton模式,ServiceLocator属于资源定位,理所当然应该使用Singleton模式。但是在EJB中,Singleton模式已经失去作用,所以ServiceLocator才分成两种,一种面向WEB服务的,一种是面向EJB服务的。 Java Singleton模式看起来简单,使用方法也很方便,但是真正用好,是非常不容易,需要对Java的类,线程,内存等概念有相当的了解。package sort;import java.util.Random;/* * 排序测试类 * * 排序算法的分类如下: 1.插入排序(直接插入排序、折半插入排序、希尔排序); 2.交换排序(冒泡泡排序、快速排序); * 3.选择排序(直接选择排序、堆排序); 4.归并排序; 5.基数排序。 * * 关于排序方法的选择: (1)若n较小(如n50),可采用直接插入或直接选择排序。 * 当记录规模较小时,直接插入排序较好;否则因为直接选择移动的记录数少于直接插人,应选直接选择排序为宜。 * (2)若文件初始状态基本有序(指正序),则应选用直接插人、冒泡或随机的快速排序为宜; * (3)若n较大,则应采用时间复杂度为O(nlgn)的排序方法:快速排序、堆排序或归并排序。 * */* * corporation 北京环亚 * author HDS * date Nov 19, 2009 10:43:44 AM * path sort * description JAVA排序汇总 */public class SortTest / /=产生随机数=/* * description 生成随机数 * date Nov 19, 2009 * author HDS * return int */public int createArray() Random random = new Random();int array = new int10;for (int i = 0; i < 10; i+) arrayi = random.nextInt(100) - random.nextInt(100);/ 生成两个随机数相减,保证生成的数中有负数System.out.println("=原始序列=");printArray(array);return array;/* * description 打印出随机数 * date Nov 19, 2009 * author HDS * param data */public void printArray(int data) for (int i : data) System.out.print(i + " ");System.out.println();/* * description 交换相邻两个数 * date Nov 19, 2009 * author HDS * param data * param x * param y */public void swap(int data, int x, int y) int temp = datax;datax = datay;datay = temp;/* * 冒泡排序-交换排序的一种 * 方法:相邻两元素进行比较,如有需要则进行交换,每完成一次循环就将最大元素排在最后(如从小到大排序),下一次循环是将其他的数进行类似操作。 * 性能:比较次数O(n2),n2/2;交换次数O(n2),n2/4 * * param data * 要排序的数组 * param sortType * 排序类型 * return */public void bubbleSort(int data, String sortType) if (sortType.equals("asc") / 正排序,从小排到大/ 比较的轮数for (int i = 1; i < data.length; i+) / 数组有多长,轮数就有多长/ 将相邻两个数进行比较,较大的数往后冒泡for (int j = 0; j < data.length - i; j+) / 每一轮下来会将比较的次数减少if (dataj > dataj + 1) / 交换相邻两个数swap(data, j, j + 1); else if (sortType.equals("desc") / 倒排序,从大排到小/ 比较的轮数for (int i = 1; i < data.length; i+) / 将相邻两个数进行比较,较大的数往后冒泡for (int j = 0; j < data.length - i; j+) if (dataj < dataj + 1) / 交换相邻两个数swap(data, j, j + 1); else System.out.println("您输入的排序类型错误!");printArray(data);/ 输出冒泡排序后的数组值/* * 直接选择排序法-选择排序的一种 方法:每一趟从待排序的数据元素中选出最小(或最大)的一个元素, * 顺序放在已排好序的数列的最后,直到全部待排序的数据元素排完。 性能:比较次数O(n2),n2/2 交换次数O(n),n * 交换次数比冒泡排序少多了,由于交换所需CPU时间比比较所需的CUP时间多,所以选择排序比冒泡排序快。 * 但是N比较大时,比较所需的CPU时间占主要地位,所以这时的性能和冒泡排序差不太多,但毫无疑问肯定要快些。 * * param data * 要排序的数组 * param sortType * 排序类型 * return */public void selectSort(int data, String sortType) if (sortType.endsWith("asc") / 正排序,从小排到大int index;for (int i = 1; i < data.length; i+) index = 0;for (int j = 1; j <= data.length - i; j+) if (dataj > dataindex) index = j;/ 交换在位置data.length-i和index(最大值)两个数swap(data, data.length - i, index); else if (sortType.equals("desc") / 倒排序,从大排到小int index;for (int i = 1; i < data.length; i+) index = 0;for (int j = 1; j <= data.length - i; j+) if (dataj < dataindex) index = j;/ 交换在位置data.length-i和index(最大值)两个数swap(data, data.length - i, index); else System.out.println("您输入的排序类型错误!");printArray(data);/ 输出直接选择排序后的数组值/* * 插入排序 方法:将一个记录插入到已排好序的有序表(有可能是空表)中,从而得到一个新的记录数增1的有序表。 性能:比较次数O(n2),n2/4 * 复制次数O(n),n2/4 比较次数是前两者的一般,而复制所需的CPU时间较交换少,所以性能上比冒泡排序提高一倍多,而比选择排序也要快。 * * param data * 要排序的数组 * param sortType * 排序类型 */public void insertSort(int data, String sortType) if (sortType.equals("asc") / 正排序,从小排到大/ 比较的轮数for (int i = 1; i < data.length; i+) / 保证前i+1个数排好序for (int j = 0; j < i; j+) if (dataj > datai) / 交换在位置j和i两个数swap(data, i, j); else if (sortType.equals("desc") / 倒排序,从大排到小/ 比较的轮数for (int i = 1; i < data.length; i+) / 保证前i+1个数排好序for (int j = 0; j < i; j+) if (dataj < datai) / 交换在位置j和i两个数swap(data, i, j); else System.out.println("您输入的排序类型错误!");printArray(data);/ 输出插入排序后的数组值/* * 反转数组的方法 * * param data * 源数组 */public void reverse(int data) int length = data.length;int temp = 0;/ 临时变量for (int i = 0; i < length / 2; i+) temp = datai;datai = datalength - 1 - i;datalength - 1 - i = temp;printArray(data);/ 输出到转后数组的值/* * 快速排序 快速排序使用分治法(Divide and conquer)策略来把一个序列(list)分为两个子序列(sub-lists)。 步骤为: * 1. 从数列中挑出一个元素,称为 "基准"(pivot), 2. * 重新排序数列,所有元素比基准值小的摆放在基准前面,所有元素比基准值大的摆在基准的后面(相同的数可以到任一边)。在这个分割之后,该基准是它的最后位置。这个称为分割(partition)操作。 * 3. 递归地(recursive)把小于基准值元素的子数列和大于基准值元素的子数列排序。 * 递回的最底部情形,是数列的大小是零或一,也就是永远都已经被排序好了。虽然一直递回下去,但是这个算法总会结束,因为在每次的迭代(iteration)中,它至少会把一个元素摆到它最后的位置去。 * * param data * 待排序的数组 * param low * param high * see SortTest#qsort(int, int, int) * see SortTest#qsort_desc(int, int, int) */public void quickSort(int data, String sortType) if (sortType.equals("asc") / 正排序,从小排到大qsort_asc(data, 0, data.length - 1); else if (sortType.equals("desc") / 倒排序,从大排到小qsort_desc(data, 0, data.length - 1); else System.out.println("您输入的排序类型错误!");/* * 快速排序的具体实现,排正序 * * param data * param low * param high */private void qsort_asc(int data, int low, int high) int i, j, x;if (low < high) / 这个条件用来结束递归i = low;j = high;x = datai;while (i < j) while (i < j && dataj > x) j-; / 从右向左找第一个小于x的数if (i < j) datai = dataj;i+;while (i < j && datai < x) i+; / 从左向右找第一个大于x的数if (i < j) dataj = datai;j-;datai = x;qsort_asc(data, low, i - 1);qsort_asc(data, i + 1, high);/* * 快速排序的具体实现,排倒序 * * param data * param low * param high */private void qsort_desc(int data, int low, int high) int i, j, x;if (low < high) / 这个条件用来结束递归i = low;j = high;x = datai;while (i < j) while (i < j && dataj < x) j-; / 从右向左找第一个小于x的数if (i < j) datai = dataj;i+;while (i < j && datai > x) i+; / 从左向右找第一个大于x的数if (i < j) dataj = datai;j-;datai = x;qsort_desc(data, low, i - 1);qsort_desc(data, i + 1, high);/* * 二分查找特定整数在整型数组中的位置(递归) 查找线性表必须是有序列表 * * paramdataset * paramdata * parambeginIndex * paramendIndex * returnindex */public int binarySearch(int dataset, int data, int beginIndex,int endIndex) int midIndex = (beginIndex + endIndex) >>> 1; / 相当于mid = (low + high)/ / 2,但是效率会高些if (data < datasetbeginIndex | data > datasetendIndex| beginIndex > endIndex)return -1;if (data < datasetmidIndex) return binarySearch(dataset, data, beginIndex, midIndex - 1); else if (data > datasetmidIndex) return binarySearch(dataset, data, midIndex + 1, endIndex); else return midIndex;/* * 二分查找特定整数在整型数组中的位置(非递归) 查找线性表必须是有序列表 * * paramdataset * paramdata * returnindex */public int binarySearch(int dataset, int data) int beginIndex = 0;int endIndex = dataset.length - 1;int midIndex = -1;if (data < datasetbeginIndex | data > datasetendIndex| beginIndex > endIndex)return -1;while (beginIndex <= endIndex) midIndex = (beginIndex + endIndex) >>> 1; / 相当于midIndex =/ (beginIndex +/ endIndex) / 2,但是效率会高些if (data < datasetmidIndex) endIndex = midIndex - 1; else if (data > datasetmidIndex) beginIndex = midIndex + 1; else return midIndex;return -1;/ /=测试=/public static void main(String args) SortTest ST = new SortTest();int array = ST.createArray();System.out.println("=冒泡排序后(正序)=");ST.bubbleSort(array, "asc");System.out.println("=冒泡排序后(倒序)=");ST.bubbleSort(array, "desc");array = ST.createArray();System.out.println("=选择排序后(正序)=");ST.selectSort(array, "asc");System.out.println("=选择排序后(倒序)=");ST.selectSort(array, "desc");array = ST.createArray(); System.out.println("=插入排序后(正序)="); ST.insertSort(array, "asc"); System.out.println("=插入排序后(倒序)="); ST.insertSort(array, "desc"); array = ST.createArray(); System.out.println("=快速排序后(正序)="); ST.quickSort(array, "asc"); ST.printArray(array); System.out.println("=快速排序后(倒序)="); ST.quickSort(array, "desc"); ST.printArray(array); System.out.println("=数组二分查找="); System.out.println("您要找的数在第" + ST.binarySearch(array, 74)+ "个位子。(下标从0计算)");1、冒泡排序 Bubble Sort最简单的排序方法是冒泡排序方法。这种方法的基本思想是,将待排序的元素看作是竖着排列的“气泡”,较小的元素比较轻,从而要往上浮。在冒泡排序算法中我们要对这个“气泡”序列处理若干遍。所谓一遍处理,就是自底向上检查一遍这个序列,并时刻注意两个相邻的元素的顺序是否正确。如果发现两个相邻元素的顺序不对,即“轻”的元素在下面,就交换它们的位置。显然,处理一遍之后,“最轻”的元素就浮到了最高位置;处理二遍之后,“次轻”的元素就浮到了次高位置。在作第二遍处理时,由于最高位置上的元素已是“最轻”元素,所以不必检查。一般地,第i遍处理时,不必检查第i高位置以上的元素,因为经过前面i-1遍的处理,它们已正确地排好序。这个算法可实现如下。算法如下:/*冒泡排序*paramsrc待排序数组*/void doBubbleSort(int src)int len=src.length;for(int i=0;i<len;i+)for(int j=i+1;j<len;j+)int temp;if(srci>srcj)temp=srcj;srcj=srci;srci=temp; printResult(i,src); 2、选择排序 Selection Sort选择排序的基本思想是:对待排序的记录序列进行n-1遍的处理,第1遍处理是将L1.n中最小者与L1交换位置,第2遍处理是将L2.n中最小者与L2交换位置,.,第i遍处理是将Li.n中最小者与Li交换位置。这样,经过i遍处理之后,前i个记录的位置就已经按从小到大的顺序排列好了。当然,实际操作时,也可以根据需要,通过从待排序的记录中选择最大者与其首记录交换位置,按从大到小的顺序进行排序处理。算法如下:/*选择排序*paramsrc待排序的数组*/void doChooseSort(int src)int len=src.length;int temp;for(int i=0;i<len;i+)temp=srci;int j;int samllestLocation=i;/最小数的下标for(j=i+1;j<len;j+)if(srcj<temp)temp=srcj;/取出最小值samllestLocation=j;/取出最小值所在下标srcsamllestLocation=srci;srci=temp;printResult(i,src);3、插入排序 Insertion Sort插入排序的基本思想是,经过i-1遍处理后,L1.i-1己排好序。第i遍处理仅将Li插入L1.i-1的适当位置,使得L1.i又是排好序的序列。要达到这个目的,我们可以用顺序比较的方法。首先比较Li和Li-1,如果Li-1 Li 騆1.i已排好序,第i遍处理就结束了;否则交换Li与Li-1的位置,继续比较Li-1和Li-2,直到找到某一个位置j(1ji-1),使得Lj Lj+1时为止。简言之,插入排序就是每一步都将一个待排数据按其大小插入到已经排序的数据中的适当位置,直到全部插入完毕。插入排序方法分直接插入排序和折半插入排序两种,这里只介绍直接插入排序,折半插入排序留到“查找”内容中进行。图1演示了对4个元素进行直接插入排序的过程,共需要(a),(b),(c)三次插入。图1 对4个元素进行插入排序在下面的插入排序算法中,为了写程序方便我们可以引入一个哨兵元素L0,它小于L1.n中任一记录。所以,我们设元素的类型ElementType中有一个常量-,它比可能出现的任何记录都小。如果常量-不好事先确定,就必须在决定Li是否向前移动之前检查当前位置是否为1,若当前位置已经为1时就应结束第i遍的处理。另一个办法是在第i遍处理开始时,就将Li放入L0中,这样也可以保证在适当的时候结束第i遍处理。下面的算法中将对当前位置进行判断。/*插入排序(WHILE循环实现)*paramsrc待排序数组*/void doInsertSort1(int src)int len=src.length;for(int i=1;i<len;i+) int temp=srci;int j=i;while(srcj-1>temp)srcj=srcj-1;j-;if(j<=0)break;srcj=temp;printResult(i+1,src);/*插入排序(FOR循环实现)*paramsrc待排序数组*/void doInsertSort2(int src)int len=src.length;for(int i=1;i<len;i+)int j;int temp=srci;for(j=i;j>0;j-)if(srcj-1>temp)srcj=srcj-1;else/如果当前的数,不小前面的数,那就说明不小于前面所有的数,/因为前面已经是排好了序的,所以直接通出当前一轮的比较break;srcj=temp;printResult(i,src);/*冒泡排序*paramsrc待排序数组.优化*/void doBubbleSort(int src)int len=src.length;for(int i=0;i<len;i+)for(int j=len;j>i;j-)int temp;if(srci>srcj)temp=srcj;srcj=srci;srci=temp; printResult(i,src);