设计模式--单例模式

单例模式(Singleton Pattern)是 Java 中最简单的设计模式之一。这种类型的设计模式属于创建型模式,它提供了一种创建对象的最佳方式。

这种模式涉及到一个单一的类,该类负责创建自己的对象,同时确保只有单个对象被创建。这个类提供了一种访问其唯一的对象的方式,可以直接访问,不需要实例化该类的对象。

 

假定我们的设计中有A B C三个类,其关系如下:

public class A{
    
}

public class B{
    public void print(){
        A a =new A();
        System.out.println(a);
    }
}

public class C{
    public void print(){
        A a =new A();
        System.out.println(a);
    }
}

print方法的作用是打印出A类实例化后的地址

public class Main {
    public static void main(String[] args){
         new B().print();
         new C().print();
    }
}

这种情况下两个print方法的执行结果肯定是不同的,也就意味着这里创建了两个对象

输出结果

Test.A@5b2133b1
Test.A@33c7353a

如果我们在多线程模式中使用这种方式,那么对于性能的开销将会变得很大,因为每一个线程都会创建多个同样的实例,重复无数次操作。

那么如何保证该实例只被创建一次呢?方法如下:


//懒汉式创建a实例
public class A {
    private static A a;//如果在此处直接创建实例,那么该方式被称为饿汉式
    private A(){//声明构造函数为private的原因是为了保证A对象只能通过一种方法创建

    }
    public static A getInstance(){
        if (null==a)a=new A();//如果采用饿汉式,此处不需要判断a实例是否为空
        return a;
    }
}


public class B{
    public void print(){
        A a =A.getInstance();
        System.out.println(a);
    }
}



public class C {
    public void print(){
        A a =A.getInstance();
        System.out.println(a);
    }
}

 

输出结果:

Test.A@5b2133b1
Test.A@5b2133b1

这种模式也仅是适合单线程模式,因为在多线程模式下我们并不知道哪个线程会先被启动,如果其中先启动的线程因为意外导致在创建实例之前被休眠,那么后面启动的线程又会创建一个实例a,等待最先启动的线程获取CPU控制权的时候又会创建一个实例a。如下所示

多线程模式下的正常情况:

package Test;

//懒汉式创建a实例
public class A {
    private static A a;//如果在此处直接创建实例,那么该方式被称为饿汉式
    private A(){//声明构造函数为private的原因是为了保证A对象只能通过一种方法创建

    }
    public static A getInstance(){
        if (null==a){
        //创建实例a
            a=new A();
        }

        return a;
    }
}


package Test;

public class B implements Runnable{
    public void print(){
        A a =A.getInstance();
        System.out.println(a);
    }

    @Override
    public void run() {
        this.print();
    }
}


package Test;

public class C implements Runnable{
    public void print(){
        A a =A.getInstance();
        System.out.println(a);
    }

    @Override
    public void run() {
      this.print();
    }
}

这种方式下,正常情况的输出肯定是一样的。但是当创建实例之前线程意外休眠的话,情况就不一样了

package Test;

//懒汉式创建a实例
public class A {
    private static A a;//如果在此处直接创建实例,那么该方式被称为饿汉式
    private A(){//声明构造函数为private的原因是为了保证A对象只能通过一种方法创建

    }
    public static A getInstance(){
        if (null==a){
        //模拟意外休眠情况
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //创建实例a
            a=new A();
        }

        return a;
    }
}

输出:

Test.A@2c0bb663
Test.A@74c02126

假定线程1先启动,那么按正常情况下线程1会创建唯一的实例a,之后的线程也将不用再次创建实例。但是如果线程1在判断实例a是否为空之后创建实例之前意外休眠,那么随后启动的线程2又会创建一个实例a(因为线程1并没有创建实例,所以a依旧为空)。但当CPU控制权再次回到线程1手里的时候,线程1又会创建一个实例a(因为线程1休眠的时候已经在if内,只是还没有创建实例),所以输出结果并不相同。

解决方法1:在声明getInstance方法的时候增加synchronizedg关键字,使线程同步。但是采用这种方式依旧存在一个问题,那就是每次调用getInstance方式的时候都会让线程形成一个队列。

package Test;

//懒汉式创建a实例
public class A {
    private static A a;//如果在此处直接创建实例,那么该方式被称为饿汉式
    private A(){//声明构造函数为private的原因是为了保证A对象只能通过一种方法创建

    }
    public synchronized static A getInstance(){//增加同步关键字,实现线程同步
        if (null==a){
        //模拟意外休眠情况
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //创建实例a
            a=new A();
        }

        return a;
    }
}

解决方式2:彻底的解决方式,采用双锁模式,因为多线程下产生多个实例的原因是先启动的线程意外休眠,如果同步getInstance方法会造成线程堵塞形成队列,那么如果直接同步实例化部分呢?如下所示

package Test;

//懒汉式创建a实例
public class A {
    private static A a;//如果在此处直接创建实例,那么该方式被称为饿汉式
    private static Object clock=new Object();//创建一个锁
    private A(){//声明构造函数为private的原因是为了保证A对象只能通过一种方法创建

    }
    public  static A getInstance(){//增加同步关键字,实现线程同步
        if (null==a){
        //模拟意外休眠情况
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //使线程在创建实例的时候同步,避免在每次调用getInstance都要同步
            synchronized (clock){
            if (a==null) a=new A();//形成双锁,此处必须再次进行一次判断,因为因为线程1休眠了,线程2已经创建了一个实例,等待线程1唤醒后又会再次创建一个实例,所以会出现两个实例;
            }
        }

        return a;
    }
}

Test.A@480a0d08
Test.A@480a0d08

如果在同步后没有再次进行判断,那么 结果如何呢?

package Test;

//懒汉式创建a实例
public class A {
    private static A a;//如果在此处直接创建实例,那么该方式被称为饿汉式
    private static Object clock=new Object();//创建一个锁
    private A(){//声明构造函数为private的原因是为了保证A对象只能通过一种方法创建

    }
    public  static A getInstance(){//增加同步关键字,实现线程同步
        if (null==a){
        //模拟意外休眠情况
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //使线程在创建实例的时候同步,避免在每次调用getInstance都要同步
            synchronized (clock){
             a=new A();
            }
        }

        return a;
    }
}

Test.A@480a0d08
Test.A@244306f9

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值