1.5 读写锁ReadWriteLock
对共享资源的访问通常可以分为读取和写入。在有些应用场景中读取可能需要花费较长时间,我们需要使用互斥锁来阻止并发的写入操作以保证数据的一致性。但是对于并发的读取线程其实并不需要使用同步。事实上只有使数据发生变化的操作才需要同步,我们希望有一种方法可以把读取和写入区分开来,读取和写入的操作之间是互斥的,但是多个读取操作可以同时进行,这样可以有效提高读取密集型程序的性能。J2SE5.0提供了ReadWriteLock接口并提供了实现该接口的ReentrantReadWriteLock类:
public interface ReadWriteLock { Lock readLock(); Lock writeLock(); } |
从接口方法中不难看出读写锁中包含读锁和写锁。实现类ReentrantReadWriteLock为我们提供了更多便捷的方法来使用读写锁,例如isWriteLocked可以用来检测是否被写锁定。
2、线程通知
除了同步锁,Java Object还有两个可用于线程间通知的同步方法wait和notify。调用对象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(); } |