synchronized1

synchronized1

1. synchronized的用法

1.1 修饰实例方法

给当前对象实例加锁,进入同步代码前要获取当前对象实例的锁

1
2
3
synchronized void method() {
//业务代码
}

1.2 修饰静态方法

给当前类加锁,进入同步代码之前要获取当前class的锁

1
2
3
synchronized static void method() {
//业务代码
}

1.3 修饰代码块

对括号里指定的对象/类加锁:

  • synchronized(object) 表示进入同步代码前要获得 给定对象的锁
  • synchronized(类.class) 表示进入同步代码前要获得 给定 Class 的锁
1
2
3
synchronized(this) {
//业务代码
}

1.4 为什么尽量不要使用synchronized(String a) ?

因为我们使用synchronized通常是为了在多线程的情况下,不让多个线程同时执行synchronized代码块内的内容,所以synchronized括号内的对象需要是同一个对象,才能保证多个线程的同步执行。

如果使用String a = new String("lock"),String b = new String("lock"),很容易出现两个字符串对象的值虽然相同,但毕竟不是同一个对象,作用到两个同步代码块无法实现锁住的效果。所以要使用字符串充当锁对象时,一般会配合intern()方法使用

2. synchronized的特性

2.1 原子性

synchronized确保在执行同步代码块时,只有持有锁的线程可以访问该代码块,从而避免数据竞争。

2.2 可见性

volatile通过内存屏障来保证可见性的Load屏障保证volatile变量每次读取数据的时候都强制从主内存读取Store屏障保证每次volatile修改之后强制将数据刷新回主内存

我们都知道sychronized底层是通过monitorenter的指令来进行加锁的、通过monitorexit指令来释放锁的。而这两个指令也具有上述的屏障作用

  • 通过monitorenter指令之后synchronized内部的共享变量每次读取数据的时候被强制从主内存读取最新的数据
  • monitorexit指令也具有Store屏障的作用,也就是让synchronized代码块内的共享变量,如果数据有变更的,强制刷新回主内存

这样通过这种方式,数据修改之后立即刷新回主内存,其他线程进入synchronized代码块后,使用共享变量的时候强制读取主内存的数据,上一个线程对共享变量的变更操作,它就能立即看到了

2.3 有序性

指令重排是指 CPU 或编译器为了提高程序的执行效率,改变代码执行顺序的一种优化技术。从 Java 源代码到最终执行的指令序列,会经历 3 种重排序:编译器重排序、指令并行重排序、内存系统重排序。

volatile通过内存屏障来保证有序性的

  • StoreStore屏障:禁止StoreStore屏障的前后Store写操作重排
  • LoadLoad屏障:禁止LoadLoad屏障的前后Load读操作进行重排
  • LoadStore屏障:禁止LoadStore屏障的前面Load读操作跟LoadStore屏障后面的Store写操作重排
  • StoreLoad屏障:禁止LoadStore屏障前面的Store写操作跟后面的Load/Store 读写操作重排

同理,synchronized也是通过monitorentermoniterexit的内存屏障功能,来保证代码执行的有序性

3. synchronized和volatile的区别

  • 两者均能保证可见性和有序性
  • synchronized可以保证原子性,即被synchronized修饰的代码块同一时间只有一个线程能执行;但volatile无法保证原子性,它仅保证了被修饰变量的读写操作是原子性的,但是,对于复合操作(例如i++),volatile无法保证原子性
  • volatile关键字的性能开销相对较低,因为它仅仅是对变量的可见性进行了保证;synchronized关键字的性能开销相对较高,因为它需要在进入和退出临界区时获取和释放锁,并且涉及到线程的上下文切换

synchronized1
http://example.com/2025/05/22/synchronized1/
作者
Kon4tsu
发布于
2025年5月22日
许可协议