MST

星途 面试题库

面试题:Java中如何通过synchronized关键字保证线程安全以及它在Java内存模型中的作用

请详细阐述在Java中,使用synchronized关键字修饰方法或代码块时,是怎样保证线程安全的?并且说明它在Java内存模型中对可见性、原子性和有序性的影响机制。
47.8万 热度难度
编程语言Java

知识考点

AI 面试

面试题答案

一键面试

一、synchronized保证线程安全的原理

  1. 对象锁机制
    • 当一个线程访问用synchronized修饰的方法或代码块时,它首先要获取该对象的锁(monitor)。每个对象都有一个与之关联的锁,这个锁在Java中被称为监视器(monitor)。
    • 如果该对象的锁已经被其他线程持有,那么当前线程就会被阻塞,进入该对象的等待队列。只有当持有锁的线程释放了锁,等待队列中的线程才有机会竞争并获取锁,从而继续执行被synchronized保护的代码。
    • 例如,假设有两个线程ThreadAThreadB,如果ThreadA先访问一个synchronized修饰的方法,它会获取对象锁,在ThreadA执行完该方法释放锁之前,ThreadB试图访问同一个synchronized方法时就会被阻塞。
  2. 方法修饰
    • synchronized修饰实例方法时,锁是当前对象实例(this)。例如:
public class SynchronizedExample {
    public synchronized void synchronizedMethod() {
        // 业务逻辑
    }
}
  • synchronized修饰静态方法时,锁是该类的Class对象。因为静态方法属于类,所以锁是针对整个类级别的。例如:
public class SynchronizedStaticExample {
    public static synchronized void staticSynchronizedMethod() {
        // 业务逻辑
    }
}
  1. 代码块修饰
    • synchronized修饰代码块时,可以指定锁对象。例如:
public class SynchronizedBlockExample {
    private final Object lock = new Object();
    public void synchronizedBlockMethod() {
        synchronized (lock) {
            // 业务逻辑
        }
    }
}
  • 这样可以更细粒度地控制锁的范围,只有进入synchronized代码块时才需要获取锁,而不像修饰方法那样整个方法执行期间都持有锁,从而提高了并发性能。

二、synchronized在Java内存模型中对可见性、原子性和有序性的影响机制

  1. 可见性
    • 在Java内存模型中,每个线程都有自己的工作内存,线程对变量的操作都在工作内存中进行,而主内存是所有线程共享的。
    • 当一个线程获取锁时,它会将主内存中的共享变量刷新到自己的工作内存中。当线程释放锁时,会将工作内存中的共享变量刷新回主内存。
    • 例如,假设有一个共享变量count,线程ThreadAsynchronized代码块中修改了count的值,当ThreadA释放锁时,count的新值会被刷新回主内存。此时,如果线程ThreadB获取锁并进入synchronized代码块,它会从主内存中获取到count的最新值,从而保证了可见性。
  2. 原子性
    • synchronized关键字可以保证被它修饰的代码块或方法在同一时刻只有一个线程能够执行,这就确保了对共享资源的操作是原子性的。
    • 例如,对于一个简单的自增操作count++,它实际上包含了读取count的值、增加1、写回count的值三个步骤,在多线程环境下如果不进行同步,可能会出现数据竞争问题。但是如果将这个操作放在synchronized代码块或方法中,就可以保证这三个步骤是原子性的,不会被其他线程干扰。
  3. 有序性
    • synchronized关键字通过内存屏障来保证有序性。在获取锁时,会有一个“读屏障”,它会确保该线程之前的所有读操作都已经完成,并且之后的读操作不会被重排序到获取锁之前。在释放锁时,会有一个“写屏障”,它会确保该线程之前的所有写操作都已经完成,并且之后的写操作不会被重排序到释放锁之后。
    • 例如,假设有以下代码:
public class SynchronizedOrderExample {
    private int a;
    private int b;
    public synchronized void method() {
        a = 1;
        b = 2;
    }
}
  • 在释放锁时,a = 1b = 2的写操作不会被重排序到释放锁之后,从而保证了有序性。同样,在其他线程获取锁进入该方法时,之前的读操作也不会被重排序到获取锁之前,保证了操作的顺序正确性。