synchronized关键字
实现原理
对于synchronized的基本了解就是作为锁来实现代码的同步,用法网上都有解释,下面也会写,但是这个底层是如何实现的,一直很想了解。
synchronized是多线程锁常用的方法,可以对方法或者代码块加锁,但在底层实现方式基本都是一样的,主要使用监视器锁monitor,这里我对一段加锁代码进行了反编译。
public class Lock1 {
public synchronized void method1(){
System.out.println("helloworld");
}
public void method2(){
synchronized (this){
System.out.println("test");
}
}
}
反编译之后
首先来看method2,这里是对代码块加锁,反编译的结果很明显显示其中加入了monitor,主要使用方式是monitorenter和monitorexit来加锁和解锁。
加锁阶段
- 尝试获取monitor锁,如果当前锁的进入数为0,那么获取锁,并加进入数加1
- 本线程另外的方法尝试获取该锁,同样获取锁,进入数加1
- 非本线程方法尝试获取该锁,进入阻塞状态,等待monitor进入数变为0
解锁阶段
- monitor的进入数减1,如果减1后进入数为0,那线程退出monitor
这机制保证了加锁代码的所有权和同步性,同时wait和notify也是在这基础上实现的。那么再看一下synchronized方法,这里和原方法几乎一致,因为方法同步区域是固定的,不需要手动去分割指令,直接通过关键字作为标识通知虚拟机在执行过程中去获取锁。
用法
synchronized主要是作为关键字在代码进行加锁,保证线程对同步代码访问的互斥,解决代码重排序的问题,主要有三种方式
- 正常方法的同步
- 代码块的同步
- 静态方法的同步
正常方法的同步
synchronized最常使用的方式,对于一般方法的同步加锁。
public synchronized void method1(){
System.out.println(Thread.currentThread().getName()+"->helloworld");
}
对于这个方法的调用,每个线程之间是同步的,会出现结果如
Thread-0->helloworld
Thread-1->helloworld
Thread-2->helloworld
之前我遇到过一个误区,如果一个类里存在多个加synchronized的方法,方法之间是同步的,因为这些方法获取的是对象锁,只允许被单一线程执行,不能异步调用。
代码块的同步
代码块的同步主要用于局部加锁,因为方法加锁的资源消耗太大,只需要对需要同步的内容加锁就可以实现目的,不需要全部加锁。
public volatile int i = 0;
public void add(){
synchronized(this){
for(int j = 0;j<100;j++){
i++;
}
}
}
我们知道修改volatile变量的操作是非原子型的,为了保证整个的原子性,就可以像这样对自增操作进行加锁,而非全部加锁。这里需要注意的是这个this,这里的意思是加锁的对象监视器是该类的对象,当然可以不是这个,我们可以定义一个公共类作为对象监视器,这个时候会存在这样的情况
public volatile int i = 0;
public String lock1 = "1";
public String lock2 = "2"
public void add(){
synchronized(lock1){
for(int j = 0;j<100;j++){
i++;
}
}
synchronized(lock1){
System.out.println(i);
}
synchronized(lock2){
System.out.println(i+1);
}
}
这里是有两个锁,前两个synchronized是同步,即其中一个被线程调用的时候,另一个也只能被该线程调用,但是第三个synchronized不是,因为它的对象没有被锁,是可以被任何线程执行的。
静态方法的同步
其实静态方法和一般方法的同步原理是一致的,但是在对象监视器的获取上存在差异,一般的获取的是我们new的变量类,但是静态方法获取的则是类本身。
public class StaticLock {
public static synchronized void method1(){
for(int i = 0;i<3;i++){
System.out.println(Thread.currentThread().getName()+"->"+i);
}
}
public static synchronized void method2(){
for(int i = 0;i<3;i++){
System.out.println(Thread.currentThread().getName()+"->"+i);
}
}
public static void main(String[] args) {
final StaticLock methodA = new StaticLock();
final StaticLock methodB = new StaticLock();
new Thread(){
@Override
public void run() {
methodA.method1();
}
}.start();
new Thread(){
@Override
public void run() {
methodB.method1();
}
}.start();
}
}
其运行结果是
Thread-0->0
Thread-0->1
Thread-0->2
Thread-1->0
Thread-1->1
Thread-1->2
假如是一般方法的加锁,不可能存在线程一和二的同步执行,因为定义两个对象,锁不同,但是静态方法的锁获取的是类锁,定义再多对象,锁还是一个。