什么是synchronized
synchronized可以保证某个代码块或者方法被一个线程占有,保证了一个线程的可先性。java 1.6之前是重量级锁,在1.6进行了各种优化,就不那么重了,并引入了偏向锁和轻量级锁,以及锁的存储结构和升级过程。
synchronized实现方式
Java中每个对象都可以作为锁:
- 对于普通同步方法,锁是实例对象。
- 对于静态通同步方法,锁是当前类的Class对象。
- 对于同步方法块,锁是Synchronized括号里的配置对象。
synchronized实现原理
synchronized可以保证方法或者代码块在运行时,同一个时刻只有一个线程可以进入到临界区,同时它还可以保证共享变量的可见性。
JVM基于进入和退出Monitor对象来实现方法同步和代码块同步。通过monitorenter和monitorexit指令实现。前者是编译后插入到同步代码块开始的位置,后者是插入到方法结束和异常处。JVM保证每个monitorenter和monitorexit指令是配对的,任何对象都会有一个monitor与之关联,当对对象的monitor被持有后,它就是处于锁定状态。线程执行到monitorenter时,就会尝试获取对象monitor的所有权,就是获取对象锁。
sychronized 特点
- 不能被继承,但是可以调用父类的同步方法达到同步的目的。
- 一个线程访问一个对象的synchronized方法或者代码块的时候,其他线程访问该对象的synchronized方法或者代码块将被阻塞,但是非synchronized方法或者代码块还是可以访问 的。
- 不论sychronized 放在对象还是方法上,如果它作用的是非静态的,那么获得锁是对象,如果是静态的,那么获得锁是类,最后导致获得所有对象的锁。
锁的升级和对比
锁一共有四种状态:
- 无锁
- 偏向锁
- 轻量级锁
- 重量级锁
锁的升级
锁可以升级,但是不能降级。
这样的规则目的是为了提高获得锁和释放锁的效率。
偏向锁
作用
大多数情况下不存在锁的竞争,为了降低同一线程获得锁的开销,就在锁的对象头中加入这一线程的ID,这样,在之后这个线程进入和退出同步块的时候就不需要CAS操作来实现加锁和解锁。
升级
当线程1访问代码块并获取锁对象时,会在java对象头和栈帧中记录偏向的锁的threadID,因为偏向锁不会主动释放锁,因此以后线程1再次获取锁的时候,需要比较当前线程的threadID和Java对象头中的threadID是否一致,如果一致(还是线程1获取锁对象),则无需使用CAS来加锁、解锁;如果不一致(其他线程,如线程2要竞争锁对象,而偏向锁不会主动释放因此还是存储的线程1的threadID),那么需要查看Java对象头中记录的线程1是否存活,如果没有存活,那么锁对象被重置为无锁状态,其它线程(线程2)可以竞争将其设置为偏向锁;如果存活,那么立刻查找该线程(线程1)的栈帧信息,如果还是需要继续持有这个锁对象,那么暂停当前线程1,撤销偏向锁,升级为轻量级锁,如果线程1 不再使用该锁对象,那么将锁对象状态设为无锁状态,重新偏向新的线程。
轻量级锁
作用
利用CAS算法,线程通过自旋的方式获取锁。这样,线程不会被阻塞。
加锁
首先JVM在线程的栈帧中创建锁记录存储空间,然后把锁对象头中的Markword复制到栈帧中。再通过利用CAS方法将锁对象的markword替换为指向线程栈帧锁记录的指针。如果成功替换,就获得了当前锁,如果已经被其他线程替换了,那么就会自旋获取锁。
解锁
将栈帧的markword再利用CAS放回对象的头中。
升级
其它线程自旋获取锁的次数是有限制的,比如10次或者100次,如果自旋次数到了线程1还没有释放锁,或者线程1还在执行,线程2还在自旋等待,这时又有一个线程3过来竞争这个锁对象,那么这个时候轻量级锁就会膨胀为重量级锁。重量级锁把除了拥有锁的线程都阻塞,防止CPU空转。
锁的比较
synchronized使用
修饰方法和代码块
修饰方法很简单,就在方法前面加一个synchronized关键字。
public synchronized void method()
{
// todo
}
public void method()
{
synchronized(this) {
// todo
}
}
指定给某个对象加锁
public void method3(SomeObject obj)
{
//obj 锁定的对象
synchronized(obj)
{
// todo
}
}
上面的代码中,通过synchronized 给obj对象加锁。当其他线程想要访问obj,就会被阻塞。
class Test implements Runnable
{
private byte[] lock = new byte[0]; // 特殊的instance变量
public void method()
{
synchronized(lock) {
// todo 同步代码块
}
}
public void run() {
}
}
如果只是想要一个锁,可以利用特殊的实例来充当锁。
修饰静态的方法
public synchronized static void method() {
// todo
}
因为静态方法是属于这个类的, 而不是实例化的对象,所以,synchronized 修饰的是静态方法锁定的这个类的所有对象。
修饰类
class ClassName {
public void method() {
synchronized(ClassName.class) {
// todo
}
}
}
和修饰静态方法一样,所有对象都共有这把类锁。