目录
Lock接口
锁是用来控制多个线程访问共享资源的方式,一般情况下,一个锁能够防止多个线程同时访问共享资源(读写锁可以允许多个线程并发的访问共享资源)。Java SE 5之前,Java程序是靠synchronized关键字实现锁功能,而Java SE 5 之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。
图片来源:百度
Lock lock = new ReentrantLock();
lock.lock();
try {
// ...
} finally {
lock.unlock();
}
在finally块中释放锁,目的是保证在获取到锁之后,最终能够被释放。
队列同步器
队列同步器(AbstractQueuedSynchronizer AQS),是用来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状态,通过内置的 FIFO(First in first out 先进先出) 队列来完成资源获取线程的排队工作。
重入锁
重入锁 ReentrantLock,它能够支持一个线程对资源的重复加锁。除此之外,该锁还支持获取锁时的公平和非公平性选择(在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的)。
synchronized关键字隐式支持可重入
public class ReentrantLockDemo {
public static void main(String[] args) {
ReentrantLockDemo lockDemo = new ReentrantLockDemo();
lockDemo.synchronizedTest();
}
private void synchronizedTest() {
new Thread(() -> {
// 第一次
synchronized (this) {
System.out.println("first lock...");
int count = 0;
while (true) {
// 循环进入
synchronized (this) {
System.out.println(count + " lock...");
count++;
if (count == 3) {
break;
}
}
}
}
}).start();
}
}
ReentrantLock
public class ReentrantLockDemo {
private final Lock lock = new ReentrantLock();
public static void main(String[] args) {
ReentrantLockDemo lockDemo = new ReentrantLockDemo();
lockDemo.reentrantLockTest();
}
private void reentrantLockTest() {
new Thread(() -> {
// 第一次
lock.lock();
System.out.println("first lock...");
try {
int count = 0;
while (true) {
// 循环进入
lock.lock();
System.out.println(count + " lock...");
count++;
if (count == 3) {
break;
}
}
} finally {
lock.unlock();
}
}).start();
}
}
ReentrantLock虽然没能像synchronized关键字一样支持隐式的重进入,但是在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。
公平锁与非公平锁的优劣
公平锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平锁虽然可能造成 “饥饿”,但极少的线程切换保证了其更大的吞吐量。
读写锁
读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。
图片来源:百度
public class ReentrantReadWriteLockDemo {
static Map<String, Object> cacheMap = new HashMap<>();
static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
// 分别获取读写锁
static Lock readLock = rwl.readLock();
static Lock writeLock = rwl.writeLock();
/**
* 获取一个 key 对应的 value
*/
public static final Object get(String key) {
readLock.lock();
try {
return cacheMap.get(key);
} finally {
readLock.unlock();
}
}
/**
* 设置 key 对应的 value,并返回旧的 value
*/
public static final Object put(String key, Object value) {
writeLock.lock();
try {
return cacheMap.put(key, value);
} finally {
writeLock.unlock();
}
}
/**
* 清空所有的内容
*/
public static final void clear() {
writeLock.lock();
try {
cacheMap.clear();
} finally {
writeLock.unlock();
}
}
}
上述示例中,mapCache组合了一个非线程安全的HashMap作为缓存的实现,同时使用读写锁的读锁和写锁来保证mapCache是线程安全的。在读操作get(String key)方法中,需要获取读锁,这使得并发访问该方法时不会被阻塞。写操作put(String key, Object value)方法和clear() 方法,在更新HashMap时必须提前获取写锁,当获取写锁后,其他线程对于读锁和写锁的获取均被阻塞,而只有写锁被释放之后,其他读写操作才能继续。mapCache使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性,同时简化了编程。
LockSupport 工具
LockSuppot 定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread) 方法用来唤醒一个被阻塞的线程。
public class ParkTest {
public static void main(String[] args) {
Thread t1 = new Thread(() -> {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
LockSupport.park();
System.out.println("park...");
});
Thread t2 = new Thread(() -> {
// unpark 和 park 顺序颠倒不影响
System.out.println("unpark...");
LockSupport.unpark(t1);
});
t1.start();
t2.start();
}
}
Condition接口
Condition接口提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。
public class ConditionDemo {
Lock lock = new ReentrantLock();
Condition condition = lock.newCondition();
static long start = 0l;
public void conditionWait() throws InterruptedException {
start = System.currentTimeMillis();
lock.lock();
try {
System.out.println("conditionWait await..." + start);
condition.await();
} finally {
lock.unlock();
}
}
public void conditionSignal() {
lock.lock();
try {
System.out.println("conditionSignal signal..." + System.currentTimeMillis());
condition.signal();
System.out.println("cost time: " + (System.currentTimeMillis() - start));
} finally {
lock.unlock();
}
}
public static void main(String[] args) throws InterruptedException {
ConditionDemo conditionDemo = new ConditionDemo();
new Thread(() -> {
try {
conditionDemo.conditionWait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}).start();
Thread.sleep(1000);
new Thread(conditionDemo::conditionSignal).start();
}
}