前言:上一节讲述了锁的原理,这节先讲解锁的应用,再通过上节的原理来实现一个自定义的锁。
1 从锁开始讲起
1.1 lock
在java.util.concurrent.locks.Lock.java中的源码解释:
根据Lock接口的源码注释,Lock接口的实现,具备和同步关键字同样的内存语义。
lock的常用API
lock.lock(); // 如果一个线程拿到锁,其他线程会等待
lock.tryLock(); // 尝试获取锁,获取不到立即返回
lock.tryLock(1000L); // 尝试获取锁1秒,获取不到也立即返回
1.1.1 可重入锁ReentrantLock
package szu.vander.lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author : Vander
* @date : 2019/12/7
* @description : 可重入锁
*/
public class ReentrantDemo {
private final static ReentrantLock reentrantLock = new ReentrantLock();
public static void main(String[] args){
reentrantLock.lock();
try {
System.out.println("第1次获取锁");
System.out.println("this thread lock hold count : " + reentrantLock.getHoldCount());
reentrantLock.lock();
System.out.println("第2次获取锁");
System.out.println("this thread lock hold count : " + reentrantLock.getHoldCount());
} finally {
reentrantLock.unlock();
reentrantLock.unlock();
}
System.out.println("this thread lock hold count : " + reentrantLock.getHoldCount());
new Thread(() -> {
System.out.println(Thread.currentThread() + ": expect to get the lock!");
reentrantLock.lock();
System.out.println(Thread.currentThread() + ": had get the lock!");
}).start();
}
}
运行结果
这里说明一下ReentrantLock有公平和非公平之分。(公平锁就是哪个线程等得久就让哪个线程先执行)
1.1.2 可响应中断的ReentrantLock
package concurrent.lock;
import java.time.Instant;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
/**
* @author : Vander
* @date : 2019/12/8
* @description : 获取锁后可被中断
*/
public class LockInterruptiblyDemo {
private Lock lock = new ReentrantLock();
public static void main(String[] args) {
LockInterruptiblyDemo lockInterruptiblyDemo = new LockInterruptiblyDemo();
Runnable runnable = () -> {
try {
lockInterruptiblyDemo.getLockAndInterrupt();
} catch (InterruptedException e) {
Instant now = Instant.now();
System.out.println(String.format("%s - %s : in the main runnable func, being interrupt, %s"
, now
, Thread.currentThread()
, e.toString()));
}
};
Thread thread0 = new Thread(runnable);
Thread thread1 = new Thread(runnable);
try {
thread0.start();
Thread.sleep(500); // 等待0.5秒,让thread0先执行
thread1.start();
Thread.sleep(2000); // 两秒后,中断thread1
thread1.interrupt(); // 处于Sleep状态或处于lockInterruptibly状态能被中断
} catch (InterruptedException e) {
Instant now = Instant.now();
System.out.println(String.format("%s - %s : in the main func, being interrupt, %s"
, now
, Thread.currentThread()
, e.toString()));
}
}
public void getLockAndInterrupt() throws InterruptedException {
Instant now = Instant.now();
System.out.println(String.format("%s - %s : expect the this lock", now, Thread.currentThread()));
lock.lockInterruptibly();// 阻塞,能立即响应中断,lock.lock(),阻塞且不立即响应中断
try {
now = Instant.now();
System.out.println(String.format("%s - %s : get the this lock" +
", and start sleep 10s", now, Thread.currentThread()));
TimeUnit.SECONDS.sleep(10);
} catch (InterruptedException e) {
now = Instant.now();
System.out.println(String.format("%s - %s : in the getLockAndInterrupt func, being interrupt, %s"
, now
, Thread.currentThread()
, e.toString()));
} finally {
now = Instant.now();
System.out.println(String.format("%s - %s : run into finally", now, Thread.currentThread()));
lock.unlock();
System.out.println(String.format("%s - %s : release the this lock", now, Thread.currentThread()));
}
}
}
运行结果:线程0获得了锁,并处于Sleep状态,线程1进入阻塞并且能响应中断的状态,将线程1中断,线程1可以响应中断。
2)改为lock.lock()
运行结果:改成lock()之后,线程1就不立即响应中断了,它会一直等锁,然后进入Sleep的时候才发现中断状态为已经被改变了,才去响应中断
1.1.3 读写锁(ReadWriteLock)
为了提高读操作比写操作多的场景的性能,设计出了读写锁的思路。当某个线程进行写操作时,所以进行读操作的线程都不能来读,因为这样有可能会读取到脏数据;另一种情况是,如果某个线程仅仅是来读数据的,总不能让其它读数据的线程不能来读吧,所以这种情况下的读锁是可以共享的,属于共享锁。而第一种情况的写数据时,锁是互斥的,属于互斥锁,也称为独享锁。
设计思路:维护一对关联锁,一个用于只读操作,一个用于写入操作;
读锁可以由多个读线程同时持有,而写锁是排他的,互斥的。
示例场景
缓存组件、集合的并发线程安全性改造。
锁降级(解决读取两次数据的数据不一致问题)
写锁是线程独占,读锁是共享,所以写->读是降级。(读->写,是不能实现的,因为持有读锁的情况下,是不能再持有写锁的)
在读写锁中还会提到的概念是锁降级指的是写锁降级成为读锁。持有当前拥有的写锁的同时,再获取到读锁,随后释放写锁的过程。
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;
/**
* @author : Vander
* @date : 2019/12/8
* @description : 这里既是读写锁的一种常见应用,也是锁降级的常用之处
*/
public class CacheDataDemo {
/**
* hashMap不是线程安全的
*/
private Map cache = new HashMap<>();
private ReadWriteLock readWriteLock = new ReentrantReadWriteLock();
public T get(String key) {
// 获取读锁,保证读期间,数据没有被修改
readWriteLock.readLock().lock();
if(cache.get(key) == null) {
// 从数据库中读取,并写入缓存
try {
T value = getDataFromDB(key);
// 先释放读锁
readWriteLock.readLock().unlock();
// 获取写锁
readWriteLock.writeLock().lock();
// 再次判断是否对应的Key的缓存为空,这是为了防止获取写锁的过程中,已经有其它线程写入了缓存
// 往缓存中写数据
if(cache.get(key) == null) {
cache.put(key, value);
}
// 锁降级:持有写锁过程中,再获取读锁,再释放写锁:写锁->读锁
readWriteLock.readLock().lock();
} finally {
// 进行后续操作,可能需要继续进行读取数据的操作
readWriteLock.writeLock().unlock();
}
}
T value = cache.get(key);
readWriteLock.readLock().unlock();
return value;
}
private T getDataFromDB(String key) {
// 模拟从数据库中读取数据
return (T)new Object();
}
}
2.1 实现一个自定义的锁
2.1.1 从原理开始讲解
上一节讲解了synchronized同步关键字,从偏向锁->轻量级锁->重量级锁,自己实现的Lock,先不实现这么复杂的东西,从简单的思路出发,我们直接来实现一个简单的重量级锁,重量级锁中有两个重要的属性:锁池(等待池,即多个线程争抢这个锁,记录下这些线程争抢这个锁的Pool)、锁的拥有者,这里我们先实现一个不可重入的锁,就先不记录锁的状态了。
基本思路:
package szu.vander.lock;
import szu.vander.log.LogLevel;
import szu.vander.log.Logger;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.LockSupport;
/**
* @author : Vander
* @date : 2019/12/8
* @description : 自定义Lock实现
*/
public class UserDefinedLock implements Lock {
private final static Logger log = new Logger(false, LogLevel.INFO.getLevel());
/**
* 存放争抢锁的线程信息
*/
private volatile LinkedBlockingQueue waitLockPool = new LinkedBlockingQueue<>();
/**
* 当前锁的拥有者,存放线程信息
*/
private volatile AtomicReference owner = new AtomicReference<>();
@Override
public boolean tryLock() {
if (owner.get() == null) {
return setOwner(Thread.currentThread());
}
return false;
}
@Override
public void lock() {
// 锁池中的每个线程都要来尝试争抢锁
while (!tryLock()) {
// 将等待锁的线程放入等待池中
waitLockPool.add(Thread.currentThread());
// 对于没有抢到锁的线程全都阻塞
log.info("begin to park");
LockSupport.park(Thread.currentThread());
}
waitLockPool.remove(Thread.currentThread());
}
@Override
public void unlock() {
// 当前线程执行了解锁
if (owner.compareAndSet(Thread.currentThread(), null)) {
// 通知等待池中的所有线程去争抢锁
for (Thread thread: waitLockPool) {
// 此处如果使用waitLockPool.poll去遍历,速度将会非常慢
LockSupport.unpark(thread);
}
}
}
/**
* 使用CAS机制来修改owner的值
*
* @param owner
*/
private boolean setOwner(Thread owner) {
return this.owner.compareAndSet(null, owner);
}
@Override
public void lockInterruptibly() throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public boolean tryLock(long time, TimeUnit unit) throws InterruptedException {
throw new UnsupportedOperationException();
}
@Override
public Condition newCondition() {
throw new UnsupportedOperationException();
}
public static void main(String[] args) {
Lock lock = new UserDefinedLock();
Runnable runnable1 = new Runnable() {
@Override
public void run() {
lock.lock();
log.info("get the this lock, and start sleep");
try {
TimeUnit.SECONDS.sleep(1);
} catch (InterruptedException e) {
e.printStackTrace();
}
log.info("release the this lock");
lock.unlock();
}
};
for (int i = 0; i < 5; i++) {
new Thread(runnable1).start();
}
}
}
测试代码:
package szu.vander.test.lock;
import szu.vander.lock.UserDefinedLock;
import java.util.concurrent.TimeUnit;
/**
* @author : Vander
* @date : 2019/12/8
* @description :
*/
public class UserDefinedLockTest {
private static int sum = 0;
private static UserDefinedLock lock = new UserDefinedLock();
private static int add() {
lock.lock();
sum++;
lock.unlock();
return sum;
}
public static void main(String[] args) throws InterruptedException {
Runnable runnable = new Runnable() {
@Override
public void run() {
for (int i = 0; i < 10000; i++) {
add();
}
}
};
for (int i = 0; i < 10; i++) {
new Thread(runnable).start();
}
TimeUnit.SECONDS.sleep(5);
System.out.println("Result : " + sum);
}
}
运行结果:使用10个线程同时进行累加,不会出现加不满10w的情况,说明自定义锁也不会存在并发问题