• 《Java并发编程的艺术》读书笔记 - 第五章 - Java中的锁


    目录

    Lock接口

    队列同步器

    重入锁

    synchronized关键字隐式支持可重入

    ReentrantLock

    公平锁与非公平锁的优劣

    读写锁

    LockSupport 工具

    Condition接口


    Lock接口

    锁是用来控制多个线程访问共享资源的方式,一般情况下,一个锁能够防止多个线程同时访问共享资源(读写锁可以允许多个线程并发的访问共享资源)。Java SE 5之前,Java程序是靠synchronized关键字实现锁功能,而Java SE 5 之后,并发包中新增了Lock接口(以及相关实现类)用来实现锁功能,它提供了与synchronized关键字类似的同步功能,只是在使用时需要显式地获取和释放锁。

     图片来源:百度

    1. Lock lock = new ReentrantLock();
    2. lock.lock();
    3. try {
    4. // ...
    5. } finally {
    6. lock.unlock();
    7. }

    在finally块中释放锁,目的是保证在获取到锁之后,最终能够被释放。 


    队列同步器

    队列同步器(AbstractQueuedSynchronizer AQS),是用来构建锁或者其他同步组件的基础框架,它使用了一个 int 成员变量表示同步状态,通过内置的 FIFO(First in first out 先进先出) 队列来完成资源获取线程的排队工作。


    重入锁

    重入锁 ReentrantLock,它能够支持一个线程对资源的重复加锁。除此之外,该锁还支持获取锁时的公平和非公平性选择(在绝对时间上,先对锁进行获取的请求一定先被满足,那么这个锁是公平的,反之,是不公平的)。 

    synchronized关键字隐式支持可重入

    1. public class ReentrantLockDemo {
    2. public static void main(String[] args) {
    3. ReentrantLockDemo lockDemo = new ReentrantLockDemo();
    4. lockDemo.synchronizedTest();
    5. }
    6. private void synchronizedTest() {
    7. new Thread(() -> {
    8. // 第一次
    9. synchronized (this) {
    10. System.out.println("first lock...");
    11. int count = 0;
    12. while (true) {
    13. // 循环进入
    14. synchronized (this) {
    15. System.out.println(count + " lock...");
    16. count++;
    17. if (count == 3) {
    18. break;
    19. }
    20. }
    21. }
    22. }
    23. }).start();
    24. }
    25. }

    ReentrantLock

    1. public class ReentrantLockDemo {
    2. private final Lock lock = new ReentrantLock();
    3. public static void main(String[] args) {
    4. ReentrantLockDemo lockDemo = new ReentrantLockDemo();
    5. lockDemo.reentrantLockTest();
    6. }
    7. private void reentrantLockTest() {
    8. new Thread(() -> {
    9. // 第一次
    10. lock.lock();
    11. System.out.println("first lock...");
    12. try {
    13. int count = 0;
    14. while (true) {
    15. // 循环进入
    16. lock.lock();
    17. System.out.println(count + " lock...");
    18. count++;
    19. if (count == 3) {
    20. break;
    21. }
    22. }
    23. } finally {
    24. lock.unlock();
    25. }
    26. }).start();
    27. }
    28. }

    ReentrantLock虽然没能像synchronized关键字一样支持隐式的重进入,但是在调用lock()方法时,已经获取到锁的线程,能够再次调用lock()方法获取锁而不被阻塞。

    公平锁与非公平锁的优劣

    公平锁保证了锁的获取按照FIFO原则,而代价是进行大量的线程切换。非公平锁虽然可能造成 “饥饿”,但极少的线程切换保证了其更大的吞吐量。 


    读写锁

    读写锁在同一时刻可以允许多个读线程访问,但是在写线程访问时,所有的读线程和其他写线程均被阻塞。读写锁维护了一对锁,一个读锁和一个写锁,通过分离读锁和写锁,使得并发性相比一般的排他锁有了很大提升。

    图片来源:百度

    1. public class ReentrantReadWriteLockDemo {
    2. static Map cacheMap = new HashMap<>();
    3. static ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    4. // 分别获取读写锁
    5. static Lock readLock = rwl.readLock();
    6. static Lock writeLock = rwl.writeLock();
    7. /**
    8. * 获取一个 key 对应的 value
    9. */
    10. public static final Object get(String key) {
    11. readLock.lock();
    12. try {
    13. return cacheMap.get(key);
    14. } finally {
    15. readLock.unlock();
    16. }
    17. }
    18. /**
    19. * 设置 key 对应的 value,并返回旧的 value
    20. */
    21. public static final Object put(String key, Object value) {
    22. writeLock.lock();
    23. try {
    24. return cacheMap.put(key, value);
    25. } finally {
    26. writeLock.unlock();
    27. }
    28. }
    29. /**
    30. * 清空所有的内容
    31. */
    32. public static final void clear() {
    33. writeLock.lock();
    34. try {
    35. cacheMap.clear();
    36. } finally {
    37. writeLock.unlock();
    38. }
    39. }
    40. }

    上述示例中,mapCache组合了一个非线程安全的HashMap作为缓存的实现,同时使用读写锁的读锁和写锁来保证mapCache是线程安全的。在读操作get(String key)方法中,需要获取读锁,这使得并发访问该方法时不会被阻塞。写操作put(String key, Object value)方法和clear() 方法,在更新HashMap时必须提前获取写锁,当获取写锁后,其他线程对于读锁和写锁的获取均被阻塞,而只有写锁被释放之后,其他读写操作才能继续。mapCache使用读写锁提升读操作的并发性,也保证每次写操作对所有的读写操作的可见性,同时简化了编程。


    LockSupport 工具

    LockSuppot 定义了一组以park开头的方法用来阻塞当前线程,以及unpark(Thread) 方法用来唤醒一个被阻塞的线程。

    1. public class ParkTest {
    2. public static void main(String[] args) {
    3. Thread t1 = new Thread(() -> {
    4. try {
    5. Thread.sleep(1000);
    6. } catch (InterruptedException e) {
    7. e.printStackTrace();
    8. }
    9. LockSupport.park();
    10. System.out.println("park...");
    11. });
    12. Thread t2 = new Thread(() -> {
    13. // unpark 和 park 顺序颠倒不影响
    14. System.out.println("unpark...");
    15. LockSupport.unpark(t1);
    16. });
    17. t1.start();
    18. t2.start();
    19. }
    20. }


    Condition接口

    Condition接口提供了类似Object的监视器方法,与Lock配合可以实现等待/通知模式。 

    1. public class ConditionDemo {
    2. Lock lock = new ReentrantLock();
    3. Condition condition = lock.newCondition();
    4. static long start = 0l;
    5. public void conditionWait() throws InterruptedException {
    6. start = System.currentTimeMillis();
    7. lock.lock();
    8. try {
    9. System.out.println("conditionWait await..." + start);
    10. condition.await();
    11. } finally {
    12. lock.unlock();
    13. }
    14. }
    15. public void conditionSignal() {
    16. lock.lock();
    17. try {
    18. System.out.println("conditionSignal signal..." + System.currentTimeMillis());
    19. condition.signal();
    20. System.out.println("cost time: " + (System.currentTimeMillis() - start));
    21. } finally {
    22. lock.unlock();
    23. }
    24. }
    25. public static void main(String[] args) throws InterruptedException {
    26. ConditionDemo conditionDemo = new ConditionDemo();
    27. new Thread(() -> {
    28. try {
    29. conditionDemo.conditionWait();
    30. } catch (InterruptedException e) {
    31. e.printStackTrace();
    32. }
    33. }).start();
    34. Thread.sleep(1000);
    35. new Thread(conditionDemo::conditionSignal).start();
    36. }
    37. }

  • 相关阅读:
    PXF编译安装
    [附源码]计算机毕业设计学生社团信息管理系统Springboot程序
    每日一题:编写程序,使程序分别输出两个整数的加减乘除运算结果
    uni-app的vue与nvue的区别(更新中)
    学习java的第三十天。。。(网络编程)
    u盘刻录系统安装盘
    3.np.random
    matplotlib教程二
    【STM32】HAL库-以太网外设-LAN8720A-LWIP-无操作系统
    Jmeter post请求传参问题
  • 原文地址:https://blog.csdn.net/shuttlepro/article/details/127744100