1. ReadWriteLock
前面说到,ReentrantLock
可以替代synchronized
实现线程同步,方便我们进行多线程开发。
但是在有些场景下ReentrantLock
效率比较低,比如论坛上,大多数人都只是阅读(读),发论坛(写)几率比较小。如果使用ReentrantLock
,那么一个用户在读的时候,对象就被锁住了,暂时不再允许其他用户读,这样在读的请求量非常高的情形下,效率比较低。
ReadWriteLock
允许多个线程在没有写入时同时读取(提高性能),但只允许一个线程写入。
示例代码如下:
public class Counter {
private final ReadWriteLock rwLock = new ReentrantReadWriteLock();
private final Lock rLock = rwLock.readLock();
private final Lock wLock = rwLock.writeLock();
private final int[] counts = new int[10];
public void inc(int index) throws InterruptedException{
wLock.lock();
try {
counts[index] += 1;
Thread.sleep(1000);
} finally {
wLock.unlock();
}
}
public int[] get() throws InterruptedException {
rLock.lock();
try {
Thread.sleep(1000);
return Arrays.copyOf(counts, counts.length);
} finally {
rLock.unlock();
}
}
}
ReadWriteLock
在读的过程中不允许进行写入,需要等待读线程释放锁后才能获取写锁,这种读锁是一种悲观锁。
2. StampedLock
StampedLock
支持在读的过程中进行写入,能够提高并发效率,但是这样可能会导致读写不一致的问题,所以需要判断在读的过程中是否有写入,如果有写入就再读一次(此时采用悲观锁,不允许其他写操作)就好了,这种读锁是一种乐观锁。
示例代码如下:
public class Point {
private final StampedLock stampedLock = new StampedLock();
private double x;
private double y;
public void move(double deltaX, double deltaY) {
long stamp = stampedLock.writeLock();
ArrayBlockingQueue
try {
x += deltaX;
y += deltaY;
} finally {
stampedLock.unlockWrite(stamp);
}
}
public double distanceFromOrigin() {
// 尝试用乐观锁进行读
long stamp = stampedLock.tryOptimisticRead();
// 假定原先为(100, 200),那么这里读取x = 100
double currentX = x;
/* 这里其他线程可能进行了写操作,写入(300, 400) */
// 那么这里读取y = 400
double currentY = y;
// 此时坐标为(100, 400),是不对的。
// 判断是否在读的过程中进行了写入
if (!stampedLock.validate(stamp)) {
// 使用悲观锁再读一次
stamp = stampedLock.readLock();
try {
currentX = x;
currentY = y;
} finally {
stampedLock.unlockRead(stamp);
}
}
return Math.sqrt(currentX * currentX + currentY * currentY);
}
}
因为写入的几率比较小,所以大部分情况下代码中的if
是进不去的,性能不会受到很多影响,同时注意这里的StampedLock
是一种不可重入锁。
3. 线程安全的集合
在多线程编程中,集合也是经常会使用过的。
但我们常见的集合,例如ArrayList
、Map
、Queue
等都是非线程安全的,需要转换成对应的线程安全集合。
Java标准库的java.util.concurrent
包提供的线程安全的集合,对应关系如下表:
interface | non-thread-safe | thread-safe |
---|---|---|
List | ArrayList | CopyOnWriteArrayList |
Map | HashMap | ConcurrentHashMap |
Set | HashSet / TreeSet | CopyOnWriteArraySet |
Queue | ArrayDeque / LinkedList | ArrayBlockingQueue / LinkedBlockingQueue |
Deque | ArrayDeque / LinkedList | LinkedBlockingDeque |
由于它们接口一样,所以在使用时只需要修改引用对象就行了,例如把
Queue<String> queue = new LinkedList<>();
修改成
Queue<String> queue = new LinkedBlockingQueue<>();
就可以了。
4. Atomic
atomic提供了一些原子操作,有时一些简单的操作,例如全局唯一ID生成器借助atomic就可以以无锁的方式实现线程安全,可以简化一些代码操作。