目录
锁是用来控制多个线程访问共享资源的方式,一般情况下,一个锁能够防止多个线程同时访问共享资源(读写锁可以允许多个线程并发的访问共享资源)。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,它能够支持一个线程对资源的重复加锁。除此之外,该锁还支持获取锁时的公平和非公平性选择(在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的)。
- 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();
- }
-
- }
- 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
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使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性,同时简化了编程。
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接口提供了类似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();
- }
-
- }