通过单例模式理解synchronized,volatile
2016-07-28 09:27
411 查看
转载地址:http://www.jianshu.com/p/5b2f063d9f68
synchronized(this)及非static的synchronized方法,只能防止多个线程同时执行
所以我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步。这叫减小锁的粒度,使代码更大程度的并发。原因是基于以上的思想,锁的代码段太长了,别的线程是不是要等很久。
如果用synchronized加在
一个线程执行互斥代码过程如下:
所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
它的工作原理是,它对写和读都是直接操作工作主存的。下面我们通过具体的示例来说明,请看下面的示例代码:
假设有多个线程分别调用上面程序的三个方法,这个程序在语意上和下面程序等价:
临界区代码的执行具有原子性。这意味着即使是64位的long型和double型变量,只要它是volatile变量,对该变量的读写就将具有原子性。
如果是多个volatile操作或类似于volatile++这种复合操作,这些操作整体上不具有原子性。简而言之,volatile变量自身具有下列特性:
可见性: 对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
对 volatile 变量的写操作,不允许和它之前的读写操作打乱顺序;对 volatile 变量的读操作,不允许和它之后的读写乱序。
以及下面这段线程不安全的singleton(单例模式)实现,尽管使用了volatile:
关于原子性的理解:只对
但是,轻易不要用volatile来替代synchronized来避免并发问题,因为很多原子操作自己可能并没那么了解,除非你是并发专家。《java编程思想》的作者是这样建议的。
这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 Classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法,比如反射)导致类装载,这时候初始化 instance显然没有达到 lazy loading 的效果。
一个双重锁定式的单例模式
讨论下volatile关键字的必要性,如果没有volatile关键字,问题可能会出在singleton = new Singleton();这句,用伪代码表示
可能会由于虚拟机的优化等导致赋值操作先执行,而构造函数还没完成,导致其他线程访问得到singleton变量不为null,但初始化还未完成,导致程序崩溃。
下篇你见过这样的单例模式吗将见识到一种新的单例模式。
参考:Java 单例真的写对了么?
文/SilenceDut(简书作者)
原文链接:http://www.jianshu.com/p/5b2f063d9f68
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
synchronized 关键字
synchronized关键字是用来控制线程同步的,就是在多线程的环境下,控制synchronized代码段不被多个线程同时执行。是一种阻塞性的锁,synchronized既可以加在一段代码上,也可以加在方法上。synchronized(this)及非static的synchronized方法,只能防止多个线程同时执行
同一个对象的同步代码段。当synchronized锁住一个对象后,别的线程如果也想拿到这个对象的锁,就必须等待这个线程执行完成释放锁,才能再次给对象加锁,这样才达到线程同步的目的。即使两个不同的代码段,都要锁同一个对象,那么这两个代码段也不能在多线程环境下同时运行。
所以我们在用synchronized关键字的时候,能缩小代码段的范围就尽量缩小,能在代码段上加同步就不要再整个方法上加同步。这叫减小锁的粒度,使代码更大程度的并发。原因是基于以上的思想,锁的代码段太长了,别的线程是不是要等很久。
如果用synchronized加在
静态方法上,就相当于用
××××.class锁住整个方法内的代码块,此时是锁住该类的Class对象,相当于一个全局,锁。使用synchronized修饰的方法或者代码块可以看成是一个原子操作。
一个线程执行互斥代码过程如下:
1. 获得同步锁; 2. 清空工作内存; 3. 从主内存拷贝对象副本到工作内存; 4. 执行代码(计算或者输出等); 5. 刷新主内存数据; 6. 释放同步锁。
所以,synchronized既保证了多线程的并发有序性,又保证了多线程的内存可见性。
volatile(非阻塞性的)
volatile的特性当我们声明共享变量为volatile后,对这个变量的读/写将会很特别。理解volatile特性的一个好方法是:把对volatile变量的单个读/写,看成是使用同一个监视器锁对这些单个读/写操作做了同步。它的工作原理是,它对写和读都是直接操作工作主存的。下面我们通过具体的示例来说明,请看下面的示例代码:
class VolatileFeaturesExample { volatile long vl = 0L; //使用volatile声明64位的long型变量 public void set(long l) { vl = l; //单个volatile变量的写 } public void getAndIncrement () { vl++; //复合(多个)volatile变量的读/写 } public long get() { return vl; //单个volatile变量的读 } } class VolatileFeaturesExample { volatile long vl = 0L; //使用volatile声明64位的long型变量 public void set(long l) { vl = l; //单个volatile变量的写 } public void getAndIncrement () { vl++; //复合(多个)volatile变量的读/写 } public long get() { return vl; //单个volatile变量的读 } }
假设有多个线程分别调用上面程序的三个方法,这个程序在语意上和下面程序等价:
class VolatileFeaturesExample { long vl = 0L; // 64位的long型普通变量 //对单个的普通 变量的写用同一个监视器同步 public synchronized void set(long l) { vl = l; } public void getAndIncrement () { //普通方法调用 long temp = get(); //调用已同步的读方法 temp += 1L; //普通写操作 set(temp); //调用已同步的写方法 } public synchronized long get() { //对单个的普通变量的读用同一个监视器同步 return vl; } }
临界区代码的执行具有原子性。这意味着即使是64位的long型和double型变量,只要它是volatile变量,对该变量的读写就将具有原子性。
如果是多个volatile操作或类似于volatile++这种复合操作,这些操作整体上不具有原子性。简而言之,volatile变量自身具有下列特性:
可见性: 对一个volatile变量的读,总是能看到(任意线程)对这个volatile变量最后的写入。
对 volatile 变量的写操作,不允许和它之前的读写操作打乱顺序;对 volatile 变量的读操作,不允许和它之后的读写乱序。
volatile不能保证原子性,原子性不是volatile来保证的,如果操作本来就具有原子性,volatile就会保证这个原子性不会被打破,理解为加上同步。比如上面例子中的get和set函数。
以及下面这段线程不安全的singleton(单例模式)实现,尽管使用了volatile:
public class UnSafeSingleton { private static volatile UnSafeSingleton sInstance = null; private UnSafeSingleton() {} public static UnSafeSingleton getInstance(){ if (sInstance == null) { sInstance = new UnSafeSingleton(); } return sInstance; } }
关于原子性的理解:只对
单个操作就有原子性,比如i++这种就不是单个操作。
但是,轻易不要用volatile来替代synchronized来避免并发问题,因为很多原子操作自己可能并没那么了解,除非你是并发专家。《java编程思想》的作者是这样建议的。
单例模式的应用
先看一个"饿汉式"的模式public class Singleton { private static Singleton instance = new Singleton(); private Singleton (){} public static Singleton getInstance() { return instance; } }
这种方式比较常用,但容易产生垃圾对象。
优点:没有加锁,执行效率会提高。
缺点:类加载时就初始化,浪费内存。
它基于 Classloader 机制避免了多线程的同步问题,不过,instance 在类装载时就实例化,虽然导致类装载的原因有很多种,在单例模式中大多数都是调用 getInstance 方法, 但是也不能确定有其他的方式(或者其他的静态方法,比如反射)导致类装载,这时候初始化 instance显然没有达到 lazy loading 的效果。
一个双重锁定式的单例模式
public class Singleton { private volatile static Singleton sSingleton; private Singleton (){} public static Singleton getSingleton() { if (sSingleton == null) { synchronized (Singleton.class) { if (sSingleton == null) { sSingleton = new Singleton(); } } } return sSingleton; } }
讨论下volatile关键字的必要性,如果没有volatile关键字,问题可能会出在singleton = new Singleton();这句,用伪代码表示
inst = allocat(); // 分配内存 sSingleton = inst; // 赋值 constructor(inst); // 真正执行构造函数
可能会由于虚拟机的优化等导致赋值操作先执行,而构造函数还没完成,导致其他线程访问得到singleton变量不为null,但初始化还未完成,导致程序崩溃。
下篇你见过这样的单例模式吗将见识到一种新的单例模式。
参考:Java 单例真的写对了么?
文/SilenceDut(简书作者)
原文链接:http://www.jianshu.com/p/5b2f063d9f68
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories