设计模式之单例模式

来源:互联网 发布:linux shell 捕获错误 编辑:程序博客网 时间:2024/05/16 09:16

单例模式:确保一个类只有一个实例,并且提供一个全局的访问点。


我们先看一下标准的懒汉方式。


public class SingletonOne {private static SingletonOne Sing;        private SingletonOne(){}        public static SingletonOne getInstance(){if(Sing==null){Sing=new SingletonOne();}return Sing;}}

分析一下这种写法。


首先声明了一个私有的静态变量 


然后我们可以在外部调用

SingletonOne.getInstance()获取对象实例。

但是现在我们模拟一下场景,如果同时两个或者更多线程同时去调用

SingletonOne.getInstance()

会出现什么情况呢,假设线程一先判断完Sing是否为null,这时候JVM把CPU资源切换到了第二个线程,第二个线程去再去判断,发现还是为null,那么将会创建两个不同的对象。如果在确保只有一个线程访问的情况下,这种方式是可以的


我们举个例子:


public class Test {public static void main(String[] args) {for (int i = 0; i < 50; i++) {Thread th=new Thread(new NewSingleton());th.start();}}}class NewSingleton implements Runnable{@Overridepublic void run() {// TODO Auto-generated method stubSystem.out.println(SingletonOne.getInstance());}}

这时候我创建了50个线程 同时去调用SingletonOne.getInstance() 我们看一下打印结果。


com.wy.singleton.SingletonOne@22b5ecf4
com.wy.singleton.SingletonOne@2d3375e
com.wy.singleton.SingletonOne@2d3375e
com.wy.singleton.SingletonOne@22b5ecf4
com.wy.singleton.SingletonOne@2d3375e
com.wy.singleton.SingletonOne@2d3375e
com.wy.singleton.SingletonOne@2d3375e

....

这里我只列举几条 我们发现 获取的对象是不一样的。


所以这种方式不适合多线程的情况。


那么有人会说了 使用synchronized 同步方法 或者同步代码块不就可以解决这个问题吗  好 我们实验一下。


我们改动一下getInstance方法  


public synchronized static SingletonOne getInstance(){if(Sing==null){Sing=new SingletonOne();}return Sing;}


(在静态方法中使用synchronized ,默认锁为类的类 相当于SingletonOne.class)


然后我们再次运行一下 结果如下


com.wy.singleton.SingletonOne@a2c7b1
com.wy.singleton.SingletonOne@a2c7b1
com.wy.singleton.SingletonOne@a2c7b1
com.wy.singleton.SingletonOne@a2c7b1
com.wy.singleton.SingletonOne@a2c7b1
com.wy.singleton.SingletonOne@a2c7b1

发现这个获取的是一致的。


但是使用synchronized会造成比较耗时,确保只有一个线程进入此方法。 我们同时开启50个线程 那么他会一个个进入。


绝大部分的耗时操作都用在synchronized修饰符的同步准备上,因此从性能上说很不划算。


public class SingletonOne {private static SingletonOne Sing;private SingletonOne(){}public  static SingletonOne getInstance(){synchronized (SingletonOne.class) {if(Sing==null){Sing=new SingletonOne();}return Sing;}}}

这种方式与上面那个方式类示 每次调用getInstance()还是要进行同步的。


我们再改一下


public class SingletonOne {private static SingletonOne Sing;        private SingletonOne(){}public static SingletonOne getInstance() {if (Sing == null) {   synchronized (SingletonOne.class) {//第二个线程会在这里等待锁 第二个线程已经判断Sing==null 那么第二个线程会再次new一次的Sing = new SingletonOne();//假设第一个线程走到这里 }}return Sing;}}


我们再看标准的饿汉方式。


根据JLS(Java Language Specification)中的规定 ,一个类在一个ClassLoader中只会被初始化一次,这点是JVM本身保证的,那就把初始化实例的事情扔给JVM好了,代码被改成这样:也就是标准的饿汉式写法


public class SingletonOne {private static SingletonOne Sing=new SingletonOne();;        private SingletonOne(){}public static SingletonOne getInstance() {return Sing;}}


我们再看一种写法


public class SingletonOne {private static class SingleHolder{static final SingletonOne sing=new SingletonOne();}private SingletonOne(){}public static SingletonOne getInstance(){return SingleHolder.sing;}}

这两种方式都是标准的线程安全的写法。




0 0