单例模式和CAS

并发中有三个属性:原子性,可见性,有序性

1.原子性

  原子性:即一个操作或者多个操作 要么全部执行并且执行的过程不会被任何因素打断,要么就都不执行。

2.可见性

  可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。

3.有序性

  有序性:即程序执行的顺序按照代码的先后顺序执行。

一,单例模式

1,懒汉式

例子:

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

初始化:调用时初始化

线程安全: 非线程安全

缺点:如果有2个线程同时请求getInstance()时,创建对象时可能会出现2个不一样的对象。

2,饿汉式

初始化:类加载时初始化

线程安全: 线程安全

缺点:浪费内存。

3. 懒汉式使用synchronized 

例子:

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

​

缺点:每次进入getInstance()方法,synchronized关键字会将整个代码进行锁住,加锁操作,在进行判断是否已经初始化,在进行释放锁,进行加锁和释放锁都会影响性能。

4,双重校验锁

例子:

public class Singleton {  
    private static Singleton singleton;  
    private Singleton (){}  
    public static Singleton getSingleton() {  
    if (singleton == null) {  
        synchronized (Singleton.class) {  
        if (singleton == null) {  
            singleton = new Singleton();  
        }  
        }  
    }  
    return singleton;  
    }  
}

优点:只会在初始化时进行加锁校验

缺点: singleton = new Singleton();不是一次性完成的,即不具有原子性。创建一个对象实例,可以分为三步:

1.分配对象内存

2.调用构造器方法,执行初始化

3.将对象引用赋值给变量。

在虚拟机实际运行时,可能发生重排序,第2和第3的顺序可能会反过来(1不会发生重排序,因为后2步都必须以1位前提),如果线程一执行了1,3,还没有执行2时,线程二刚好进入,获取到的是非null,而此时对象还没有初始化,这时候就会出现异常。

解决办法:使用volatile禁止指令重排序优化。

private volatile static Singleton singleton;  

5,静态内部类

第一次加载Singleton类时并不会初始化sInstance,只有第一次调用getInstance方法时虚拟机加载SingletonHolder 并初始化sInstance ,这样不仅能确保线程安全也能保证Singleton类的唯一性。

public class Singleton { 
    private Singleton(){
    }
      public static Singleton getInstance(){  
        return SingletonHolder.sInstance;  
    }  
    private static class SingletonHolder {  
        private static final Singleton sInstance = new Singleton();  
    }  
} 

6,枚举

enum在编译过后会对对象加上static和final,利用静态变量只加载一次的原理来保证实例的唯一性

public enum Singleton {
    INSTANCE;
    Singleton() {
    }
    public void method() {
      
    }
}

public class SingletonHelper {
    public void getMethod() {
        Singleton.INSTANCE.method();
    }
}

7. 容器

在程序的初始化,将多个单例类型注入到一个统一管理的类中,使用时通过key来获取对应类型的对象,可以通过统一的接口进行操作管理多种类型的单例。

public class SingletonManager {
    private static Map<String,Object> map=new HashMap<String, Object>();
    
    private SingletonManager(){}
    
    public static void registerService(String key,Object instance){
        if (!map.containsKey(key)){
            map.put(key,instance);
        }
    }
    
    public static Object getService(String key){
        return map.get(key);
    } 
}

一,volatile关键字

一个变量被volatile修饰之后,会具备了两个属性:

 1.保证了不同线程对这个变量进行操作时的可见性,即一个线程修改了某个变量的值,这新值对其他线程来说是立即可见的。

 2.禁止进行指令重排序。

保证可见性的原因:每个线程在运行过程中都有自己的工作内存,当一个线程对象进行修改时,会先读取到工作内存中,修改后再写入到内存中。当2个线程同时操作一个对象时会出现线程A修改了对象的值但是没有写入内存时,线程B获取的是修改前的值。而使用volatile关键字会强制将修改的值立即写入主存,同时会将其他线程缓存中持有的该对象失效,其他线程要读取该对象时就会重新从主存中读取

缺点:volatile关键字实现了可见性,但是没有实现原子性,所以不能替代synchronized。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值