Java平台中的任何一个对象都有唯一一个与之关联的锁。这种锁被称为监视器(Monitor)或者内部锁(Intrinsic Lock)。内部锁是一种排他锁,它能够保障原子性、可见性和有序性。
内部锁是通过synchronized关键字实现的。synchronized关键字可以修饰方法以及代码块(花括号“{}”包裹的代码)。
synchronized关键字修饰的方法被称为同步方法(Synchronized Method)。synchronized关键字修饰的静态方法被称为同步静态方法,synchronized关键字修饰的实例方法被称为同步实例方法。同步方法的整个方法体就是一个临界区。
具体说明如下:
1 同步普通方法
这个也是我们用得最多的,只要涉及线程安全,上来就给方法来个同步锁。这种方法使用虽然最简单,但是只能作用在单例上面,如果不是单例,同步方法锁将失效。
/**
* 用在普通方法
*/
private synchronized void synchronizedMethod() {
System.out.println("synchronizedMethod");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
此时,同一个实例只有一个线程能获取锁进入这个方法。
2 同步静态方法
同步静态方法,不管你有多少个类实例,同时只有一个线程能获取锁进入这个方法。
/**
* 用在静态方法
*/
private synchronized static void synchronizedStaticMethod() {
System.out.println("synchronizedStaticMethod");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
同步静态方法是类级别的锁,一旦任何一个线程进入这个方法,其他所有线程将无法访问这个类的任何同步类锁的方法。
3 同步类
下面提供了两种同步类的方法,锁住效果和同步静态方法一样,都是类级别的锁,同时只有一个线程能访问带有同步类锁的方法。
/**
* 用在类
*/
private void synchronizedClass() {
synchronized (TestSynchronized.class) {
System.out.println("synchronizedClass");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
/**
* 用在类
*/
private void synchronizedGetClass() {
synchronized (this.getClass()) {
System.out.println("synchronizedGetClass");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
这里的两种用法是同步块的用法,这里表示只有获取到这个类锁才能进入这个代码块。
4 同步this实例
这也是同步块的用法,表示锁住整个当前对象实例,只有获取到这个实例的锁才能进入这个方法。
/**
* 用在this
*/
private void synchronizedThis() {
synchronized (this) {
System.out.println("synchronizedThis");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
用法和同步普通方法锁一样,都是锁住整个当前实例。
5 同步对象实例
这也是同步块的用法,和上面的锁住当前实例一样,这里表示锁住整个 LOCK 对象实例,只有获取到这个 LOCK 实例的锁才能进入这个方法。
/**
* 用在对象
*/
private void synchronizedInstance() {
synchronized (LOCK) {
System.out.println("synchronizedInstance");
try {
Thread.sleep(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
另外,类锁与实例锁不相互阻塞,但相同的类锁,相同的当前实例锁,相同的对象锁会相互阻塞。
synchronized关键字修饰的代码块被称为同步块(Synchronized Block),其语法如下所示:
synchronized (锁句柄) {
// 在此代码块中访问共享数据
}
synchronized关键字所引导的代码块就是临界区。锁句柄是一个对象的引用(或者能够返回对象的表达式)。例如,锁句柄可以填写为this关键字(表示当前对象)。锁句柄对应的监视器就被称为响应同步块的引导锁。
线程在执行临界区代码的时候必须持有该临界区的引导锁。一个线程执行到同步块(同步方法也可看作同步块)时必须先申请该同步块的引导锁,只有申请成功(获得)该锁的线程才能够执行相应的临界区。一个线程执行完临界区代码后引导该临界区的锁就会被自动释放。在这个过程中,线程对内部锁的申请与释放的动作由Java 虚拟机负责代为实施,这也正是synchronized实现的锁被称为内部锁的原因。
内部锁的使用并不会导致锁泄漏。这是因为Java编译器(javac)在将同步块代码编译为字节码的时候,对临界区中可能抛出的而程序代码中又未捕获的异常进行了特殊(代为)处理,这使得临界区的代码即使抛出异常也不会妨碍内部锁的释放。
Reference
《Java多线程编程实战指南(核心篇)》黄文海