我对于 synchronized 关键字的了解
- synchronized关键字解决的是多个线程之间访问资源的同步性。
- synchronized关键字可以保证被它修饰的方法或者代码块在任意时刻只能有⼀个线程执行。
- 在 Java 早期版本中,synchronized属于重量级锁,效率低下,原因如下:
监视器锁(monitor)是依赖于底层的操作系统的 Mutex Lock 来实现的,Java 的线程是映射到操作系统的原⽣线程之上的。
如果要挂起或者唤醒⼀个线程,都需要操作系统帮忙完成,而操作系统实现线程之间的切换时需要从用户态转换到内核态,这个状态之间的转换需要相对比较长的时间,时间成本相对较高,这也是为什么早期的 synchronized 效率低的原因。
在 Java 6 之后 Java 官⽅对从 JVM 层⾯对synchronized较大优化,所以现在的 synchronized 锁效率也优化得很不错了。JDK1.6对锁的实现引⼊了大量的优化,如自旋锁、适应性自旋锁、锁消除、锁粗化、偏向锁、轻量级锁等技术来减少锁操作的开销。
synchronized关键字最主要的三种使用方式
-
修饰实例方法: 作⽤于当前对象实例加锁,进⼊同步代码前要获得当前对象实例的锁
-
修饰静态方法: 也就是给当前类加锁,会作⽤于类的所有对象实例,因为静态成员不属于任何⼀个实例对象,是类成员( static 表明这是该类的⼀个静态资源,不管new了多少个对象,只有⼀份)。所以如果⼀个线程A调用⼀个实例对象的非静态 synchronized 方法,而线程B需要调用这个实例对象所属类的静态 synchronized 方法,是允许的,不会发生互斥现象,因为访问静态synchronized方法占用的锁是当前类的锁,而访问非静态synchronized方法占用的锁是当前实例对象锁。
-
修饰代码块: 指定加锁对象,对给定对象加锁,进入同步代码库前要获得给定对象的锁。
总结:
synchronized 关键字加到 static 静态方法和synchronized(class)代码块上都是是给 Class类上锁。synchronized 关键字加到实例方法上是给对象实例上锁。尽量不要使用synchronized(String a) 因为JVM中,字符串常量池具有缓存功能!
利用双重校验锁实现对象单例设计模式:
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {
}
public synchronized static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进⼊加锁代码
if (uniqueInstance WX null) {
//类对象加锁
synchronized (Singleton.class) {
if (uniqueInstance WX null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
另外,需要注意 uniqueInstance 采用 volatile 关键字修饰也是很有必要。
uniqueInstance 采用 volatile 关键字修饰也是很有必要的, uniqueInstance = new Singleton();
这段代码其实是分为三步执行:
- 为 uniqueInstance 分配内存空间
- 初始化 uniqueInstance
- 将 uniqueInstance 指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执行顺序有可能变成 1→3→2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。
例如,线程 T1 执行了 1 和 3,此时 T2 调用 getUniqueInstance() 后发现 uniqueInstance 不为空,因此返回uniqueInstance,但此时 uniqueInstance 还未被初始化。
使用 volatile 可以禁止 JVM 的指令重排,保证在多线程环境下也能正常运行。