本文节选自 Effective Java by Joshua Bloch 和 Concurrent Programming in Java by Doug Lea.
1.1 概述
多线程程序设计比单线程程序设计要困难的多,所以,如果一个库中的类能够帮助你从低层的多线程程序设计中解脱出来,那么一定要使用这个类。比如java.util.Timer。另外,util.concurrent包是一个高层次的线程工具集合。在 Java 语言中,协调对共享字段的访问的传统方法是使用同步,确保完成对共享字段的所有访问。对于现代 JVM 而言,无竞争的同步现在非常便宜。以下是两个简单例子:
public class SynchronizedCounter { private int value; public synchronized int getValue() { return value; } public synchronized int increment() { return ++value; } public synchronized int decrement() { return --value; } } public class SynchronizedMutex { private Thread curOwner = null; public synchronized void acquire() throws InterruptedException { if (Thread.interrupted()) throw new InterruptedException(); while (curOwner != null) { wait(); } curOwner = Thread.currentThread(); } public synchronized void release() { if (curOwner == Thread.currentThread()) { curOwner = null; notify(); } else { throw new IllegalStateException("not owner of mutex"); } } }
1.2 synchronized关键字
1.2.1 语义
synchronized关键字不属于方法签名的一部分。所以当子类覆盖父类的方法时,synchronized修饰符不会被继承。因此接口中的方法不能被声明为synchronized。同样地,构造函数也不能被声明为synchronized(尽管构造函数内的程序块可以被声明为synchronized)。
在java语言中,锁是递归(recursive)的,锁操作是基于“每线程”而不是“每调用”,如果调用线程已经拥有了锁,当他试图再次获得锁的时候,即使此时该锁保护的数据上有另一个完全不相关的操作正在进行,它也会成功。本质上讲,这时候该锁没有起到应有的作用。递归锁简化了多线程面向对象程序的设计和构造,但是可能会把活性失败(liveness failure)变成安全性失败(safety failure)。锁的申请和释放操作是在使用synchronized关键字的时候根据内部的获得-释放协议来使用的。所有的锁都是块结构。当进入synchronized方法或块的时候得到锁,退出的时候释放锁,即使因为异常也会释放锁。
1.2.2作为类成员函数的修饰符
当把synchronized关键字作为类成员函数的修饰符时,这时候锁定的是被调用同步方法的对象, 例如
public synchronized int increment() { return ++value; }
实际上等同于
public int increment() { synchronized(this) { return ++value; } }
子类和父类的方法使用同一个锁,但是内部类的锁和它的外部类无关,然而,一个非静态的内部类可以锁住它的外部类,例如:
synchronized(OuterClass.this){ /* body */ }
1.2.3作为类静态函数的修饰符
锁住一个对象并不代表不可访问这个对象或者其任何父类的静态数据。可以通过synchronized static方法或块来实现静态数据的保护。当把synchronized关键字作为类静态函数的修饰符时,这时候锁定的是被调用同步方法的类对象,和每个类相关的静态锁与任何其它类的锁都没有关系,包括它的父类。如果想在子类中增加一个静态同步方法来达到保护父类的静态数据的目的是不可能的,应该用明确同步块版本。以下是synchronized关键字作为类静态函数的修饰符的例子:
public synchronized static int increment() { return ++value; }
实际上等同于
public int increment() { synchronized(SynchronizedCounter.class) { return ++value; } }
1.2.4同步块
当有一个明确的对象作为同步锁的时候, 就可以使用同步块。另外, 如果只是想同步一段代码,那么可以创建一个特殊的对象来充当锁
public void foo(Object obj) { synchronized(obj) { // do something } }