单例模式

什么是单例模式

单例模式是一种对象创建型模式,使用单例模式,可以保证为一个类只生成唯一的实例对象。也就是说,在整个程序空间中,该类只存在一个实例对象。
其实,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;
	}
}


原文地址:https://www.cnblogs.com/nnxud/p/9874173.html