想必大家对这个关键字都很熟悉,毕竟用起来的比较方便和简单的,不需要像用重入锁的时候释放锁,而且此从jdk1.6开始,对Synchronized有很大的优化,性能也得到了一定的提升。
本文将主要围绕Synchronized展开,介绍其用法、原理、以及它是如何保证原子性、可见性和有序性的。
1:Synchronized用法
Synchronized其实是一种内部锁,内部锁是一种排它锁,能够保证原子性、可见性和有序性。Synchronized关键字可以用来修饰方法和代码块。
package sync;
public class SynchronizedDemo {
//同步方法
public synchronized void synMethod(){
System.out.println("同步方法");
}
//同步代码块
public void synChunk(){
synchronized (SynchronizedDemo.class){
System.out.println("同步代码块");
}
}
}
如果同步的是静态方法相当于当前类对象(Java中的类本身也是一个对象)为引导锁的同步块,如下面的等效的:
//同步静态方法
public synchronized void staticMethd(){
//在此处访问共享数据
}
public static void staticMethod(){
synchronized (SynchronizedDemo.class){
//在此处访问共享数据
}
}
实际上,Java虚拟机及编译器对同步块和同步方法处理方式不同,但是这并不影响我们做出这样的理解。
Synchronized修饰的方法或者代码块,在同一时间,只能被一个线程访问,从而保证线程安全。
2:Synchronize实现原理
可以对上面类用javac进行编译,饭后使用javap命令对.class进行反编译。
对同步方法,JVM采用ACC_SYNCHRONIZED
标记符来实现同步。 对于同步代码块。JVM采用monitorenter
、monitorexit
两个指令来实现同步。
同步方法通过ACC_SYNCHRONIZED
关键字隐式的对方法进行加锁。当线程要执行的方法被标注上ACC_SYNCHRONIZED
时,需要先获得锁才能执行该方法。
同步代码块通过monitorenter
和monitorexit
执行来进行加锁。当线程执行到monitorenter
的时候要先获得所锁,才能执行后面的方法。当线程执行到monitorexit
的时候则要释放锁。
每个对象自身维护这一个被加锁次数的计数器,当计数器数字为0时表示可以被任意线程获得锁。当计数器不为0时,只有获得锁的线程才能再次获得锁。即可重入锁。
3:Synchronized原子性
原子性是指一个操作是不可中断的,要全部执行完成,要不就都不执行。
在Java中,为了保证原子性,提供了两个高级的字节码指令monitorenter
和monitorexit
。而这两个字节码指令,在Java中对应的关键字就是synchronized
。
通过monitorenter
和monitorexit
指令,可以保证被synchronized
修饰的代码在同一时间只能被一个线程访问,在锁未释放之前,无法被其他线程访问到。因此,在Java中可以使用synchronized
来保证方法和代码块内的操作是原子性的。
4:Synchronized可见性
可见性是指当多个线程访问同一个变量时,一个线程修改了这个变量的值,其他线程能够立即看得到修改的值。
synchronized
修饰的代码,在开始执行时会加锁,执行完成后会进行解锁。而为了保证可见性,有一条规则是这样的:对一个变量解锁之前,必须先把此变量同步回主存中。这样解锁后,后续线程就可以访问到被修改后的值。所以,synchronized关键字锁住的对象,其值是具有可见性的。
5:Synchronized有序性
有序性即程序执行的顺序按照代码的先后顺序执行。
虽然synchronized
是无法禁止指令重排和处理器优化的,但不管指令怎么重排,在单线程下执行结果都都不会改变。由于synchronized
修饰的代码,同一时间只能被同一线程访问。那么也就是单线程执行的。所以,可以保证其有序性。