public interface ReadWriteLock {
Lock readLock();
Lock writeLock();
}
从接口方法中不难看出读写锁中包含读锁和写锁。实现类ReentrantReadWriteLock为我们提供了更多便捷的方法来使用读写锁,例如isWriteLocked可以用来检测是否被写锁定。
2 线程通知
除了同步锁,Java Object还有两个可用于线程间通知的同步方法wait和notify。wait和notify必须和synchronized联合使用,一个获得对象锁的线程通过调用对象的wait方法可以暂时放弃锁,把自己阻塞在对象的等待队列中,好让其它等待同一把锁的线程有机会执行。当另外那个获得了锁的线程完成工作后可以调用notify方法来唤醒它继续执行。每次notify调用只能唤醒一个在等待队列中的线程,notifyAll方法可以唤醒所有在该对象等待队列中的线程。
3 最小化同步
线程同步通过让线程顺序进入同步代码块解决了多个线程竞争同一资源而引起的不确定性,但是牺牲了效率,因此为了取得更好地性能,我们需要尽可能少地使用同步。事实上并不是所有的竞争条件都是需要避免的,只有当竞争条件出现在非线程安全的代码段时才会引起问题。
3.1 Atomic 变量
如果一个操作是原子操作,例如给一个boolean 变量赋值,我们就不需要同步。Java提供了一些Atomic类,使得一些本来不是原子操作(例如自增操作++,它包含了取值、加1、赋值三个原子操作)也能够原子执行,从而不需要使用同步。
Java提供了4个基本的原子类,AtomicInteger, AtomicLong, AtomicBoolean和AtomicReference分别提供针对int,long,boolean,object的原子操作。有意思的是如果你打开JDK的源代码想看看这些原子操作是如何实现的,你会失望地发现代码里面没有使用任何同步或其它技术。如果你在自己的程序中写下同样地代码,那么它们并不是原子的。
3.2 Thread Local 变量
如果每个线程都有自己私有的成员变量,那么我们也不需要同步。ThreadLocal就是线程的私有变量,每个使用ThreadLocal变量的线程都会有自己独立的ThreadLocal对象,因此就不存在多个线程访问同一个变量的问题。当然由于ThreadLocal变量为线程私有,它也就不可以用于在多个线程间共享状态。
ThreadLocal类并不神秘,它的实现原理比较简单:每个Thread对象有自己用来存储私有ThreadLocal对象的容器ThreadLocalMap,当某个线程调用ThreadLocal对象的get()方法来 取值的时候,get方法首先会取得当前线程对象,然后取出该线程的ThreadLocalMap,然后检查自己是否已经在map中,如果自己已经存在,直接返回map中的value。如果不存在,把自己作key并初始化一个value加入到当前线程的map中。
public T get() {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null ) {
ThreadLocalMap.Entry e = map.getEntry(this );
if (e != null )
return (T)e.value;
}
return setInitialValue();
}
4 线程池Thread Pool
线程虽然不像进程需要那么多资源,但是它的创建也是有一定开销的,频繁地创建和销毁线程会降低程序的性能;此外应用程序可以创建线程的数量是受机器物理条件制约的,过多的线程会耗尽机器的资源,因此我们在设计程序的时候需要限制并发线程的数量。解决这两个问题的通常做法是使用线程池。线程池在启动的时候一次性初始化若干个线程(也可以根据负载按需启动,也有闲置一定时间的线程会被销毁的策略),然后程序把任务交给线程池去执行而不是直接交给某个线程执行,由线程池给这些任务分配线程。当某个线程执行完一个任务后,线程池会把它设成空闲状态以备下一个任务重用而不是销毁它。线程池在初始化的时候需要指定线程数量上限,当并发任务数量超过线程数量的时候,线程池不会再创建新的线程而是让新任务等待,这样我们就不在需要担心线程数量过多耗尽系统资源了。JDK1.5开始为我们提供了标准的线程池。
4.1 执行器Executor
Java的线程池实现了以下Executor接口:
public interface Executor {
void execute(Runnable command);
}
在多线程编程中,执行器是一种常用的设计模式,它的好处在于提供了一种简单有效的编程模型,我们只需把需要并发处理的工作拆分成独立的任务,然后交给执行器去执行即可而不必关心线程的创建,分配和调度。J2SE5.0主要提供了两种功能的执行器:ThreadPoolExecutor和ScheduledThreadPoolExecutor。ThreadPoolExecutor是基本的线程池实现,ScheduledThreadPoolExecutor在前者基础上增加了任务调度的功能,在把任务交给它时我们可以指定任务的执行时间,而不是立刻执行。
java.util.concurrent.Executors是用来创建线程池的工厂类,通过它提供的工厂方法,我们可以方便地创建不同特性的线程池。
4.2 Future接口
Executor接口并没有看起来那么理想,有时候我们执行一个任务是要得到计算的结果,有时候我们需要对任务有更多控制,例如知道它是否完成,或者中途终止它。返回void的execute方法并不能满足我们这些需求。当然我们可以在传入的Runnable类上下功夫来提供类似的功能,但是这样做繁琐且容易出错。既然J2SE为我们提供了线程池的标准实现把我们从多线程编程中解放出来,这些常见的需求当然也会很好地满足。事实上线程池实现了一个更为丰富的ExecutorService接口,它定义