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();这段代码其实是分为三步执⾏:

  1. uniqueInstance分配内存空间
  2. 初始化uniqueInstance
  3. uniqueInstance指向分配的内存地址

但是由于 JVM 具有指令重排的特性,执⾏顺序有可能变成 1->3->2。指令重排在单线程环境下不会出现问题,但是在多线程环境下会导致⼀个线程获得还没有初始化的实例。例如,线程 T1 执⾏了 1 和 3,此时 T2 调⽤getUniqueInstance()后发现uniqueInstance不为空,因此返回uniqueInstance,但此时uniqueInstance还未被初始化。

使⽤volatile可以禁⽌ JVM 的指令重排,保证在多线程环境下也能正常运⾏。

最后修改日期: 2021年11月28日

留言

撰写回覆或留言

发布留言必须填写的电子邮件地址不会公开。