Lock是JDK1.5种新增的同步工具,其实真正的实现Lock接口的类就三个,ReentrantLock和ReentrantReadWriteLock的两个内部类(ReadLock和WriteLock实现了Lock的接口);
ReentrantLock 实现了标准的互斥操作,也就是一次只能有一个线程持有锁,也即所谓独占锁的概念。我们也一直在强调这个特点。显然这个特点在一定程度上面减低了吞吐量,实际上独占锁是一种保守的锁策略,在这种情况下任何“读/读”,“写/读”,“写/写”操作都不能同时发生。
public class ReentrantLockTest {
// 公平锁
private Lock lock = new ReentrantLock(true);
// 资源
private Resource resource = new Resource();
public static void main(String[] args) {
final ReentrantLockTest test = new ReentrantLockTest();
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 20; i++) {
service.submit(new Runnable() {
@Override
public void run() {
Random random = ThreadLocalRandom.current();
test.write(random.nextInt());
}
});
service.submit(new Runnable() {
@Override
public void run() {
test.read();
}
});
}
service.shutdown();
}
private void write(final int value) {
// 如果可以获取锁
if (lock.tryLock()) {
try {
// 执行业务逻辑
System.out.println(Thread.currentThread().getName() + "获取了锁 写入 value=" + value);
resource.setValue(String.valueOf(value));
} finally {
// 释放锁
System.out.println(Thread.currentThread().getName() + "释放了锁");
lock.unlock();
}
}
}
void read() {
if (lock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + "获取了锁 value=" + resource.getValue());
} finally {
System.out.println(Thread.currentThread().getName() + "释放了锁");
lock.unlock();
}
}
}
}
测试的结果是:
pool-1-thread-1获取了锁 写入 value=2111184282
pool-1-thread-1释放了锁
pool-1-thread-2获取了锁 value=2111184282
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 写入 value=-1679499784
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 写入 value=1678350750
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 value=1678350750
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 value=1678350750
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 写入 value=152101866
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 value=152101866
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 写入 value=1147384711
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 value=1147384711
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 写入 value=-2130483959
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 value=-2130483959
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 写入 value=417404714
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 value=417404714
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 写入 value=-405288963
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 value=-405288963
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 写入 value=-139338780
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 value=-139338780
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 写入 value=-745627488
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 value=-745627488
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 写入 value=-1455461843
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 value=-1455461843
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 写入 value=-1129478741
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 写入 value=-1560021948
pool-1-thread-2释放了锁
但实际应用场景中我们会经常遇到这样的情况:某些资源需要并发访问,并且大部分时间是用来进行读操作的,写操作比较少,而锁是有一定的开销的,当并发比较大 的时候,锁的开销就比较可观了。所以如果可能的话就尽量少用锁,如果非要用锁的话就尝试看能否能实现读写分离,将其改造为读写锁。
// 公平锁
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 写锁
private Lock writeLock = readWriteLock.writeLock();
// 读锁
private Lock readLock = readWriteLock.readLock();
// 资源
private Resource resource = new Resource();
public static void main(String[] args) {
final ReentrantReadWriteLockTest test = new ReentrantReadWriteLockTest();
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.submit(new Runnable() {
@Override
public void run() {
Random random = ThreadLocalRandom.current();
test.write(random.nextInt());
}
});
service.submit(new Runnable() {
@Override
public void run() {
test.read();
}
});
}
service.shutdown();
}
private void write(final int value) {
// 如果可以获取锁
if (writeLock.tryLock()) {
try {
// 执行业务逻辑
System.out.println(Thread.currentThread().getName() + "获取了锁 写入 value=" + value);
resource.setValue(String.valueOf(value));
} finally {
// 释放锁
System.out.println(Thread.currentThread().getName() + "释放了锁");
writeLock.unlock();
}
}
}
private void read() {
if (readLock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + "获取了锁 value=" + resource.getValue());
} finally {
System.out.println(Thread.currentThread().getName() + "释放了锁");
readLock.unlock();
}
}
}
}
测试结果是:
pool-1-thread-1获取了锁 写入 value=-702575113
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 value=-702575113
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 写入 value=-1619148924
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 value=-1619148924
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 写入 value=-1469315956
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 value=-1469315956
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 写入 value=1667915800
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 value=1667915800
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 写入 value=-1280947014
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 value=-1280947014
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 写入 value=1208950056
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 value=1208950056
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 写入 value=1089508899
pool-1-thread-1释放了锁
pool-1-thread-1获取了锁 写入 value=760397757
pool-1-thread-1释放了锁
pool-1-thread-2获取了锁 value=760397757
pool-1-thread-2释放了锁
pool-1-thread-2获取了锁 value=760397757
pool-1-thread-1获取了锁 value=760397757
pool-1-thread-2释放了锁
pool-1-thread-1释放了锁
可以发现写锁是独占锁,读锁是共享锁,那么读锁是不是无限共享呢?实际上不是的,最大同时可以背65534个共享。
public class ReentrantTest {
private Lock lock = new ReentrantReadWriteLock().readLock();
static long count = 0;
/**
*
* @param args
* @author zhangwei
*/
public static void main(String[] args) {
ReentrantTest test = new ReentrantTest();
for (;;) {
if (test.lock.tryLock()) {
System.out.println(count++);
}
}
}
}
65530
65531
65532
65533
65534
Exception in thread "main" java.lang.Error: Maximum lock count exceeded
at java.util.concurrent.locks.ReentrantReadWriteLock$Sync.tryReadLock(ReentrantReadWriteLock.java:588)
at java.util.concurrent.locks.ReentrantReadWriteLock$ReadLock.tryLock(ReentrantReadWriteLock.java:803)
at org.demo.core.lock.ReentrantTest.main(ReentrantTest.java:34)
锁降级 写线程获取写入锁后可以获取读取锁,然后释放写入锁,这样就从写入锁变成了读取锁,从而实现锁降级的特性。
public class DegradeReentrantReadWriteLockTest2 {
// 公平锁
private ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock();
// 写锁
private Lock writeLock = readWriteLock.writeLock();
// 读锁
private Lock readLock = readWriteLock.readLock();
// 资源
private Resource resource = new Resource();
public static void main(String[] args) {
final DegradeReentrantReadWriteLockTest2 test = new DegradeReentrantReadWriteLockTest2();
ExecutorService service = Executors.newCachedThreadPool();
for (int i = 0; i < 10; i++) {
service.submit(new Runnable() {
@Override
public void run() {
Random random = ThreadLocalRandom.current();
test.writeAndRead(random.nextInt());
}
});
service.submit(new Runnable() {
@Override
public void run() {
test.read();
}
});
}
service.shutdown();
}
private void writeAndRead(final int value) {
// 如果可以获取锁
try {
try {
// 写锁锁定
writeLock.lock();
// 执行业务逻辑
System.out.println(Thread.currentThread().getName() + "获取了写锁 写入 value=" + value);
resource.setValue(String.valueOf(value));
} finally {
System.out.println(Thread.currentThread().getName() + "写锁降级为读锁");
// 读锁锁定
readLock.lock();
// 释放写锁
writeLock.unlock();
}
System.out.println(resource.getValue());
} finally {
// 释放读锁
System.out.println(Thread.currentThread().getName() + "释放了读锁");
readLock.unlock();
}
}
private void read() {
if (readLock.tryLock()) {
try {
System.out.println(Thread.currentThread().getName() + "获取了读锁 value=" + resource.getValue());
} finally {
System.out.println(Thread.currentThread().getName() + "释放了读锁");
readLock.unlock();
}
}
}
}
公平锁的实现:公平性是指最先试图获取锁的线程一定可以保证最先获取
如果当前线程之前还有线程在等待,获取锁失败!通过队列排序保证获取锁的公平性。
/**
* Sync object for fair locks
*/
static final class FairSync extends Sync {
private static final long serialVersionUID = -3000897897090466540L;
final void lock() {
acquire(1);
}
/**
* Fair version of tryAcquire. Don't grant access unless
* recursive call or no waiters or is first.
*/
protected final boolean tryAcquire(int acquires) {
final Thread current = Thread.currentThread();
int c = getState();
if (c == 0) {
if (!hasQueuedPredecessors() &&
compareAndSetState(0, acquires)) {
setExclusiveOwnerThread(current);
return true;
}
}
else if (current == getExclusiveOwnerThread()) {
int nextc = c + acquires;
if (nextc < 0)
throw new Error("Maximum lock count exceeded");
setState(nextc);
return true;
}
return false;
}
}