介绍

synchronized是解决线程安全的问题,常用在同步普通方法、静态方法、代码块中,每个对象有一个锁和等待队列,锁只能被一个线程持有,其他需要锁的线程需要阻塞等待,锁被释放后,对象会从队列中取出一个并唤醒,唤醒哪个线程是不确定的,不保证公平性,
因此,synchronized是非公平、可重入的悲观锁

同步方法

生成的字节码文件中会多一个ACC_SYNCHRONIZED标志位,当一个线程访问方法时,会去检查是否存在ACC_SYNCHRONIZED标志位,如果存在,执行线程将先获取monitor,获取成功之后才能执行方法体,方法执行完后再释放monitor,在方法执行期间,其他任何线程都无法在获取同一个monitor对象,也叫隐式同步

同步代码块

加了synchronized关键字的代码段,生成的字节码会多出monitorenter和monitorexit两条指令,每个monitor维护着一个记录拥有次数的计数器,未被拥有的monitor的该计数器0,没当一个线程执行monitorenter后,该计数器自增1,当issue执行monitorexit指令的时候,计数器自减1,当计数器为0的时候,monitor将被释放,也叫显式同步


两种没有本质区别,都是通过monitor来实现同步。

synchronized是如何保证原子性、有序性,可见性

  • 原子性

synchronized保证原子性是通过monitorenter和monitorexit来实现的,当线程执行monitorenter的时候需要先获得锁才能执行后面的方法,当执行monitorexit的时候就需要释放锁。
在没有被释放之前,其他线程是无法再次获取锁的,所以通过monitorenter和monitorexit可以保证synchronized修饰的代码在同一时间只有一个线程访问。因此在java中可以使用synchronized来保证原子性。

  • 有序性
    因为处理器的优化和执行重排序等,CPU可能对输入代码进行乱序执行,比如1,2,3可能被优化成1,3,2。这就是可能存在有序性问题,那么synchronized是如何保证有序性的呢?

在java程序中天然的有序性可以总结为:**如果在本线程内观察,所有的操作都是天然有序的。如果在一个线程中观察另一个线程,所有操作都是无需的。这句话也是《深入理解Java虚拟机》中的原话,周志明并没有详细的解释,这里我简单扩展一下,这其实和as-if-serial语义相关。
as-if-serial:不管怎么重排序,单线程程序的执行结果都不能被改变。编译器和处理器无论如何优化,都必须遵守as-if-serial。
那么我们就可以简单的认为,单线程程序是按照顺序执行的 **。
所以,由于synchronized修饰的代码,在保证原子性的同时,也保证了在同一时间只能被一个线程访问。那么也就是单线程执行的,所以,可以保证其有序性。

  • 可见性
    可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看到修改的值

synchronized修饰的代码,在开始的会加锁,执行完成会释放锁,而为了保证可见性,有这样一个规则:对一个变量解释之前,必须先把此变量同步回主内存中。这样解锁后,后续线程就可以放到被修改后的值。

07-07 16:28