zoukankan      html  css  js  c++  java
  • 单例模式

    什么是单例模式

    单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。
    其实,GoF(《Design Patterns: Elements of Reusable Object-Oriented Software》(即后述《设计模式》一书),由 Erich Gamma、Richard Helm、Ralph Johnson 和 John Vlissides 合著(Addison-Wesley,1995)。这几位作者常被称为"四人组(Gang of Four)"。)对单例模式的定义是:保证一个类、只有一个实例存在,同时提供能对该实例加以访问的全局访问方法。
    这一模式的目的是使得类的一个对象成为系统中的唯一实例。要实现这一点,可以从客户端对其进行实例化开始。因此需要用一种只允许生成对象类的唯一实例的机制,“阻止”所有想要生成对象的访问。使用工厂方法来限制实例化过程。这个方法应该是静态方法(类方法),因为让类的实例去生成另一个唯一实例毫无意义。
    在应用系统开发中,我们常常有以下需求:
    1、在多个线程之间,比如servlet环境,共享同一个资源或者操作同一个对象
    2、在整个程序空间使用全局变量,共享资源
    3、大规模系统中,为了性能的考虑,需要节省对象的创建时间等等。

    因为Singleton模式可以保证为一个类只生成唯一的实例
    对象,所以这些情况,Singleton模式就派上用场了

    单例模式的三个要点

    1、某个类只能有一个实例;(那么怎么保证呢)
    2、它必须自行创建这个实例;(自己怎么创建?)
    3、它必须自行向整个系统提供这个实例。(怎么向整个系统提供这个实例)

    1、要保证某个类只有一个实例,所以构造函数得私有化,这样,外界程序就不能通过new()的方式获取该类的实例。
    2、自己创建这个实例,即在本类中提供静态方法,用于创建这个类的实例(这个方法能供外界调用),那既然这个公共进哪个静态方法能供外界调用,那就不免会多个代码调用这个方法,那岂不是还保证不了系统中始终只有一个这样的实例,所以还需注意,在那个静态方法中返回的实例必须是此类的静态属性变量。(即在本类中还需有一个该类对象的静态属性引

    代码体现——方式一(饿汉式)

    因为“一进来就创建对象”,形象称之为饿汉式。
    饿汉式,不存在线程安全问题

    
    public class Person {
    	//静态引用,在内存中只有一份。
    	public static final Person person = new Person();
    	private String name;
    	
    	
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    	//构造函数私有化
    	private Person() {
    	}
    	
    	//提供一个全局的静态方法
    	public static Person getPerson() {
    		return person;
    	}
    }
    
    
    public class MainClass {
    	public static void main(String[] args) {
    		Person2 per = Person2.getPerson();
    		Person2 per2 = Person2.getPerson();
    		per.setName("zhangsan");
    		per2.setName("lisi");		
    		System.out.println(per.getName());
    		System.out.println(per2.getName());	
    	}
    }
    
    

    运行结果

    lisi
    lisi
    

    代码体现——方式一(懒汉式)

    
    public class Person2 {
    	private String name;
    	//这里和饿汉式不同的是:这里只是个引用,并未创建对象
    	private static Person2 person;
    	
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    	//构造函数私有化
    	private Person2() {
    	}
    	
    	//提供一个全局的静态方法
    	public static Person2 getPerson() {
    		//判断是否为空
    		if(person == null) {
    			person = new Person2();
    		}
    		return person;
    	}
    }
    
    	public static void main(String[] args) throws Exception {
    		Person2 per = Person2.getPerson();
    		Person2 per2 = Person2.getPerson();
    		per.setName("zhangsan");
    		per2.setName("lisi");		
    		System.out.println(per.getName());
    		System.out.println(per2.getName());	
    	}
    }
    

    单线程下是没有问题的
    运行结果:

    lisi
    lisi
    

    但是在多线程下,就容易出现问题,问题就出现在Person2类的getPerson()方法上,因为这个方法分了三步:

    正是因为这样,才可能会出现线程安全问题
    比如:线程1刚判断我爱你是否为空,这时,线程一休眠了,此时,线程二进,进方法了,由于线程一正在休眠,还未创建对象,此时线程二刚好进来判断,判断的结果肯定为空,所以线程二自然也会去创建对象,这样程序执行下来,这个person就有两个实例了。
    代码:

    
    public class Person2 {
    	private String name;
    	//这里和饿汉式不同的是:这里只是个引用,并未创建对象
    	private static Person2 person;
    	
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    	//构造函数私有化
    	private Person2() {
    	}
    	
    	//提供一个全局的静态方法
    	public static Person2 getPerson() throws Exception {
    		//判断是否为空
    		if(person == null) {
    			Thread.sleep(1000);
    			person = new Person2();
    		}
    		return person;
    	}
    }
    
    
    public class MainClass {
    	static Runnable runnable = new Runnable() {
    		
    		public void run() {
    			Person2 per = null;
    			try {
    				per = Person2.getPerson();
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    			per.setName("zhangsan");
    			System.out.println(per.getName());
    		}
    	};
    	static Runnable runnable1 = new Runnable() {
    		
    		public void run() {
    			Person2 per = null;
    			try {
    				per = Person2.getPerson();
    			} catch (Exception e) {
    				// TODO Auto-generated catch block
    				
    				e.printStackTrace();
    			}
    			per.setName("lisi");
    			System.out.println(per.getName());
    		}
    	};
    	public static void main(String[] args) throws Exception {
    		/*Person2 per = Person2.getPerson();
    		Person2 per2 = Person2.getPerson();
    		per.setName("zhangsan");
    		per2.setName("lisi");		
    		System.out.println(per.getName());
    		System.out.println(per2.getName());	*/
              Thread thread1 =new Thread(runnable);
    	      Thread thread2 =new Thread(runnable1);
    
    			thread1.start();
    			thread2.start();
    
    	}
    }
    
    

    第一次执行结果:

    第二次执行

    解决方案:当然是用同步

    
    public class Person3 {
    	private String name;
    	private static Person3 person;
    	
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    	//构造函数私有化
    	private Person3() {
    	}
    	
    	//提供一个全局的静态方法,使用同步方法
    	public static synchronized Person3 getPerson() throws Exception {
    		if(person == null) {
    			Thread.sleep(1000);
    			person = new Person3();
    		}
    		return person;
    	}
    }
    
    
    public class MainClass {
    	static Runnable runnable = new Runnable() {
    		
    		public void run() {
    			Person3 per = null;
    			try {
    				per = Person3.getPerson();
    			} catch (Exception e) {
    				e.printStackTrace();
    			}
    			per.setName("zhangsan");
    			System.out.println(per.getName());
    		}
    	};
    	static Runnable runnable1 = new Runnable() {
    		
    		public void run() {a
    			Person3 per = null;
    			try {
    				per = Person3.getPerson();
    			} catch (Exception e) {
    				// TODO Auto-generated catch block
    				
    				e.printStackTrace();
    			}
    			per.setName("lisi");
    			System.out.println(per.getName());
    		}
    	};
    	public static void main(String[] args) throws Exception {
    		/*Person2 per = Person2.getPerson();
    		Person2 per2 = Person2.getPerson();
    		per.setName("zhangsan");
    		per2.setName("lisi");		
    		System.out.println(per.getName());
    		System.out.println(per2.getName());	*/
              Thread thread1 =new Thread(runnable);
    	      Thread thread2 =new Thread(runnable1);
    
    			thread1.start();
    			thread2.start();
    
    	}
    }
    
    

    运行结果:


    即要么全是zhangsan ,要么全是lisi。

    还有一种方式 叫做双重检查

    
    public class Person4 {
    	private String name;
    	private static Person4 person;
    	
    	public String getName() {
    		return name;
    	}
    
    	public void setName(String name) {
    		this.name = name;
    	}
    	
    	//构造函数私有化
    	private Person4() {
    	}
    	
    	//提供一个全局的静态方法
    	public static Person4 getPerson() {
    		if(person == null) {
    			synchronized (Person4.class) {
    				//这个就是第二次检查
                                   //(因为此时这个方法不是同步的,第一、二两个线程可能相差零点几毫秒依次进入方法,此时,若不做第二次检查,这两个线程还是各自new 自己的)
    				if(person == null) {
    					person = new Person4();
    				}
    			}
    			
    		}
    		return person;
    	}
    }
    
    
    
  • 相关阅读:
    正则表达式的妙用获得数组
    道不远人,话IO框架
    页面和控件的生命周期及其二者的关系
    深度解析 TypeConverter & TypeConverterAttribute (二)
    今天我顺利的在cnblogs安家,我要在这里写下辉煌
    AJAX or Ajax
    深度解析 TypeConverter & TypeConverterAttribute (一)
    SonarQube简单入门
    vuecli · Failed to download repo vuejstemplates/webpack: connect ETIMEDOUT 192.30.253.112:443
    Sqlmap使用教程
  • 原文地址:https://www.cnblogs.com/nnxud/p/9874173.html
Copyright © 2011-2022 走看看