synchronized关键字
synchronized关键字最主要的三种使用方式的总结
1.修饰实例方法,作用于当前对象实例加锁,进入同步代码块前要获得当前对象实例的锁
2.修饰静态方法,作用于当前类对象加锁,进入同步代码块前要获得当前类对象的锁。也就是给当前类加锁,会作用于类的所有对象实例,因为静态成员不属于任何一个实例
对象,是类成员(static表明这是该类的一个静态资源,不管new了多少个对象,只有这一份),如果一个线程A调用一个实例对象的非静态synchronized方法,而线程B调用
这个实例对象所属类的静态synchronized方法,是允许的,不会发生互斥现象。因为访问静态的synchronized方法占用的锁是当前类的锁,访问非静态的synchronized方法
占用的锁是当前实例对象的锁。
3.修饰代码块,指定加锁对象,对给定对象加锁,进入同步代码块前要获得给定对象的锁。和synchronized方法一样,synchronized(this)代码块也是锁定当前对象的。
synchronized关键字加到static方法和synchronized(class)代码块上都是都是给class类上锁。再提一下,synchronized关键字非静态方法上是给当前对象实例加锁,另外需要
注意的是,尽量不要使用synchronized(String a)因为JVM中字符串常量池具有缓冲功能。
双重校验锁实现对象单例
public class Singleton { private volatile static Singleton singleton; private Singleton(){} public static Singleton getSingleton(){
if (singleton==null){
synchronized (Singleton.class){
if (singleton==null){
singleton = new Singleton();
}
}
}
return singleton;
}
}
需要注意的是singleton对象使用volatile关键字修饰原因如下:
singleton = new Singleton(); 这段代码其实分为三步执行,第一步为singleton分配内存空间,第二步初始化singleton,第三部将singleton指向分配的内存地址,但是由于
JVM具有指令重排的特征,执行顺序有可能变成第一步->第三步->第二步,此指令重排在单线程环境下没有问题,但是在多线程环境下会导致某个线程获得还没有初始化
的实例。例如线程1执行了第一步和第三步,此时线程2调用getSingleton()方法发现singleton并不为空,因此返回singleton,但此时singleton还未初始化。使用volatile
关键字可以禁止JVM的指令重排,保证在多线程环境下正常运行。
synchronized关键字和volatile关键字的区别
把变量声明为volatile,当一个线程在工作内存中修改该变量的值之后会立即将值同步到主内存,同时使其他线程的工作内存缓存的原始值无效,另一个线程需要使用
这个变量时,需要重新从主内存读取该变量值到自己的工作内存,保证了变量的可见性,还有一个作用是防止指令重排序。
区别有以下几点:
1.volatile关键字是线程同步的轻量级实现,所以volatile关键字的性能肯定要比synchronized关键字要好,但是volatile关键字只能用于变量,而synchronized关键字可以
修饰方法和代码块。在JDK1.6之后进行了获得锁和释放锁带来的性能消耗的优化,相对来说synchronized关键字使用场景较多一点
2.多线程访问volatile关键字不会发生阻塞,synchronized可能会发生阻塞
3.volatile关键字能保证数据的可见性,但不能保证数据的原子性,synchronized关键字两者都能保证
4.volatile关键字主要用于解决变量在多个线程之间的可见性,synchronized关键字解决的是多个线程之间访问资源的同步性