您的位置:首页 > 编程语言 > Java开发

Java常用GoF设计模式之一单例模式

2016-10-21 16:54 375 查看
单例模式概念及特点

  java中单例模式是一种常见的设计模式,单例模式分三种:懒汉式单例、饿汉式单例、登记式单例(应用比较少,不做介绍)三种。

  单例模式有一下特点:

  1、单例类只能有一个实例。

  2、单例类必须自己自己创建自己的唯一实例。
  3、单例类必须给所有其他对象提供这一实例。

单例的使用场景

懒汉式单例模式一(方法加锁):

package cn.gof.singleton;
/**
* @author zhaopan
* @date 2016年10月21日 上午10:11:01
* 单例模式--懒汉式
* 优点:实现了简单的懒加载,真正用的时候才加载。
* 缺点:方法同步,调用效率稍低
*/
public class Singleton03 {

private static Singleton03 singleton03;
private Singleton03(){

}
public static synchronized Singleton03 getInstance(){
if (singleton03==null){
singleton03=new Singleton03();
}
return singleton03;
}
}
懒汉式单例模式二(双重检测锁机制):

package cn.gof.singleton;
/**
* @author zhaopan
* @date 2016年10月21日 上午10:27:50
* 双重检测锁机制的懒汉式
* 优点:将同步内容下放到if内部,提高了执行效率,不必每次获取对象时进行同步。
* 缺点:由于编译器优化和JVM底层内部模型原因,偶尔会出问题,不建议使用。JDK5.0以后版本若instance为volatile则可行
*
* 因为这里的同步只需在第一次创建实例时才同步,一旦创建成功,以后获取实例时就不需要同获取锁了)
* 但在Java中行不通,因为同步块外面的if (instance == null)可能看到已存在,但不完整的实例。
* JDK5.0以后版本若instance为volatile则可行
*/
public class Singleton04 {

//private static Singleton04 singleton04;
private volatile static Singleton04 singleton04;
private Singleton04(){

}
public static Singleton04 getInstance(){
if(singleton04==null){
synchronized (Singleton04.class) {
if(singleton04==null){
singleton04=new Singleton04();
}
}
}
return singleton04;
}
}


为什么用volatile可以保证线程安全参考:

单例模式与双重检测

用happen-before规则重新审视DCL

懒汉式单例模式三(静态内部类的方式):

package cn.gof.singleton;
/**
* @author zhaopan
* @date 2016年10月21日 上午10:46:40
* 静态内部类方式  实现懒汉式单例
* 优点:并发高效调用和延时加载的优势。
* 外部类没有static属性,则不会像俄汉式那样立即加载对象。
* 只有真正调用getInstance(),才会加载静态内部类。加载类时是线程安全的。
* 因为instance是static final 类型,保证了内存中只有这样一个实例存在,而且只能被赋值一次,从而保证线程安全性。
*
*/
public class Singleton05 {

private Singleton05(){

}
public static class SingletonInstance{
private static final Singleton05 SINGLETON05 =new Singleton05();
}

public static Singleton05 getInstance(){
return SingletonInstance.SINGLETON05;
}

}


饿汉式单例模式一(静态常量实现):

package cn.gof.singleton;
/**
* @author zhaopan
* @date 2016年10月21日 上午10:03:35
* 最简单的单例模式 饿汉式
*
* 优点:static,此时不会设计多个线程对象访问该对象的问题。变量会在类加载时初始化线程安全,调用效率高
* 缺点:无法实现延迟加载,如果是加载本类,而没有使用,则会造成资源浪费。
*/
public class Singleton01 {

private static final Singleton01 singleton01=new Singleton01();
//私有化构造方法
private Singleton01() {

}
//提供公有的获取实例的方法
public static Singleton01 getInstance(){
return singleton01;
}

}


饿汉式单例模式二(枚举实现):

package cn.gof.singleton;
/**
* @author zhaopan
* @date 2016年10月21日 上午10:37:48
* 枚举实现饿汉式单例模式
* 优点:1、 自由序列化;2、 保证只有一个实例(即使使用反射机制也无法多次实例化一个枚举量);3、 线程安全;
* 缺点:无延时加载
*/
public enum Singleton02 {

INSTANCE;
public void sayHi(){
System.out.println("我是枚举类型的单例");
}

}

测试效率:

package cn.gof.singleton;
/**
* @author zhaopan
* @date 2016年10月21日 下午4:57:02
*
*/
import java.util.concurrent.CountDownLatch;

/**
* 测试多线程环境下五种创建单例模式的效率
* @author liguodong
*
*/
public class ClientTest {
public static void main(String[] args) throws InterruptedException {

long start = System.nanoTime();
int threadNum = 10;
long sum=0;
final CountDownLatch count = new CountDownLatch(threadNum);
int[] ins=new int[10];
for(int j=0;j<100;j++) {
for(int i=0;i<10;i++) {
new Thread(new Runnable() {

@Override
public void run() {
// TODO Auto-generated method stub
for(int i=0;i<1000000;i++)
{
/**
* 饿汉式
*/
// Object o = Singleton01.getInstance(); //普通饿汉式118755558---143202450
//Object o = Singleton02.INSTANCE;//枚举式的111872193---163903002  大部分是11-13
/**
* 懒汉式
*/
//Singleton03 instance = Singleton03.getInstance(); //方法加同步关键字2126685034
//Singleton04 instance2 = Singleton04.getInstance();//将方法内部加同步464849105
//Object o = Singleton05.getInstance();  //内部类的方式  118638630--134209601
}
count.countDown();

}
}).start();
}

count.await();//main线程阻塞,直到计数器变为0,才会继续往下执行。
long end = System.nanoTime();

sum=sum+end-start;
}
System.out.println("总耗时:"+(sum/100));
}
}
比较两种模式?

单例对象  占用资源少,不需要延时加载:

枚举式好与 饿汉式

单例对象  占用资源大,需要延时加载:

静态内部类式好于懒汉式
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息