推荐:Java并发编程汇总
Java并发编程一ReentrantReadWriteLock初使用
ReentrantReadWriteLock
是一种读写锁,从类名也可以看出来。
ReentrantReadWriteLock
类有两个属性ReentrantReadWriteLock.ReadLock readerLock
(代表读锁)、ReentrantReadWriteLock.WriteLock writerLock
(代表写锁)。
/** Inner class providing readlock */
private final ReentrantReadWriteLock.ReadLock readerLock;
/** Inner class providing writelock */
private final ReentrantReadWriteLock.WriteLock writerLock;
ReadLock
类和WriteLock
类都是ReentrantReadWriteLock
类的内部类,它们都实现了Lock
接口。
public static class ReadLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -5992448646407690164L;
private final Sync sync;
protected ReadLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquireShared(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireSharedInterruptibly(1);
}
public boolean tryLock() {
return sync.tryReadLock();
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireSharedNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.releaseShared(1);
}
public Condition newCondition() {
throw new UnsupportedOperationException();
}
public String toString() {
int r = sync.getReadLockCount();
return super.toString() +
"[Read locks = " + r + "]";
}
}
public static class WriteLock implements Lock, java.io.Serializable {
private static final long serialVersionUID = -4992448646407690164L;
private final Sync sync;
protected WriteLock(ReentrantReadWriteLock lock) {
sync = lock.sync;
}
public void lock() {
sync.acquire(1);
}
public void lockInterruptibly() throws InterruptedException {
sync.acquireInterruptibly(1);
}
public boolean tryLock( ) {
return sync.tryWriteLock();
}
public boolean tryLock(long timeout, TimeUnit unit)
throws InterruptedException {
return sync.tryAcquireNanos(1, unit.toNanos(timeout));
}
public void unlock() {
sync.release(1);
}
public Condition newCondition() {
return sync.newCondition();
}
public String toString() {
Thread o = sync.getOwner();
return super.toString() + ((o == null) ?
"[Unlocked]" :
"[Locked by thread " + o.getName() + "]");
}
public int getHoldCount() {
return sync.getWriteHoldCount();
}
}
我们来演示一下怎么使用ReentrantReadWriteLock
。
测试代码:
package lock.readwrite;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class CinemaReadWrite {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock();
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
private static void read() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
}
}
private static void write() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写入");
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
}
}
public static void main(String[] args) {
new Thread(()->read(),"Thread1").start();
new Thread(()->read(),"Thread2").start();
new Thread(()->write(),"Thread3").start();
new Thread(()->write(),"Thread4").start();
}
}
输出:
Thread1得到了读锁,正在读取
Thread2得到了读锁,正在读取
Thread2释放读锁
Thread1释放读锁
Thread3得到了写锁,正在写入
Thread3释放写锁
Thread4得到了写锁,正在写入
Thread4释放写锁
很明显可以看出,读锁是可以被多个线程同时持有的,而写锁却不行,这也很正常,读数据并不会修改数据,所以多个线程一起持有读锁也是线程安全的,而写数据就不一样了。
规则如下:
- 线程持有读锁,其他线程想要获取读锁是可行的。
- 线程持有读锁,其他线程想要获取写锁是不可行的。
- 线程持有写锁,其他线程想要获取读锁是不可行的。
- 线程持有写锁,其他线程想要获取写锁是不可行的。
读写锁的方法就不演示了,看下面这篇博客即可。
Java并发编程一Lock和ReentrantLock初使用
fair属性
设置fair
属性为true
,则表明为公平锁,设置为false
,则表明为非公平锁。
首先来看看设置fair
属性为true
的情况。
/**
* Creates a new {@code ReentrantReadWriteLock} with
* the given fairness policy.
*
* @param fair {@code true} if this lock should use a fair ordering policy
*/
public ReentrantReadWriteLock(boolean fair) {
sync = fair ? new FairSync() : new NonfairSync();
readerLock = new ReadLock(this);
writerLock = new WriteLock(this);
}
fair
属性为true
,sync
会被赋值为new FairSync()
。
/**
* Fair version of Sync
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -2274990926593161451L;
final boolean writerShouldBlock() {
return hasQueuedPredecessors();
}
final boolean readerShouldBlock() {
return hasQueuedPredecessors();
}
}
看看hasQueuedPredecessors()
的源码。
/**
* Queries whether any threads have been waiting to acquire longer
* than the current thread.
*/
public final boolean hasQueuedPredecessors() {
// The correctness of this depends on head being initialized
// before tail and on head.next being accurate if the current
// thread is first in queue.
Node t = tail; // Read fields in reverse initialization order
Node h = head;
Node s;
return h != t &&
((s = h.next) == null || s.thread != Thread.currentThread());
}
从代码与注释中,可以很明显看出,如果有其他线程排在当前线程前面,当前线程就要等待前面的线程先获取锁,读写锁都是如此,这种获取锁的顺序,可见是公平的,先来先得。
再来看看设置fair
属性为false
的情况。
fair
属性为false
,sync
会被赋值为new NonfairSync()
。
/**
* Nonfair version of Sync
*/
static final class NonfairSync extends Sync {
private static final long serialVersionUID = -8159625535654395037L;
final boolean writerShouldBlock() {
return false; // writers can always barge
}
final boolean readerShouldBlock() {
/* As a heuristic to avoid indefinite writer starvation,
* block if the thread that momentarily appears to be head
* of queue, if one exists, is a waiting writer. This is
* only a probabilistic effect since a new reader will not
* block if there is a waiting writer behind other enabled
* readers that have not yet drained from the queue.
*/
return apparentlyFirstQueuedIsExclusive();
}
}
writerShouldBlock()
一直会返回为false
,可见想要获取写锁的当前线程不需要等待前面的线程先获取锁,它是可以去抢锁的。
/**
* Returns {@code true} if the apparent first queued thread, if one
* exists, is waiting in exclusive mode. If this method returns
* {@code true}, and the current thread is attempting to acquire in
* shared mode (that is, this method is invoked from {@link
* #tryAcquireShared}) then it is guaranteed that the current thread
* is not the first queued thread. Used only as a heuristic in
* ReentrantReadWriteLock.
*/
final boolean apparentlyFirstQueuedIsExclusive() {
Node h, s;
return (h = head) != null &&
(s = h.next) != null &&
!s.isShared() &&
s.thread != null;
}
/**
* Returns true if node is waiting in shared mode.
*/
final boolean isShared() {
return nextWaiter == SHARED;
}
isShared()
是用来判断该结点是不是在等待共享锁,也就是读锁,所以从上面的代码我们可以知道,如果第一个结点是想要获取写锁的,那么想要获取读锁的当前线程是不能抢的,否则是可以抢的。
锁降级
写锁是可以降级为读锁的,而读锁是不可以升级为写锁的
(认为写锁比读锁级别更高)。
测试代码:
package lock.readwrite;
import java.util.concurrent.locks.ReentrantReadWriteLock;
public class Upgrading {
private static ReentrantReadWriteLock reentrantReadWriteLock = new ReentrantReadWriteLock(
false);
private static ReentrantReadWriteLock.ReadLock readLock = reentrantReadWriteLock.readLock();
private static ReentrantReadWriteLock.WriteLock writeLock = reentrantReadWriteLock.writeLock();
private static void readUpgrading() {
readLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了读锁,正在读取");
Thread.sleep(1000);
System.out.println("升级会带来阻塞");
writeLock.lock();
System.out.println(Thread.currentThread().getName() + "获取到了写锁,升级成功");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
}
}
private static void writeDowngrading() {
writeLock.lock();
try {
System.out.println(Thread.currentThread().getName() + "得到了写锁,正在写入");
Thread.sleep(1000);
readLock.lock();
System.out.println("在不释放写锁的情况下,直接获取读锁,成功降级");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
System.out.println(Thread.currentThread().getName() + "释放读锁");
readLock.unlock();
System.out.println(Thread.currentThread().getName() + "释放写锁");
writeLock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
System.out.println("演示降级是可行的");
Thread thread1 = new Thread(() -> writeDowngrading(), "Thread1");
thread1.start();
thread1.join();
System.out.println("------------------");
System.out.println("演示升级是不行的");
Thread thread2 = new Thread(() -> readUpgrading(), "Thread2");
thread2.start();
}
}
输出:
演示降级是可行的
Thread1得到了写锁,正在写入
在不释放写锁的情况下,直接获取读锁,成功降级
Thread1释放读锁
Thread1释放写锁
------------------
演示升级是不行的
Thread2得到了读锁,正在读取
升级会带来阻塞
线程在锁升级过程中被一直阻塞着,这也很明显验证了写锁是可以降级为读锁的,而读锁是不可以升级为写锁的
。为什么不可以呢?这样设计是有原因的,因为锁升级容易引起死锁。
假如,现在线程A
与线程B
都持有了读锁,这是允许的,现在它们都想要进行锁升级,就是升级为写锁,所以线程A
要等待线程B
释放掉读锁才能去升级成写锁,而线程B
要等待线程A
释放掉读锁才能去升级成写锁,这样就造成了死锁。
而锁降级是安全的,因为写锁只能由一个线程持有,该线程进行锁降级是很自由的,没有其他线程来干扰。