• Day125.JUC


    目录

    一、JUC概述及回顾

    二、Lock 锁

    可重入锁 (递归锁) ReentrantLock

    公平锁

    限时等待上锁 tryLock (解决死锁)  

    三、读写锁  (ReentrantReadWriteLock)

    锁降级 (了解)


    一、JUC概述及回顾

    在 Java 5.0 提供了 java.util.concurrent(简称JUC)包,在此包中增加了在并发编程中很常用的工具类。此包包括了几个小的、已标准化的可扩展框架,并提供一些功能实用的类,没有这些类,一些功能会很难实现或实现起来冗长乏味。

    Int i = 0, i ++; 非原子性,分三步;需要用到atomic 原子性容器

    线程和进程

    每一个程序都有一个进程,操作系统动态执行的基本单元;

    一个进程中可以包含若干个线程;

    生活实例:

    使用QQ,查看进程一定有一个QQ.exe的进程,我可以用qq和A文字聊天,和B视频聊天,给C传文件,给D发一段语言,这就是线程。QQ支持录入信息的搜索。

    大四的时候写论文,用word写论文,同时用QQ音乐放音乐,同时用QQ聊天,多个进程。

    word如没有保存,停电关机,再通电后打开word可以恢复之前未保存的文档,word也会检查你的拼写,两个线程:容灾备份,语法检查

    Nginx 多个进程

    并行和并发

    并行:同一时间多个线程在执行,同一时刻多个线程在访问同一个资源,  (烧水泡面)

    并发:同一时间多个线程在做同一件事 (秒杀、春运抢票)

    wait/sleep的区别

    wait 释放锁sleep 不释放锁

    wait是Object的方法,sleep是Thread

    wait 一般搭配 notify 使用

    相同点:在哪睡的在哪醒

    创建线程的方式:

    继承Thread抽象类;

    实现Runnable接口:

    实现JUC 的Callable接口

    实现线程池 (常用)

    lambda表达式

    Lambda 是一个匿名函数,使代码更简洁、更灵活

    左侧:指定了 Lambda 表达式需要的所有参数

    右侧:指定了 Lambda 体,即 Lambda 表达式要执行的功能

    复制小括号(参数),写死右箭头(->),落地大括号{}

    前提:Lambda,省略了方法名。要求必须是函数式接口 @FunctionalInterface

    1. //Foo foo = (int x,int y) -> {return x+y;};
    2. Foo foo = (x,y) -> x+y;
    3. System.out.println(foo.add(10,20));

    函数式接口扩展

    1.8之后 接口可以实现default(缺省方法)方法、静态方法

    1. @FunctionalInterface
    2. interface Foo{
    3. public int add(int x,int y);
    4. default int sub(int x,int y){
    5. return x - y;
    6. }
    7. public static int div(int x,int y){
    8. return x/y;
    9. }
    10. }

    synchronized的8锁问题

    多线程编程模板上:高内聚,低耦合;线程 操作 资源类

    1. // 资源类
    2. class Phone{
    3. // 发送短信
    4. public synchronized void sendMsg() throws InterruptedException {
    5. System.out.println("----发送短信方法....");
    6. }
    7. // 发送邮件
    8. public synchronized void sendEmail(){
    9. System.out.println("----发送邮件方法....");
    10. }
    11. // 打电话
    12. public void getHello(){
    13. System.out.println("----打电话 hello word!....");
    14. }
    15. }
    16. //1. 标准访问,先打印短信还是邮件
    17. //2. 停4秒在短信方法内,先打印短信还是邮件
    18. //3. 普通的hello方法,是先打短信还是hello
    19. //4. 现在有两部手机,先打印短信还是邮件
    20. //5. 两个静态同步方法,1部手机,先打印短信还是邮件
    21. //6. 两个静态同步方法,2部手机,先打印短信还是邮件
    22. //7. 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件
    23. //8. 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件
    24. public class Lock_8 {
    25. public static void main(String[] args) {
    26. // 线程操作资源类
    27. Phone phone = new Phone();
    28. // Phone phone2 = new Phone();
    29. // 定义一个线程:
    30. new Thread(()->{
    31. try {
    32. phone.sendMsg();
    33. } catch (InterruptedException e) {
    34. e.printStackTrace();
    35. }
    36. },"AA").start();
    37. // 定义一个线程:
    38. new Thread(()->{
    39. phone.sendEmail();
    40. // phone2.sendEmail();
    41. // phone.getHello();
    42. },"BB").start();
    43. }
    44. }
    45. *答案
    46. //1. 标准访问,先打印短信还是邮件(短信)
    47. //2. 停4秒在短信方法内,先打印短信还是邮件(短信)
    48. //3. 普通的hello方法,是先打短信还是hello(hello)
    49. //4. 现在有两部手机,先打印短信还是邮件(邮件)
    50. //5. 两个静态同步方法,1部手机,先打印短信还是邮件(短信)
    51. //6. 两个静态同步方法,2部手机,先打印短信还是邮件(短信)
    52. //7. 1个静态同步方法,1个普通同步方法,1部手机,先打印短信还是邮件(邮件)
    53. //8. 1个静态同步方法,1个普通同步方法,2部手机,先打印短信还是邮件(邮件)

    普通同步方法,锁是当前实例对象 this

    静态同步方法,锁是当前类的Class对象。

    同步方法块,锁是Synchonized括号里配置的对象

    所有的静态同步方法用的是同一把锁—— 类对象本身

    二、Lock 锁

    Lock 实现提供了比使用同步方法和语句可以获得的更广泛的锁操作。它们允许更灵活的结构,可能具有非常不同的属性,并且可能支持多个关联的条件对象。 (公平锁,非公平锁,共享锁,独占锁...)

    Lock 是一个接口,这里主要有三个实现:ReentrantLock(可重复的)、ReentrantReadWriteLock.ReadLock、ReentrantReadWriteLock.WriteLock (可重复的读写锁)

    参考API:

    注意:synchronized和Lock不能同时使用

    1. class X {
    2. private final ReentrantLock lock = new ReentrantLock();
    3. // ...
    4. public void m() {
    5. lock.lock(); // 上锁
    6. try {
    7. // ... method body
    8. } finally {
    9. lock.unlock() // 解锁
    10. }
    11. }
    12. }

    改造后的售票方法

    1. //资源类
    2. class Ticket{
    3. private Integer number = 20;
    4. private Lock lock = new ReentrantLock();
    5. public void sale(){
    6. lock.lock(); //上锁
    7. try {
    8. //判断有没有票
    9. if(number<=0){
    10. System.out.println("票已售空");
    11. return;
    12. }
    13. Thread.sleep(200);
    14. number--;
    15. System.out.println(Thread.currentThread().getName()+"卖票成功,剩余票数"+number);
    16. } catch (InterruptedException e) {
    17. e.printStackTrace();
    18. } finally {
    19. lock.unlock(); //解锁
    20. }
    21. }
    22. }

    可重入锁 (递归锁) ReentrantLock

    可重入锁又名递归锁(锁可以传递),是指在同一个线程在外层方法获取锁的时候,再进入该线程的内层方法会自动获取锁。Java中ReentrantLock 和 synchronized都是可重入锁,可重入锁的一个优点是可一定程度避免死锁。 

    1. class A{
    2. public synchronized void aa{
    3. ......
    4. bb();
    5. ......
    6. }
    7. public synchronized void bb{
    8. ......
    9. }
    10. }
    11. A a = new A();
    12. a.aa();

    ReentrantLock和synchronized区别

    二者都是独占锁

    (1) synchronized加锁和解锁的过程自动进行,ReentrantLock 上锁解锁需要手动进行,比较灵活,

    (2) synchronized可重入,因为加锁和解锁自动进行,不必担心最后是否释放锁;ReentrantLock也可重入,但加锁和解锁需要手动进行,且次数需一样,否则其他线程无法获得锁。

    (3) synchronized不可响应中断,一个线程获取不到锁就一直等着;ReentrantLock可以响应中断。

    公平锁

    所谓公平锁,也就是在锁上等待时间最长的线程将获得锁的使用权。通俗的理解就是谁排队时间最长谁先执行获取锁。

    在创建ReentrantLock锁时,加入Boolean参数,默认非公平

    注意:底层多维护了一个队列效率会降低

    private Lock lock = new ReentrantLock(true);
    ReentrantLock()
              创建一个 ReentrantLock 的实例。
    ReentrantLock(boolean fair)
              创建一个具有给定公平策略的 ReentrantLock(公平锁)

    限时等待上锁 tryLock (解决死锁)  

    tryLock 方法可以选择传入指定的等待时间,无参则表示立即返回锁申请的结果true表示获取锁成功,false表示获取锁失败。我们可以将这种方法用来解决死锁问题

     booleantryLock()
              仅在调用时锁未被另一个线程保持的情况下,才获取该锁。
     booleantryLock(long timeout, TimeUnit unit)
              如果锁在给定等待时间内没有被另一个线程保持,且当前线程未被中断,则获取该锁。
    1. boolean flag = false;
    2. try {
    3. //限时等待上锁
    4. flag = lock.tryLock(3,TimeUnit.SECONDS);
    5. } catch (InterruptedException e) {
    6. e.printStackTrace();
    7. }

    三、读写锁  (ReentrantReadWriteLock)

    读写锁允许同一时刻被多个读线程访问写线程访问时,读线程和其他的写线程都会被阻塞

    同一时刻允许多个线程对共享资源进行读操作;同一时刻只允许一个线程对共享资源进行写操作;

    生活案例:开会投影仪、直播、红蜘蛛

    • 写写不可并发
    • 读写不可并发 (脏读)
    • 读读可以并发

    避免死锁:锁的轻一点,范围小一些

    1. 创建实例 没有锁的情况

    1. class MyCache{
    2. // volatile : 在多线程中, 被它修饰的变量是可见的!(防止指令重排)
    3. private volatile Map map = new HashMap<>();
    4. //写数据
    5. public void pot(String key,String value){
    6. try {
    7. System.out.println(Thread.currentThread().getName()+"开始写入-----");
    8. Thread.sleep(200);
    9. map.put(key, value);
    10. System.out.println(Thread.currentThread().getName()+"写入完成-----");
    11. } catch (InterruptedException e) {
    12. e.printStackTrace();
    13. }
    14. }
    15. //写数据
    16. public void get(String key){
    17. try {
    18. System.out.println(Thread.currentThread().getName()+"开始读取-----");
    19. Thread.sleep(200);
    20. String msg = map.get(key);
    21. System.out.println(Thread.currentThread().getName()+"---读取完成......."+msg);
    22. } catch (InterruptedException e) {
    23. e.printStackTrace();
    24. }
    25. }
    26. }
    27. public class ReentrantReadWriteLockDemo {
    28. public static void main(String[] args) {
    29. MyCache myCache = new MyCache();
    30. //创建5个写线程
    31. for (int i = 0; i < 5; i++) {
    32. String num = String.valueOf(i);
    33. new Thread(()->{
    34. myCache.pot(num,num);
    35. },num).start();
    36. }
    37. //创建5个写线程
    38. for (int i = 0; i < 5; i++) {
    39. String num = String.valueOf(i);
    40. new Thread(()->{
    41. myCache.get(num);
    42. },num).start();
    43. }
    44. }
    45. }

      

    2. 参考API

    1. class CachedData {
    2. Object data;
    3. volatile boolean cacheValid;
    4. ReentrantReadWriteLock rwl = new ReentrantReadWriteLock();
    5. void processCachedData() {
    6. rwl.readLock().lock();
    7. if (!cacheValid) {
    8. // Must release read lock before acquiring write lock
    9. rwl.readLock().unlock();
    10. rwl.writeLock().lock();
    11. // Recheck state because another thread might have acquired
    12. // write lock and changed state before we did.
    13. if (!cacheValid) {
    14. data = ...
    15. cacheValid = true;
    16. }
    17. // Downgrade by acquiring read lock before releasing write lock
    18. rwl.readLock().lock();
    19. rwl.writeLock().unlock(); // Unlock write, still hold read
    20. }
    21. use(data);
    22. rwl.readLock().unlock();
    23. }
    24. }

    3. 加入读写锁

    1. class MyCache{
    2. // volatile : 在多线程中, 被它修饰的变量是可见的!(防止指令重排)
    3. private volatile Map map = new HashMap<>();
    4. //读写锁
    5. private final ReentrantReadWriteLock rwlock = new ReentrantReadWriteLock();
    6. //写数据
    7. public void pot(String key,String value){
    8. rwlock.writeLock().lock(); //上写锁 (排他锁)
    9. try {
    10. System.out.println(Thread.currentThread().getName()+"开始写入-----");
    11. Thread.sleep(200);
    12. map.put(key, value);
    13. System.out.println(Thread.currentThread().getName()+"写入完成-----");
    14. } catch (InterruptedException e) {
    15. e.printStackTrace();
    16. }finally {
    17. rwlock.writeLock().unlock(); //解开写锁
    18. }
    19. }
    20. //写数据
    21. public void get(String key){
    22. rwlock.readLock().lock(); //读写锁 (共享锁)
    23. try {
    24. System.out.println(Thread.currentThread().getName()+"开始读取-----");
    25. Thread.sleep(200);
    26. String msg = map.get(key);
    27. System.out.println(Thread.currentThread().getName()+"---读取完成......."+msg);
    28. } catch (InterruptedException e) {
    29. e.printStackTrace();
    30. }finally {
    31. rwlock.readLock().unlock(); //解开写锁
    32. }
    33. }
    34. }

    锁降级 (了解)

    独占改为共享可以(降级),共享改为独占不可(升级)。

    什么是锁降级,锁降级就是从写锁降级成为读锁。在当前线程拥有写锁的情况下,再次获取到读锁,随后释放写锁的过程就是锁降级。这里可以举个例子:

    1. // 锁的降级:从写锁降级成为读锁,随后释放写锁的过程就是锁降级!
    2. public void c(){
    3. rwl.writeLock().lock();
    4. System.out.println("写锁--------------");
    5. //写锁还没有释放之前,获取读锁
    6. rwl.readLock().lock();
    7. System.out.println("读锁===============");
    8. rwl.writeLock().unlock();
    9. System.out.println("释放写锁+++++++++++");
    10. rwl.readLock().unlock();
    11. System.out.println("释放读锁###########");
    12. }

    读写锁总结

    1. 支持公平/非公平策略

    2. 支持可重入
    - 同一读线程在获取了读锁后还可以获取读锁
    - 同一写线程在获取了写锁之后既可以再次获取写锁又可以获取读锁

    3. 支持锁降级,不支持锁升级

    4. 读写锁如果使用不当,很容易产生“饥饿”问题:
    - 在读线程非常多,写线程很少的情况下,容易导致写线程“饥饿”,虽然使用“公平”策略可以一定程度上缓解这个问题,但“公平”策略会使系统吞吐量降低。 (底层排队,维护队列效率降低)

    5. Condition 条件支持 (钥匙)
    - 写锁可以通过newCondition()方法获取Condition对象。但是读锁是没法获取Condition对象,读锁调用newCondition()方法会直接抛出UnsupportedOperationException

  • 相关阅读:
    DDD 架构分层,MQ消息要放到那一层处理?
    【攻防世界】学习记录-crypto(系数2 part2)
    光速掌握-CSS预处理器SASS从入门到高级(上)
    【天池竞赛】心跳数据挖掘
    Python快速刷题网站——牛客网 数据分析篇(七)
    Python进阶学习:json.dumps()和json.dump()的区别
    LeetCode50天刷题计划(Day 31— 插入区间(9.00-11.20)
    电脑开不了机怎么办?三招帮你成功解决!
    PaxCompiler语言的编译器
    终于有大佬把“计算机底层原理“全部总结出来了
  • 原文地址:https://blog.csdn.net/a111042555/article/details/126314569