synchronized
关键字最主要的三种使⽤⽅式:
1.修饰实例⽅法:作⽤于当前对象实例加锁,进⼊同步代码前要获得当前对象实例的锁
synchronized void method() {
//业务代码
}
2.修饰静态⽅法:也就是给当前类加锁,会作⽤于类的所有对象实例,进⼊同步代码前要获得当前class
的锁。因为静态成员不属于任何⼀个实例对象,是类成员(static
表明这是该类的⼀个
静态资源,不管new
了多少个对象,只有⼀份)。所以,如果⼀个线程 A 调⽤⼀个实例对象的⾮静态synchronized
⽅法,⽽线程 B 需要调⽤这个实例对象所属类的静态synchronized
⽅法,是允许的,不会发⽣互斥现象,因为访问静态synchronized
⽅法占⽤的锁是当前类的锁,⽽访问⾮静态synchronized
⽅法占⽤的锁是当前实例对象锁。
synchronized void staic method() {
//业务代码
}
3.修饰代码块:指定加锁对象,对给定对象/类加锁。 synchronized(this|object)
表示进⼊同步代码库前要获得给定对象的锁。synchronized(Myclass.class)
表示进⼊同步代码前要获得当前class
的锁
synchronized(this) {
//业务代码
}
总结:
synchronized
关键字加到static
静态⽅法和synchronized(class)
代码块上都是是给类上锁。- synchronized 关键字加到实例⽅法上是给对象实例上锁。
- 尽量不要使⽤
synchronized(String a)
,因为 JVM 中,字符串常量池具有缓存功能!
下⾯我以⼀个常⻅的⾯试题为例讲解⼀下synchronized
关键字的具体使⽤。
⾯试中⾯试官经常会说:“单例模式了解吗?来给我⼿写⼀下!给我解释⼀下双重检验锁⽅式实现单例模式的原理呗!”
双重校验锁实现对象单例(线程安全)
public class Singleton {
private volatile static Singleton uniqueInstance;
private Singleton() {}
public static Singleton getUniqueInstance() {
//先判断对象是否已经实例过,没有实例化过才进⼊加锁代码
if (uniqueInstance == null) {
//类对象加锁
synchronized(Singleton.class) {
if (uniqueInstance == null) {
uniqueInstance = new Singleton();
}
}
}
return uniqueInstance;
}
}
uniqueInstance
采⽤volatile
关键字修饰也是很有必要的,uniqueInstance = new Singleton();
这段代码其实是分为三步执⾏:
- 为
uniqueInstance
分配内存空间 - 初始化
uniqueInstance
- 将
uniqueInstance
指向分配的内存地址
但是由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。例如,线程 T1 执⾏了 1 和 3,此时 T2 调⽤getUniqueInstance()
后发现uniqueInstance
不为空,因此返回uniqueInstance
,但此时uniqueInstance
还未被初始化。
使⽤volatile
可以禁⽌ JVM 的指令重排,保证在多线程环境下也能正常运⾏。
留言