23种设计模式——单例模式(所属创建型模式)
本章节的源代码位于gitee上,想要下载的请点击单例设计模式
简单聊聊单例设计模式
聊到一种设计模式,首先我们需要知道它是干什么的,有什么用。首先来看看正常我们在进行实例化对象时的操作。
class Message{
public Message(){
System.out.println("构造方法");
}
public void printf(){
System.out.println("C3H2");
}
}
public class SingletonDesignDemo {
public static void main(String[] args) {
Message messageA = new Message();
Message messageB = new Message();
messageA.printf();
messageB.printf();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qwkE8JjO-1602297840783)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201010094149959.png)]
从这里我们可以看出我们已经创建了两个实例出来。那么单例模式是什么呢?
单例模式主要是控制实例化产生个数的设计操作。因为有些业务的需求(构建数据库连接)的时候,只允许一个提供一个实例化对象。那么这个时候我们就需要控制构造方法了,因为所有对象的实例化都需要调用构造方法,而如果没有构造方法,那么就无法从外部随意调用并进行实例化操作了。
单例设计模式分为两种,一种是懒汉式,一种为饿汉式。下面就这两种来展开说明。
懒汉式单例设计模式
懒汉式可以直接从字面上理解,就是非常懒,干什么都需要别人催促。就好像你家人在过节的时候。如果没人来,你家就不准备东西,别人来了才准备。
表现在代码上就是如下:
class Message2{
private Message2(){
System.out.println("构造方法");
}
private static Message2 message2 = null;
public static Message2 getInstance(){
if(message2==null){
message2 = new Message2();
}
return message2;
}
public void printf(){
System.out.println("C3H2");
}
}
public class LazySingleton {
public static void main(String[] args) {
Message1 messageA = Message1.getInstance();
Message1 messageB = Message1.getInstance();
messageA.printf();
messageB.printf();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-4BQ9Pvrp-1602297840791)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201010100024126.png)]
我们在使用单例的时候明显不同的是讲构造方法进行了私有化,一旦构造方法被私有化了之后,就不能在外部通过new来创建对象了。但是我们又需要将类实例化,于是我们提供了一个方法getInstance()
该方法就是用于于外部进行交互。因为没有被实例化就要被调用,所以该类要被static
进行修饰。这样就能保证所创建的对象只有一个了。
饿汉式单例设计模式
饿汉式相比于懒汉式有所不同,但是都大同小异。懒汉式是什么都等着别人上门了才处理。饿汉式则是我早早的就将东西准备好,就等你上门来。具体的表现形式如下:
class Message1{
private Message1(){
System.out.println("构造方法");
}
private static Message1 message1 = new Message1();
public static Message1 getInstance(){
return message1;
}
public void printf(){
System.out.println("C3H2");
}
}
public class EagerSingleton {
public static void main(String[] args) {
Message1 messageA = Message1.getInstance();
Message1 messageB = Message1.getInstance();
messageA.printf();
messageB.printf();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3Il89vno-1602297840795)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201010095933376.png)]
多线程对单例模式的影响
我们说一个程序开发出来之后不可能只有一个人使用,如果是这样那它的意义就不是很大了。因此无论干什么都需要考虑到多线程在并发时对程序的影响。那么单例模式会不会在多线程的冲击之下出现问题呢?多说无益,直接上代码。
class Message3{
private Message3(){
System.out.println("构造方法");
}
private static Message3 message3 = null;
public static Message3 getInstance(){
if(message3==null){
message3 = new Message3();
}
return message3;
}
public void printf(){
System.out.println("C3H2");
}
}
public class ThreadSingleton {
public static void main(String[] args) {
for(int i = 0 ; i < 5 ; i ++){
new Thread(()->{
Message3 message3 = Message3.getInstance();
message3.printf();
}).start();
}
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-XdTkVhjK-1602297840800)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201010101206516.png)]
这里使用的是懒汉式来进行的测试,可以看出在开辟了五个线程之后出现了五次调用构造方法,也就是说进行了五次实例化。因此多线程对懒汉式是有影响的,但是对饿汉式是否有影响呢?各位可以自己去测测。
如果这个问题不解决的话,那这种单例模式和不要有什么区别呢?于是我们想到了同步synchronized
,使用同步是否能解决问题呢?
public static synchronized Message3 getInstance(){
if(message3==null){
message3 = new Message3();
}
return message3;
}
直接在这个getInstance
方法上添加了同步的操作。是否可以完成呢?
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-kX5asU0i-1602297840804)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201010101629828.png)]
事实证明这是可以的,但是我们这样将同步添加到方法上,不会影响其执行速率吗?我们就不可以直接对判断处进行加锁操作吗?
public static Message3 getInstance(){
if(message3==null){
synchronized (Message3.class){
if(message3==null){
message3 = new Message3();
}
}
}
return message3;
}
我们这样就完成了对需要判断的语句进行加锁。这两种锁有什么区别呢?等后续有机会在细聊吧。只能说一个锁的是方法,一个是类,这不同的锁。想要详细了解锁,需要好好理解八锁问题。
反射对单例设计的影响
上面我们使用synchronized
解决了多线程对单例模式带来的影响。那么反射又会有什么影响呢?或者是有没有影响呢?
我们说反射是Java中一个非常重要的特征,可以说:没有反射,就没有Java现在的地位。那么这个反射有什么样的操作呢?
我个人认为反射最大的问题,就是它那个可以破解封装类的修饰权限的功能。这个功能可以让Java的封装直接崩溃。那么反映到单例上又是怎样的呢?
class Message4{
private Message4(){
System.out.println("构造方法");
}
private static Message4 message4 = null;
public static Message4 getInstance(){
if(message4==null){
message4 = new Message4();
}
return message4;
}
public void printf(){
System.out.println("C3H2");
}
}
public class ReflectSingleton {
public static void main(String[] args) throws Exception {
Message4 messageA = Message4.getInstance();
Class<?> clazz = Message4.class;
Constructor con = clazz.getDeclaredConstructor(null);
con.setAccessible(true);
Object object = con.newInstance();
Field messageField = clazz.getDeclaredField("message4");
messageField.setAccessible(true);
messageField.set(object,null);
Message4 messageB = Message4.getInstance();
}
}
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EgfSjSZM-1602297840808)(C:\Users\C3H2\AppData\Roaming\Typora\typora-user-images\image-20201010103836243.png)]
通过构造方法不仅可以直接调用构造方法,而且还可以修改其属性的值。
反射导致的问题是无法解决的,除法你使用了枚举类。那么枚举为什么能解决这个问题呢?请期待后续分享。