基本情况
类别 | Synchronized | Lock |
---|---|---|
存在层次 | Jvm,Synchronized是Java的关键字 | 类 |
锁的获取 | 当一个线程获取到锁的时候,其他线程只能等待 | 可以有多种情况,不一定要一直等待下去 |
锁的释放 | 由于在Jvm层面,使用完毕之后会自动的释放锁 | 使用完锁之后必须手动的释放锁,一般放在finally里面 |
锁的类型 | 非公平锁 | 可以是公平的,默认非公平 |
性能 | 少量同步 | 在大量同步时,建议使用Lock |
状态 | 无法判断 | 可以通过condition来进行不同的锁控制 |
Synchronized
- Synchronized关键字用于对某一个对象加锁,那么在同一时刻只能有一个线程来执行这个对象的Synchronized方法,在不同线程之间就可以形成顺序访问的结果。
- 但是,这只会阻塞被Synchronized修饰的方法,假设一个线程A访问Synchronized方法获取到了锁,此时线程B访问对象的非Synchronized方法是可以同时进行的。因为Synchronized实际上是对这个对象的锁做了限制,非Synchronized方法不需要获取到这个对象的锁就可以进行。
但是如果获取到锁的线程本身在执行方法的内部调用该对象或者父类的其他Synchronized方法,是可以得到锁的,这就叫做锁重入
下面是一个父类锁重入的例子
父类
public class Father {
public int i = 10;
synchronized public void fatherMethod() {
i--;
System.out.println("father i=" + i);
}
}
子类
public class Son extends Father {
synchronized public void sonMethod() {
while (i > 0) {
i--;
System.out.println("son i=" + i);
this.fatherMethod();
}
}
}
自定义线程
public class SynThread extends Thread {
@Override
public void run() {
Son son = new Son();
son.sonMethod();
}
}
输出结果
son i=9
father i=8
son i=7
father i=6
son i=5
father i=4
son i=3
father i=2
son i=1
father i=0
可以看到输出是从9到0,说明程序是可重入的
同步代码块
synchronized可以修饰在方法上,但是一个方法如果被synchronized修饰,当线程在执行这个方法的时候,其他的线程都只能阻塞等该线程执行完,如果这个方法所需的时间很长,那么对性能就是一个影响。所以我们可以考虑只能有可能造成多线程冲突的部分进行同步,也就是同步代码块
- synchronized(this){}
- synchronized(object){}
synchronized(this){}被称为this同步代码块,针对是当前的对象,synchronized(object){}被称为对象同步代码块,针对的是指定的对象。**但无论是哪一种(包括synchronized方法)实际上都是对锁的争夺,一个对象指挥产生一个锁,只要有一个线程在执行synchronized修饰的东西,其他线程都无法访问被synchronized修饰的方法或代码块。
synchronized(object) 不要使用String类型的对象,因为Jvm有常量池缓存,可能会产生各种问题。
类锁
关键字synchronized还可以修饰static的方法,或者是写synchronized(class)的形式,从而将锁升级为类锁,也就是给这个Java类加锁,这意味着就算是不同的对象来执行synchronized的方法或者代码块时都会变成同步执行
可以理解成对class对象的加锁,每一个类都有一个Class类的对象,对该class对象加锁,其他同步代码块自然是访问不了的
底层实现
Java是通过字节码命令来控制代码的,在synchronized形成的字节指令中,会形成两段流程
查看class源码,可以看到如下
我们可以清晰的看到,其实synchronized形成的字节码就算增加了两个指令,monitorenter和monitorexit,当一条线程执行到monitorenter的时候,就会尝试去获得锁,如果获得锁,那么锁对象就+1,如果没有得到锁,那就会阻塞等待,当执行到monitorexit的时候锁对象就-1。当锁对象为0的时候,锁就会被释放。
那么为什么上图有两个monitorexit呢,因为synchronized在发生异常的时候会自动的释放锁。那么就会有两种情况,一种是正常的执行完释放锁,一种是发生了异常释放了锁。图中第二个monitorexit就算发生异常时的流程,可以看到如果经历了第一个monitorexit,紧接着就会goto 19,不会执行第二个monitorexit。