JUC并发编程

LockSupport

1. LockSupport是什么

  1. LockSupport是用于创建锁和其他同步类基本线程阻塞原语
  2. LockSupport没有构造函数,说明不可以new对象,只可以使用静态方法
  3. 核心方法就是 park() 和 unpark()
  4. park()除非许可证可用,否则禁用当前线程
  5. unpark(Thread):如果给定线程不可用,则为其提供许可
  6. park()和unpark()分别是阻塞线程解除被阻塞的线程
  7. LockSupport就是对线程等待唤醒机制(wait()/notify())的优化

2. 线程等待唤醒机制

  1. 使用Object中的wait()让线程等待,使用notify()唤醒等待的线程
    1. 通过调用对象的wait()来阻塞,然后再调用该对象的notify()来唤醒被阻塞的线程
    2. wait()方法会释放当前对象的锁,会释放当前对象的锁,而sleep()方法不会释放调用对象的锁
    3. wait 方法释放了锁,但是t1线程是阻塞状态不可以争夺cpu时间片,当t2线程notify之后唤醒t1线程并进入就绪态后才可以争夺cpu时间片进行运行
    4. 如果要使用wait()和notify(),必须要包在Synchronized代码块内
    5. 必须在获得某个对象的锁之后,才可以调用该对象的wait()和notify()方法来阻塞唤醒线程,故wait和notify必须在Synchronized代码块或者方法内,且使用锁对象调用
    6. 通过对象的wait()和notify()可以实现线程阻塞和唤醒,但必须是同一个对象才可以唤醒被阻塞的线程,且必须获得对象的锁之后才可以调用对象的wait()和notify()方法来阻塞唤醒线程
    7. 且如果使用对象的wait()和notify()来阻塞唤醒线程,则此时如果顺序出错,则会导致线程一直阻塞,必须先获得对象的锁之后,才可以调用对象的wait和notify来阻塞唤醒线程,且wait会释放当前对象的锁
    8. 必须保证wait之后再notify,否则会出现线程一直阻塞现象,不是死锁,因为只有一个线程一直阻塞
    9. 死锁是两个或以上线程因为竞争资源而造成的相互等待现象
  2. 使用JUC包中Lock锁对象的Condition的await()让线程等待,使用signal()唤醒线程
    1. 通过对象的wait()和notify()可以实现线程的阻塞和唤醒,但必须保证先获得对象的锁,即必须在Synchronized代码块中;且必须保证先wait之后再notify,否则会导致一直阻塞
    2. 通过Lock锁对象.newCondition()创建Condition对象,然后通过c1.await()阻塞线程,再通过c1.signal()唤醒被阻塞的线程
    3. 通过JUC中Lock锁对象自带的Condition的await()和signal()也可以实现线程的噪声和唤醒
    4. 使用Condition对象阻塞唤醒线程时,依旧要确保先加锁,必须先获得创建Condition对象的Lock锁之后才可以使用该Condition对象的await和signal;Condition的await和signal也必须在lock和unLock锁块之中使用
    5. 且必须要保证先await()阻塞线程之后再signal()唤醒线程,否则会导致线程一直阻塞,此时不是死锁,因为只有一个线程阻塞,而不是两个或以上相互竞争资源
  3. 使用LockSupport的park()阻塞线程,使用unpark()唤醒线程
    1. 使用Object对象的wait()和notify()或者使用Condition对象的await()和signal()可以实现线程阻塞唤醒机制,但都必须满足必须先加锁,然后才可以调用方法;且必须先阻塞后再唤醒,保证顺序不变,否则会导致线程一直阻塞
    2. LockSupport的park()和unpark()也可以实现线程的噪声和唤醒,且park()是当没有许可时阻塞线程,而unpark(Thread)是如果线程没有许可则给其许可,且通过LockSupport的静态方法来调用,故此时不需要先加锁,而且可以先unpark()后再park()
    3. LockSupport是创建锁和其他同步类的基本线程阻塞原语,通过park()和unpark()来阻塞和唤醒线程,且使用Permit许可来做到阻塞和唤醒线程,每一个线程都有最多一个许可,如果存在许可证,则park()不会阻塞,且会消耗许可证;最多只可以拥有一个许可证,且每次park都会消耗一个许可证,且许可证不可以累积,通过unpark(Thread)来获得许可证
    4. LockSupport的park()调用的是UNSAFE类的park(boolean,time阻塞时间),其底层调用的是native的park(),且当线程没有许可时就会阻塞time时间,此时就需要别的线程通过unpark(thread)来给线程许可来唤醒
    5. 当线程调用LockSupport.park()后,如果该线程没有许可,则会阻塞,直到其他线程通过unpark(thread)给指定线程一个许可后才可以使其唤醒;每一个线程最多只可以有一个许可,且每次park()都会消耗一个许可
    6. Java底层是使用C++实现
    7. 如果使用Object对象的wait()/notify(),或者使用JUC中的Condition对象的await()和signal()实现线程阻塞和唤醒时,此时必须要先获得锁对象,即必须在锁块中才可以调用函数,且必须保证先阻塞再唤醒;为了克服这些限制,可以使用LockSupport的park()和unpark()来实现线程阻塞和唤醒,此时不需要在锁块中调用,直接使用LockSupport的静态方法,且不需要保证调用顺序,因为其是使用permit许可来实现的
    8. LockSupport不需要先加锁直接就可以实现线程的阻塞和唤醒,不需要先加锁,直接进行线程阻塞和唤醒,通过LockSupport.park()来加锁,通过LockSupport.unpark(Thread)来对线程唤醒,park()会消耗permit许可,而unpark()会增加许可,最多只可以有一个许可
    9. LockSuport不需要先加锁,而且可以先唤醒再阻塞,顺序相反不会影响
    10. 使用LockSupport时不需要关注执行顺序,顺序相反仍可以实现,此时可以确保某个代码执行完成后再让另一个代码执行

3. LockSupport

  1. LockSupport通过许可来实现阻塞和唤醒,且每一个线程最多只有一个许可,此时unpark()后不会再增加许可
  2. LockSupport是创建锁和其他类的线程阻塞原语,是线程阻塞的工具类,与Object的wait/notify以及Condition的await/signal相比,不需要先加锁,而且唤醒和阻塞顺序不影响最终结果通过静态方法park()和unpark(thread)来实现阻塞和唤醒;通过permit许可来实现,且每一个线程最多只可以有一个许可,且每次park()均会消耗一个许可,如果没有许可时就会阻塞,此时必须让别的线程通过unpark来给其许可才会唤醒
  3. Object和Condition的线程阻塞唤醒机制,必须先获得对应对象的锁,然后调用同一个对象的阻塞和唤醒方法才可以实现线程阻塞和唤醒
  4. 每一个线程最多只可以有一个许可,故多次unpark()的结果是一样的
### Java JUC 并发编程的核心概念 #### 1. **JUC 的核心功能** `java.util.concurrent` 及其子包 `java.util.concurrent.atomic` 和 `java.util.concurrent.locks` 提供了一系列高级工具来支持并发编程[^2]。这些工具不仅简化了多线程开发,还提高了性能和可靠性。 - **线程管理**: Java 中可以通过继承 `Thread` 类、实现 `Runnable` 接口或使用 `Callable` 来启动新线程[^1]。此外,通过 `ExecutorService` 创建线程池能够更高效地管理和复用线程资源。 - **同步机制**: 使用内置锁(如 `synchronized` 关键字)或者显式的 `Lock` API(如 `ReentrantLock`),可以控制对共享资源的竞争访问[^3]。 - **原子变量**: `AtomicInteger`, `AtomicLong` 等类提供了无锁的高性能操作方式,在高并发场景下表现尤为突出[^4]。 - **集合框架扩展**: 如 `ConcurrentHashMap` 支持高效的读写分离策略,允许更高的并发度而不会阻塞整个表结构[^5]。 --- #### 2. **常见问题及解决方案** ##### (1)**死锁问题** 当两个或更多线程互相等待对方持有的资源时会发生死锁。预防措施包括但不限于按固定顺序加锁以及减少嵌套锁定的时间窗口长度。 ```java // 错误示范:可能导致死锁的情况 Object lockA = new Object(); Object lockB = new Object(); new Thread(() -> { synchronized(lockA){ try{Thread.sleep(10);}catch(Exception e){} synchronized(lockB){} // 尝试获取第二个锁 } }).start(); new Thread(() -> { synchronized(lockB){ try{Thread.sleep(10);}catch(Exception e){} synchronized(lockA){} // 尝试获取第一个锁 } }).start(); ``` 推荐采用单一全局锁对象或者利用 `tryLock()` 方法代替传统同步语句以避免潜在风险。 --- ##### (2)**线程安全的队列** 在生产者消费者模式中经常需要用到线程安全队列。例如下面这段代码展示了如何基于 `ConcurrentLinkedQueue` 构建简单的消息传递系统: ```java import java.util.concurrent.ConcurrentLinkedQueue; public class ProducerConsumerExample { private final ConcurrentLinkedQueue<Integer> queue = new ConcurrentLinkedQueue<>(); public void produce(int taskNumber) { queue.offer(taskNumber); } public Integer consume() { return queue.poll(); } } ``` 这里需要注意的是,尽管该容器本身具备良好的吞吐能力,但在极端负载条件下仍需考虑额外缓冲区设计防止溢出等问题发生。 --- ##### (3)**线程间通信** 除了基本的通知/唤醒外,还可以借助条件变量(`Condition`)配合显示锁完成更加复杂的协作逻辑: ```java import java.util.concurrent.locks.Condition; import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; class BoundedBuffer { private final Lock lock = new ReentrantLock(); private final Condition notFull = lock.newCondition(); private final Condition notEmpty = lock.newCondition(); private final Object[] items = new Object[10]; private int putptr, takeptr, count; public void put(Object x) throws InterruptedException { lock.lock(); try { while (count == items.length) notFull.await(); items[putptr] = x; if (++putptr == items.length) putptr = 0; ++count; notEmpty.signal(); } finally { lock.unlock(); } } public Object take() throws InterruptedException { lock.lock(); try { while (count == 0) notEmpty.await(); Object x = items[takeptr]; if (++takeptr == items.length) takeptr = 0; --count; notFull.signal(); return x; } finally { lock.unlock(); } } } ``` 上述例子清晰体现了如何运用现代API构建健壮可靠的组件模型。 --- ### 性能优化建议 为了进一步提升应用效率可以从以下几个方面入手: - 减少不必要的上下文切换次数; - 合理配置工作线程数量使之匹配实际硬件环境; - 对热点路径实施精细化调整比如引入缓存技术等手段降低外部依赖频率。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值