synchronized1
synchronized1
1. synchronized的用法
1.1 修饰实例方法
给当前对象实例加锁,进入同步代码前要获取当前对象实例的锁
1 |
|
1.2 修饰静态方法
给当前类加锁,进入同步代码之前要获取当前class的锁
1 |
|
1.3 修饰代码块
对括号里指定的对象/类加锁:
synchronized(object)
表示进入同步代码前要获得 给定对象的锁synchronized(类.class)
表示进入同步代码前要获得 给定 Class 的锁
1 |
|
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
也是通过monitorenter和moniterexit的内存屏障功能,来保证代码执行的有序性
3. synchronized和volatile的区别
- 两者均能保证可见性和有序性
synchronized
可以保证原子性,即被synchronized
修饰的代码块同一时间只有一个线程能执行;但volatile
无法保证原子性,它仅保证了被修饰变量的读写操作是原子性的,但是,对于复合操作(例如i++),volatile
无法保证原子性volatile
关键字的性能开销相对较低,因为它仅仅是对变量的可见性进行了保证;synchronized
关键字的性能开销相对较高,因为它需要在进入和退出临界区时获取和释放锁,并且涉及到线程的上下文切换