Sychronized是java内置关键字
1:一把锁只能同时被一个线程获取,没有获得锁的线程只能等待,在entrySet队列中;
2: 每个实例都对应有自己的一把锁(this),不同实例之间互不影响;
3:当锁对象是*.class以及synchronized修饰的是static方法的时候,所有对象公用同一把锁 synchronized修饰的方法,无论方法正常执行完毕还是抛出异常,都会释放锁
Sychronized作用范围
1:修饰方法
// 修饰普通方法(默认锁对象为this,当前实例对象)
public synchronized void method() {
}
// 修饰静态方法,默认的锁就是当前所在的Class类,所以无论是哪个线程访问它,需要的锁都只有一把
public static synchronized void method() {
}
2:修饰代码块(自己指定)
//对象锁
synchronized (对象) {
}
//类锁
synchronized (A.class) {
}
Sychronized实现原理
sychronized同步依赖monitor对象,每个对象实例都会有一个Monitor对象,在对象头markword里,有个指向monitor对象的指针,Monitor对象会和Java对象一同创建并销毁。Monitor对象是由C++来实现的。
sychronrzed修饰的代码经过编译后会生成monitorenter和monitorexit字节码指令,而monitorenter,monitorexit依赖于底层的操作系统的Mutex Lock来实现的 。
monitorenter指令会发生如下3中情况之一:
monitor计数器为0,意味着目前还没有被获得,那这个线程就会立刻获得然后把锁计数器+1,一旦+1,别的线程再想获取,就需要等待
如果这个monitor已经拿到了这个锁的所有权,又重入了这把锁,那锁计数器就会累加,变成2,并且随着重入的次数,会一直累加
这把锁已经被别的线程获取了,等待锁释放
monitorexit指令
释放对于monitor的所有权,释放过程很简单,就是讲monitor的计数器减1,如果减完以后,计数器不是0,则代表刚才是重入进来的,当前线程还继续持有这把锁的所有权,如果计数器变成0,则代表当前线程不再拥有该monitor的所有权,即释放锁。
JVM中锁的优化
简单来说在JVM中monitorenter和monitorexit字节码依赖于底层的操作系统的Mutex Lock来实现的,但是由于使用Mutex Lock需要将当前线程挂起并从用户态切换到内核态来执行,这种切换的代价是非常昂贵的;然而在现实中的大部分情况下,同步方法是运行在单线程环境(无锁竞争环境)如果每次都调用Mutex Lock那么将严重的影响程序的性能。不过在jdk1.6中对锁的实现引入了大量的优化,如锁粗化(Lock Coarsening)、锁消除(Lock Elimination)、轻量级锁(Lightweight Locking)、偏向锁(Biased Locking)、适应性自旋(Adaptive Spinning)等技术来减少锁操作的开销
synchronized的缺陷
*效率低:锁的释放情况少,只有代码执行完毕或者异常结束才会释放锁;试图获取锁的时候不能设定超时,不能中断一个正在使用锁的线程,相对而言,Lock可以中断和设置超时
不够灵活:加锁和释放的时机单一,每个锁仅有一个单一的条件(某个对象),相对而言,读写锁更加灵活
无法知道是否成功获得:相对而言,Lock可以拿到状态,如果成功获取锁,如果获取失败
参考: padi.