并发中有三个属性:原子性,可见性,有序性
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。