synchronized 修饰的代码块、方法任意时刻只能有一个线程执行。
修饰实例方法锁的是当前对象,静态方法、代码块锁的是当前类。构造方法本身就是线程安全的,不需要加锁。
JDK1.6之前,synchronized 是重量级锁,加锁和释放锁的消耗很大。
JDK1.6对synchronized 进行了优化,加入了锁升级、偏向锁、轻量级锁、可重入锁等机制。PS:https://www.cnblogs.com/leejk/tag/Java锁/
原子性
指一个或多个操作不可中断,要么都执行要么都不执行,不会被其他线程干扰。
synchronized本质上是获取监视器锁monitor。获取锁的线程就进入了临界区,锁释放之前其他线程都无法获得处理器资源,保证了不会发生时间片轮转,因此保证了原子性。
可见性
当共享变量被修改,其他线程立即可以看到修改后的最新值。
JMM中关于synchronized有如下规定,线程加锁时,必须清空工作内存中共享变量的值,从而使用共享变量时需要从主内存重新读取;线程在释放锁时,需要把工作内存中最新的共享变量的值写入到主内存种。
(这里是个泛指,不是说只有在释放 synchronized 时才同步变量到主内存)
synchronized 保证可见性的前提是多个线程通过了同一把锁,很显然单例下不满足这个条件,所以需要volatile来保证可见性。(双重校验锁单例)
有序性
由于编译器或 JVM 的优化,代码的执行顺序未必就是编写代码时候的顺序。
根据as-if-serial语义,无论编译器和处理器怎么优化或指令重排,单线程下的运行结果一定是正确的。而synchronized 保证了单线程独占CPU,也就保证了有序性。
原子性:加锁、释放锁实现。
可见性:Load屏障、Store屏障实现,加锁会refresh数据,释放锁会flush数据。
int a = 0; synchronize (this){ //monitorenter // Load屏障 a = 10; int b = a; }//monitorexit // Store屏障
monitorenter 指令之后会有一个 Load 屏障,执行refresh处理器缓存操作,把别的处理器修改过的最新的值加载到自己的高速缓存中;
monitorexit 指令之后会有一个 Store 屏障,让线程把自己修改的变量都执行flush处理器缓存操作,刷到高速缓存或是主内存中。
有序性:Acquire屏障、Release屏障。
int a = 0; synchronize (this){ //monitorenter // Load屏障 // Acquire屏障 a = 10; 内部还是会发生指令重排 int b = a; // Release屏障 }//monitorexit // Store屏障
在 monitorenter 指令和 Load 屏障之后,会加一个 Acquire屏障,这个屏障的作用是禁止读操作和读写操作之间发生指令重排;
在 monitorexit 指令前加一个Release屏障,同样禁止写操作和读写操作之间发生重排。
须知
synchronized可以保证并发编程的三大特性,而volatile只能保证可见性、有序性,并不能保证原子性。