加锁机制:内置锁和重入
内置锁:
java提供了一种内置的锁机制来支持原子性:同步代码块(Synchronized Block).
同步代码块包括两部分:一个作为锁的对象引用,一个作为由这个锁保护的代码块。以关键字Synchronized来修饰的方法就是一种横跨整个方法体的同步代码块,其中该同步代码块的锁就是方法调用所在的对象。静待的synchronized方法以Class对象作为锁。
synchronized (lock){
//访问或修改由锁保护的共享状态
}
每个java对象都可以用做一个实现同步的锁,这些锁被称为内置锁(Intrinsic Lock)或监视器锁(Monitor Lock)。线程在进入同步代码块之前会自动获得锁,并且在退出同步代码块时自动释放锁,而无论是通过正常的控制路径退出,还是通过从代码块中抛出异常退出。获得内置锁的唯一途径就是进入由这个锁保护的同步代码块或方法。
java内置锁相当于一种互斥体(互斥锁),即最多只有一个线程能持有这种锁,当线程A尝试获取一个由线程B持有的锁时,线程A必须等待或者阻塞,直到线程B释放这个锁。如果B永远不释放锁,那么A也将永远等下去。
由于每次只能有一个线程执行内置锁保护的代码块,因此,由这个锁保护的同步代码块会以原子方式执行,多个线程在执行该代码块时也不会相互干扰。并发环境中的原子性和事务应用程序中的原子性有着相同的意义——一组语句作为一个不可分割的单元被执行。任何一个执行同步代码块的线程,都不可能看到有其他线程正在执行由同一个锁保护的同步代码块。
缺点:Synchronized是线程安全的,然而,过于极端,多个客户端无法同时使用,服务的响应性非常低。是一个性能问题,而不是线程安全问题。
重入:
public class Widget{
public synchronized void doSomething(){
............................
}
}
public class LoggingWidget extends Widget{
public synchronized void doSomething(){
System.out.println(toString()+":calling doSomething");
super.doSomething();
}
}
如果内置锁不是可重入的,那么这段代码会出现死锁。
由于Widget和LoggingWidget中doSomething方法都是synchronized方法,因此每个doSomething方法在执行前都会获取Widget上的锁。然而,如果内置锁不是可重入的,那么在调用super.doSomething时将无法获得Widget上的锁,因为这个锁已经被持有,从而线程将永远停顿下去,等待一个永远也无法获得的锁。重入避免了死锁。
注:当某个线程请求一个由其他线程持有的锁时,发出请求的线程就会阻塞。然而,由于内置锁是可重入的,因此如果某个线程试图获得一个已经由它自己持有的锁,那么这个请求就会成功。“重入”意味着获取锁的操作的粒度是“线程”而不是“调用”。