多线程代码案例之单例模式

单例模式,即单个实例.进程中的某个类有且只有一个对象(不会new多个对象出来),这样的对象就称为单例.如何做才能保证某个类只有一个实例呢?可以通过一些编码上的技巧,使在创建多个对象的时候直接编译出错.下面主要介绍两种写法:饿汉模式与懒汉模式

1.饿汉模式,代码如下

package Demo6;
//单例模式,即单个实例
//单例模式中的饿汉模式
class Singleton{
    public static Singleton instance=new Singleton();
    public static Singleton getinstance(){
        return instance;
    }
    private Singleton(){}
}
public class Demo1{
    public static void main(String[] args){
        Singleton t1=Singleton.getinstance();
        //Singleton t2=new Singleton();
    }
}

首先

public static Singleton instance=new Singleton();

在Singleton类中直接new一个Singleton对象,并用static 修饰,static 成员初始化时机是在类加载的时候.可以认为当JVM启动的时候,就立即加载.static 修饰的是类属性,就是在"类对象"上的,每个类的类对象在JVM中只有一个,里面的静态方法只有一份.

接着

public static Singleton getinstance(){
       return instance;
}

后续需要使用这个类的实例的时候,就可以通过getinstance这个方法来获取了,而不是重新创建实例.

最后

private Singleton(){}

将构造方法设置为private类型,后续想再次new新的实例的时候,就会报错.

总结一下,饿汉模式就是在类加载的时候就已经初始化好了单个实例,无论后续是否会调用,这个单例已经存在了.

2.懒汉模式,代码如下

package Demo6;
class Singletonlazy{
    //啥时候调用就啥时候创建,如果不调用,就不创建了.
    public static Singletonlazy instance=null;
    public static Singletonlazy getInstance(){
        if(instance==null){
            instance=new Singletonlazy();
        }
        return instance;
    }
    private Singletonlazy(){
    }
}

public class Demo2 {
    public static void main(String[] args){
        Singletonlazy t1=Singletonlazy.getInstance();
        System.out.println(t1);
    }
}

首先

public static Singletonlazy instance=null;

将instance置为null,即在未调用的时候不会创建实例.

接着

public static Singletonlazy getInstance(){
        if(instance==null){
          instance=new Singletonlazy();
         }
         return instance;
}

调用getInstance函数,首先会判断是否已经实例化了对象,如果已经实例化了就返回,如果没有就实例化对象.

最后

private Singletonlazy(){}

将构造方法设置为私有的,通过new一个对象这样的方法来创建实例会报错.

对比这两种写法,如果代码中存在多个单例类,使用饿汉模式,就会导致这些实例都是在程序启动的时候扎堆创建的,可能把程序启动时间拖慢.如果是懒汉模式,啥时候调用,啥时候启动,调用时机是分散的,化整为零,不太容易卡顿.

接着思考一下,这两种写法哪一种是线程安全的,哪一种是线程不安全的.

首先是饿汉模式

class Singleton{
    public static Singleton instance=new Singleton();
    public static Singleton getInstance(){
        return instance;
    }
   private Singleton(){}
}

单例的创建是在java进程启动的时候(比main线程还早),那么后续线程中调用getInstance的时候,意味着上述示例早就已经有了.每个线程的getInstance只做了一件事,就是读取静态变量的值.前面介绍的线程不安全的几个方面1.线程抢占式执行 2.多个线程修改同一个变量 3.修改操作不是原子的.饿汉模式只是涉及到线程 多个线程读取同一个变量,是线程安全的. 

接着是懒汉模式

class Singletonlazy{
    public static Singletonlazy instance=null;
    public static Singletonlazy getInstance(){
        if(instance==null){
            instance=new Singletonlazy();
        }
        return instance;
    }
    private Singletonlazy(){
    }
}

 当多个线程同时调用读操作的时候,如果此时示例还未被创建instance为空,这些线程就会都进入new() Singletonlazy的操作,前面介绍过了当多个线程同时修改一个变量的时候,容易引发线程安全问题.通过一个图理解一下此处的线程安全问题

如果按照上述的顺序执行,当t1执行完之后,因为instance只有一份,t1创建的对象的地址会被第二个创建的地址覆盖.虽然覆盖了但是客观上还是创建过的,是客观存在的,有资源开销的.

尝试一下给创建实例操作加锁.如下:

class Singletonlazy{
    public static Singletonlazy instance=null;
    public static Singletonlazy getInstance(){
        Object object=new Object();
        if(instance==null){
            synchronized(object) {
                instance = new Singletonlazy();
            }
        }
        return instance;
    }
    private Singletonlazy(){
    }
}

在这里加锁依然会出现上图中的问题,可以尝试将锁加载if的外面将判断和创建操作原子化,就可以避免上图中出现的问题了.观察上述代码可以发现,每一次调用getInstance()方法都需要加锁操作,但懒汉模式只是在最开始调getInstance()的时候会存在线程安全问题,一旦把实例创建好了,后续在调用,就不会出现线程安全问题了.每次调用时都需要加锁的操作,会有时间开销.使代码性能降低.可以进行改进如下:

class Singletonnlazy{
    public volatile static Singletonnlazy instance=new Singletonnlazy();
     static Object object=new Object();
    public static Singletonnlazy getInstance() {
        if (instance == null) {
            synchronized (object) {
                if (instance == null) {
                    instance = new Singletonnlazy();
                }
                return instance;
            }
        }
        return instance;
    }
}

在外面加一层判断,如果此时实例已经创建就直接返回不需要再进行加锁操作了. 这就是最后的线程安全的懒汉模式的写法.

 

 

 

 

 


 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值