适用场景:
你需要从数据库加载股票交易的安全代码并且考虑到性能进行了缓存。这些安全代码需要每30分钟刷新一次。在这里缓存数据需要一个单独的写线程进行生成和刷新,并且被若干读进程进行读取。这种情况下,你又要如何来保证你的读写方案做到良好的扩展和线程安全呢?
解决方案:java.util.concurrent.locks包提供了可以并行执行的写锁以及被单个线程独占的写锁的实现。这个ReadWriterLock(读写锁)接口包含了一对关联的锁,一个只读锁和一个写锁。其中readLock()(读锁)可以被多个进行读操作的线程同时持有,然而writeLock(写锁)确实独占的。通常情况下,这种实现方式可以在与以下场景下相对于互斥锁可以显著的提高性能以及程序的可扩展性
场景一:
读操作以及读的频率要频繁于写操作和写的频率。
场景二:执行场景依赖底层操作系统 -- 例如在多核处理器上为了实现更好的并行执行
ConcurrentHashMap是另外一个可以在读多于写操作的情况下提高性能的数据结构。ConcurrentHashMap允许并发读取以及独占的更新或者插入操作。
这里有一些技术可以让你更加了解锁的实现
Q:什么是可重入锁?
A:可重入锁是作为传统“等待-唤醒”方法的替代物出现的。它的基本实现理念是,每一个需要进入临界区的线程需要获取锁并且应该在随后的操作完成后释放锁。ReetrantLock可以通过减少“synchronized”关键字同步来提供更好的并发
reetrant这个单词其实意味着如果线程试图进去当前线程已持有锁保护的并且需要同步的代码块,线程会默许操作是允许的,并且不会在线程退出第二个(随后的)同步代码区释放持有的锁,只有在线程退出被同一锁保护的首先进入的同步代码块时才会释放锁,因为锁本身维护着锁的获取次数,并且如果一个已经获取锁的线程需要重新获取锁,维护的锁的大小会增加并且当前线程需要释放两次才能真正的释放锁,写线程可以获取读锁(优先级比读线程高) ---但是读线程却不能获取写锁,如果读线程尝试获取写锁,那将是永不能得到的(只能双眼包含热泪的望着).
Q:在释放锁的时候需要的注意事项?
A:锁的释放操作需要在finally块中进行,否则如果程序出现异常,那您的那把锁可能永远丢不掉(哈哈!)
Q:什么理由使你在已有synchronized关键字的情况下仍然选择使用重入锁?并且都能从中获取哪些好处?
A:重入锁在并发读的时候有更好的可扩展性。前面已经说过,java.util.concurrent.lock包中的若干锁的实现是针对于高级用户的高级工具,并且也在上面讨论过的特定场景下适用。通常来说,如果没有如下的条件作为前提还是规劝您老实的使用synchronization:
1.读操作的个数远多于写操作
2.可以通过一些测试数据证实在特定场景下同步是主要的扩展瓶颈
3.诸如超时锁等待,可中断的锁等待,非阻塞结构的,或者有多个条件需要判断,或者需要轮询锁这些普通锁不具有的或者实现不如此的情况下
Q:什么是公平锁和非公平锁?
A:ReetrantLock的构造器有一个boolean类型的参数可以用来指示是否使用公平锁还是非公平锁。公平锁是获取锁的顺序和线程请求锁的线程一致。也就是说,当前的写锁释放的时候,无论是一个写线程等待多久,只要有等待队列中有读线程在写线程之前,读操作的线程就会首先得到读锁。但是当你使用的是非公平锁的时候,获取锁的顺序就不一定是线程请求的顺序了。
如果读线程是活跃的,那么只有写线程已经获取写锁并且释放写锁后读线程才能被授予读锁,下面是一个读写锁的示例。
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class SharedData<T> {
private List<Integer> numbers = new ArrayList<Integer>(20);
private ReentrantReadWriteLock lock = new ReentrantReadWriteLock();
public List<Integer> getTechnologies() {
return numbers;
}
public void writeData(Integer number) {
String threadName = Thread.currentThread().getName();
lock.writeLock().lock(); //如果没有其他线程持有读锁和写锁,获取写锁
//写锁只允许一个线程持有
System.out.println("threads waiting for read/write lock: " + lock.getQueueLength());
// This should be always one
System.out.println("writer locks held " + lock.getWriteHoldCount());
try {
numbers.add(number);
System.out.println(">>>>>" + threadName + " writing: " + number);
Thread.sleep(2000); // 线程休眠两秒钟
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(threadName + " releasing write lock");
lock.writeLock().unlock(); //释放锁
}
}
public void readData() {
String threadName = Thread.currentThread().getName();
lock.readLock().lock();//在没有线程持有写锁的情况下获取读锁
//允许并发读操作
System.out.println("threads waiting for read/write lock: " + lock.getQueueLength());
System.out.println("reader locks held " + lock.getReadLockCount());
try {
for (Integer num: numbers) {
System.out.println("<<<<<<<" + threadName + " reading: " + num);
}
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
finally{
System.out.println(threadName + " releasing read lock");
lock.readLock().unlock(); //释放锁
}
}
}
下一步,定义读和写线程类。
public class Reader<T> extends Thread {
private SharedData<T> sharedData;
public Reader(SharedData<T> sharedData) {
this.sharedData = sharedData;
}
@Override
public void run() {
sharedData.readData();
}
}
public class Writer<T> extends Thread {
private boolean oddNumber = true;
private SharedData<T> sharedData;
public Writer(SharedData<T> sharedData, boolean oddNumber ) {
this.sharedData = sharedData;
this.oddNumber = oddNumber;
}
@Override
public void run() {
for(int i=1; i<=2; i++) {
if(!oddNumber && i%2 == 0) {
sharedData.writeData(i);
}
else if (oddNumber && !(i%2 == 0)){
sharedData.writeData(i);
}
}
}
}
最后,ReadWriteProcessor(读写主线程)类需要生成读写线程并为线程传递SharedData.
会有如下输出结果:
threads waiting for read/write lock: 0
writer locks held 1
>>>>>oddWriter writing: 1
oddWriter releasing write lock
threads waiting for read/write lock: 3
writer locks held 1
>>>>>evenWriter writing: 2
evenWriter releasing write lock
threads waiting for read/write lock: 2
threads waiting for read/write lock: 1
reader locks held 3
threads waiting for read/write lock: 0
reader locks held 3
reader locks held 2
<<<<<<<reader1 reading: 1
<<<<<<<reader2 reading: 1
<<<<<<<reader3 reading: 1
<<<<<<<reader2 reading: 2
<<<<<<<reader1 reading: 2
<<<<<<<reader3 reading: 2
reader1 releasing read lock
reader3 releasing read lock
reader2 releasing read lock
其实还有下面的一些java.util.concurrent包下的类可以在其他现实场景下起到很好的作用.
1.CountDownLatch
2.CyclicBarrier
3.Semaphore
4.Atomic 若干类