Java并发线程 共享模型之管程 5

news2024/9/27 12:16:36

1. 生产者消费者

package cn.itcast.testcopy;

import cn.itcast.n2copy.util.Sleeper;
import lombok.extern.slf4j.Slf4j;

import java.util.LinkedList;

/**
 * ClassName: Test21
 * Package: cn.itcast.testcopy
 * Description: 生产者消费者
 *
 * @Author: 1043
 * @Create: 2024/9/4 - 11:11
 * @Version: v1.0
 */
public class Test21 {
    public static void main(String[] args) {
        MessgeQueue queue=new MessgeQueue(2);
        for (int i = 0; i < 3; i++) {
            int id=i;
            new Thread(()->{
                queue.put(new Message(id,"值"+id));
            },"生产者"+i).start();
        }
        new Thread(()->{
            while (true){
                Sleeper.sleep(1);
                Message take = queue.take();
            }
        },"消费者").start();
    }
}

// 消息队列类,rabbitmq是进程间通信,这个类比较简单是线程间通信
@Slf4j(topic = "c.MessgeQueue")
class MessgeQueue {
    // 消息的队列集合
    private LinkedList<Message> list = new LinkedList<>();
    // 队列容量
    private int capcity;

    public MessgeQueue(int capcity) {
        this.capcity = capcity;
    }

    public Message take() {
        // 检查队列是否为空
        synchronized (list) {
            while (list.isEmpty()) {
                try {
                    log.debug("队列为空, 消费者线程等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 从队列头部获取消息并返回
            Message message = list.removeFirst();
            log.debug("已消费消息 {}", message);
            list.notifyAll();
            return message;
        }
    }

    public void put(Message message) {
        synchronized (list) {
            // 检查对象是否已满
            while (list.size() == capcity) {
                try {
                    log.debug("队列已满, 生产者线程等待");
                    list.wait();
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            // 将消息加入队列尾部
            list.addLast(message);
            log.debug("已生产消息 {}", message);
            list.notifyAll();
        }
    }
}

final class Message {
    private int id;
    private Object message;

    public Message(int id, Object message) {
        this.id = id;
        this.message = message;
    }

    public int getId() {
        return id;
    }

    public Object getMessage() {
        return message;
    }

    @Override
    public String toString() {
        return "Message{" +
               "id=" + id +
               ", message=" + message +
               '}';
    }
}

 2. park和unpark

先看一段代码

@Slf4j(topic = "c.Testpark")
public class Testpark {
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("start...");
            sleep(2);
            log.debug("park...");
            LockSupport.park();
            log.debug("resume...");
        }, "t1");
        t1.start();

        sleep(1);
        log.debug("unpark...");
        LockSupport.unpark(t1);
    }
}

运作结果如图

 与 Object 的 wait & notify 相比

  • waitnotify 和 notifyAll 必须配合 Object Monitor 一起使用,而 parkunpark 不必。
  • park 和 unpark 是以线程为单位来【阻塞】和【唤醒】线程,而 notify 只能随机唤醒一个等待线程,notifyAll 是唤醒所有等待线程,就不那么【精确】。
  • park 和 unpark 可以先 unpark,而 wait 和 notify 不能先 notify

 原理

        每个线程都有自己的一个 Parker 对象,由三部分组成 _counter , _cond 和 _mutex 打个比喻线程就像一个旅人,Parker 就像他随身携带的背包,条件变量就好比背包中的帐篷。_counter 就好比背包中的备用干粮(0 为耗尽,1 为充足)
        调用 park 就是要看需不需要停下来歇息
                如果备用干粮耗尽,那么钻进帐篷歇息
                如果备用干粮充足,那么不需停留,继续前进
        调用 unpark,就好比令干粮充足
                如果这时线程还在帐篷,就唤醒让他继续前进
                如果这时线程还在运行,那么下次他调用 park 时,仅是消耗掉备用干粮,不需停留,继续前进,因为背包空间有限,多次调用 unpark 仅会补充一份备用干粮

3. 多把锁

一间大屋子有两个功能:睡觉、学习,互不相干。
现在小南要学习,小女要睡觉,但如果只用一间屋子(一个对象锁)的话,那么并发度很低
解决方法是准备多个房间(多个对象锁)例如

class BigRoom2 {
    public void sleep() {
        synchronized (this) {
            log.debug("sleeping 2 小时");
            Sleeper.sleep(2);
        }
    }

    public void study() {
        synchronized (this) {
            log.debug("study 1 小时");
            Sleeper.sleep(1);
        }
    }
}
BigRoom bigRoom = new BigRoom();
        new Thread(() -> {
            bigRoom.study();
        }, "小南").start();
        new Thread(() -> {
            bigRoom.sleep();
        }, "小女").start();

 此时效率很低,可改进降低锁的粒度

public class TestMultiLock {
    public static void main(String[] args) {
        BigRoom bigRoom = new BigRoom();
        new Thread(() -> {
            bigRoom.study();
        }, "小南").start();
        new Thread(() -> {
            bigRoom.sleep();
        }, "小女").start();
    }
}

@Slf4j(topic = "c.BigRoom")
class BigRoom {

    private final Object studyRoom = new Object();

    private final Object bedRoom = new Object();

    public void sleep() {
        synchronized (bedRoom) {
            log.debug("sleeping 2 小时");
            Sleeper.sleep(2);
        }
    }

    public void study() {
        synchronized (studyRoom) {
            log.debug("study 1 小时");
            Sleeper.sleep(1);
        }
    }

}

此时两个小房间互不影响。不过这种情况容易造成死锁。

死锁

        有这样的情况:一个线程需要同时获取多把锁,这时就容易发生死锁t1 线程 获得 A对象 锁,接下来想获取 B对象的锁 t2 线程 获得 B对象 锁,接下来想获取 A对象的锁 例:

@Slf4j(topic = "c.TestDeadLock")
public class TestDeadLock {
    public static void main(String[] args) {
        test1();
    }

    private static void test1() {
        Object A = new Object();
        Object B = new Object();
        Thread t1 = new Thread(() -> {
            synchronized (A) {
                log.debug("lock A");
                sleep(1);
                synchronized (B) {
                    log.debug("lock B");
                    log.debug("操作...");
                }
            }
        }, "t1");

        Thread t2 = new Thread(() -> {
            synchronized (B) {
                log.debug("lock B");
                sleep(0.5);
                synchronized (A) {
                    log.debug("lock A");
                    log.debug("操作...");
                }
            }
        }, "t2");
        t1.start();
        t2.start();
    }
}

检测死锁可以使用 jconsole工具,或者使用 jps 定位进程 id,再用 jstack 定位死锁:

活锁

        活锁出现在两个线程互相改变对方的结束条件,最后谁也无法结束,例如

@Slf4j(topic = "c.TestLiveLock")
public class TestLiveLock {
    static volatile int count = 10;
    static final Object lock = new Object();

    public static void main(String[] args) {
        new Thread(() -> {
            // 期望减到 0 退出循环
            while (count > 0) {
                sleep(0.2);
                count--;
                log.debug("count: {}", count);
            }
        }, "t1").start();
        new Thread(() -> {
            // 期望超过 20 退出循环
            while (count < 20) {
                sleep(0.2);
                count++;
                log.debug("count: {}", count);
            }
        }, "t2").start();
    }
}

一个希望向下减一个希望向上加,互相正直在一个范围内。也是被锁住了。

饥饿

        很多教程中把饥饿定义为,一个线程由于优先级太低,始终得不到 CPU 调度执行,也不能够结束,饥饿的情况不易演示,讲读写锁时会涉及饥饿问题。如哲学家就餐问题将最后一个哲学家的左右手顺序颠倒一个就不会死锁了,但会由此引发饥饿问题。

4.ReentrantLock

相对于 synchronized 它具备如下特点
        可中断
        可以设置超时时间
        可以设置为公平锁
        支持多个条件变量
        与 synchronized 一样,都支持可重入,不过synchronized是在关键字级别保护临界区而ReentrantLock在对象级别保护临界区。
        基本语法

// 获取锁
reentrantLock.lock();
try {
    // 临界区
} finally {
    // 释放锁
    reentrantLock.unlock();
}

 第一行加锁的语句放try里跟上面效果完全等价。

特性1 可重入

示例

package cn.itcast.testcopy;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

/**
 * ClassName: Test22
 * Package: cn.itcast.testcopy
 * Description:
 *
 * @Author: 1043
 * @Create: 2024/9/4 - 16:09
 * @Version: v1.0
 */
@Slf4j(topic = "c.Test22")
public class Test22 {
    private static ReentrantLock lock=new ReentrantLock();
    public static void main(String[] args) {
        lock.lock();
        try {
            log.debug("enter main");
            m1();
        }finally {
            lock.unlock();
        }
    }

    public static void m1() {
        lock.lock();
        try {
            log.debug("enter m1");
            m2();
        }finally {
            lock.unlock();
        }
    }
    public static void m2() {
        lock.lock();
        try {
            log.debug("enter m2");
        }finally {
            lock.unlock();
        }
    }
}

 

 特性2 可打断

package cn.itcast.testcopy;

import lombok.extern.slf4j.Slf4j;

import java.util.concurrent.locks.ReentrantLock;

/**
 * ClassName: Test22kedaduan
 * Package: cn.itcast.testcopy
 * Description:
 *
 * @Author: 1043
 * @Create: 2024/9/4 - 16:13
 * @Version: v1.0
 */
@Slf4j(topic = "c.Test22kedaduan")
public class Test22kedaduan {
    private static ReentrantLock lock = new ReentrantLock();

    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            try {
                // 如果没有竞争那么此方法就会获取 Lock 对象锁
                // 如果有竞争就进入阻塞队列,可以被其它线程用 interrupt 方法打断
                log.debug("尝试获取锁");
                lock.lockInterruptibly();
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("没有获得锁,返回");
                return;
            }
            try {
                log.debug("获取到锁");
            } finally {
                lock.unlock();
            }
        },"t1");
        t1.start();
    }
}

        lock.lock();//主线程先加锁
        t1.start();

        Sleeper.sleep(1);
        log.debug("打断t1");
        t1.interrupt();

t1可以被打断,不会死等下去, 这就为防止死锁发生提供了方式。

特性3 锁超时

@Slf4j(topic = "c.Test22")
public class Test22 {
    private static ReentrantLock lock = new ReentrantLock();
    public static void main(String[] args) {
        Thread t1 = new Thread(() -> {
            log.debug("尝试获得锁");
            try {
                if (!lock.tryLock(2, TimeUnit.SECONDS)) {
                    log.debug("获取不到锁");
                    return;
                }
            } catch (InterruptedException e) {
                e.printStackTrace();
                log.debug("获取不到锁");
                return;
            }
            try {
                log.debug("获得到锁");
            } finally {
                lock.unlock();
            }
        }, "t1");

        lock.lock();
        log.debug("获得到锁");
        t1.start();
        sleep(3);
        log.debug("释放了锁");
        lock.unlock();
    }
}

主线程3s后才释放锁,t1的tryLock只等2s,2s后还获取不到就放弃等待。tryLock若是没参数就只尝试一次获取不到立即放弃。

应用 解决哲学家就餐问题


public class TestDeadLock {
    public static void main(String[] args) {
        Chopstick c1 = new Chopstick("1");
        Chopstick c2 = new Chopstick("2");
        Chopstick c3 = new Chopstick("3");
        Chopstick c4 = new Chopstick("4");
        Chopstick c5 = new Chopstick("5");
        new Philosopher("苏格拉底", c1, c2).start();
        new Philosopher("柏拉图", c2, c3).start();
        new Philosopher("亚里士多德", c3, c4).start();
        new Philosopher("赫拉克利特", c4, c5).start();
        new Philosopher("阿基米德", c5, c1).start();
    }
}

@Slf4j(topic = "c.Philosopher")
class Philosopher extends Thread {
    Chopstick left;
    Chopstick right;

    public Philosopher(String name, Chopstick left, Chopstick right) {
        super(name);
        this.left = left;
        this.right = right;
    }

    @Override
    public void run() {
        while (true) {
            // 尝试获得左手筷子
            if (left.tryLock()) {
                try {
                    // 尝试获得右手筷子
                    if (right.tryLock()) {
                        try {
                            eat();
                        } finally {
                            right.unlock();
                        }
                    }
                } finally {
                    left.unlock();
                }
            }
        }
    }

    private void eat() {
        log.debug("eating...");
        Sleeper.sleep(1);
    }
}



class Chopstick extends ReentrantLock {
    String name;

    public Chopstick(String name) {
        this.name = name;
    }

    @Override
    public String toString() {
        return "筷子{" + name + '}';
    }
}

         核心在于获取右手筷子失败后放开左手的筷子,这样别人就有机会获取筷子,就不会发生死锁与饥饿了。

特性4. 公平锁

        ReentrantLock 默认是不公平的,公平锁一般没有必要,会降低并发度。

特性5 条件变量

        synchronized 中也有条件变量,就是我们前面分析原理时那个 waitSet 休息室,当条件不满足时进入 waitSet 等待。
        ReentrantLock 的条件变量比 synchronized 强大之处在于,它是支持多个条件变量的,这就好比 synchronized 是那些不满足条件的线程都在一间休息室等消息。而 ReentrantLock 支持多间休息室,有专门等烟的休息室、专门等早餐的休息室、唤醒时也是按休息室来唤醒
        使用要点:
        await 前需要获得锁
        await 执行后,会释放锁,进入 conditionObject 等待
        await 的线程被唤醒(或打断、或超时)取重新竞争 lock 锁
        竞争 lock 锁成功后,从 await 后继续执行

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2104484.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

Hadoop vs Spark

Hadoop 和 Spark 都是apache基金会下、在大数据架构中广泛使用的开源框架&#xff0c;两个框架都各自有各自的开源技术生态系统&#xff0c;用于准备、处理、管理和分析大数据集。 Hadoop 生态系统由四个主要模块组成: HDFS): Hadoop的数据存储系统&#xff0c;用于管理运行在普…

vue3中如何拿到element plus中el-tree多选的值?

在vue3中使用了element plus的el-tree组件 并将其设置为可选择的情况下如何拿到所选择的值&#xff1f; 首先我们要为el-tree设置show-checkbox&#xff08;它的作用是&#xff1a;节点是否可被选择&#xff09;&#xff0c;然后为el-tree绑定ref vue3中的ref跟vue2中的ref获取…

未来十年美业发展方向:健康与美容的结合|美业SaaS系统收银系统源码

随着人们对健康和美容的重视不断增加&#xff0c;美业正在经历一场革命性的变革。未来&#xff0c;美业的发展将更加注重健康与美容的结合&#xff0c;这一趋势将在多个领域产生深远影响。 下面博弈美业为大家阐释「为什么未来美业的发展方向是健康和美容的结合」&#xff1a;…

Java web开发常见中间件多版本下载备用

备注&#xff1a;每次换电脑都要重新构建一下环境&#xff0c;下载找资源很麻烦&#xff0c;官网英文网页找个历史版本看不懂&#xff0c;还要慢慢去搜&#xff0c;所以直接整理一波&#xff0c;需要的自行收藏。 1.nodejs自选版本下载&#xff1a; 地址&#xff1a;https://…

Facebook广告投放如何在节日季脱颖而出

众所周知&#xff0c;节日季是销售的旺季&#xff0c;根据统计基本都集中在年末。所以如果你想在今年的节日季大赚一笔&#xff0c;你需要从现在开始做准备工作&#xff0c;以便敲定你的节日季的营销策略。如果你感兴趣的话就继续看下去吧~ 1、设置转化API 在 Facebook 广告中…

leetcode回文链表

leetcode 回文链表 题目 题解 两种方式进行题解 /*** Definition for singly-linked list.* struct ListNode {* int val;* ListNode *next;* ListNode() : val(0), next(nullptr) {}* ListNode(int x) : val(x), next(nullptr) {}* ListNode(int x, Li…

[rk3399 android11]关闭声卡

使用以下命令查看声卡&#xff0c;可以看到目前有三个声卡 cat /proc/asound/cards 修改设备树 diff --git a/kernel/arch/arm64/boot/dts/rockchip/rk3399-jw-d039.dts b/kernel/arch/arm64/boot/dts/rockchip/rk3399-jw-d039.dtsindex 515334c127..5b592a852f 100755--- a/…

Unity Xcode方式接入sdk

入口 创建 GameAppController 类 继承 UnityAppController 并且在类的实现之前 需要 加 IMPL_APP_CONTROLLER_SUBCLASS(GameAppController)&#xff0c;表明这个是程序的入口。UnityAppController 实现了 UIApplicationDelegate。 可以简单看下 UIApplicationDelegate 的生命周…

[数据集][目标检测]智慧牧场猪只检测数据集VOC+YOLO格式16245张1类别

数据集格式&#xff1a;Pascal VOC格式YOLO格式(不包含分割路径的txt文件&#xff0c;仅仅包含jpg图片以及对应的VOC格式xml文件和yolo格式txt文件) 图片数量(jpg文件个数)&#xff1a;16245 标注数量(xml文件个数)&#xff1a;16245 标注数量(txt文件个数)&#xff1a;16245 标…

如何在算家云搭建ComfyUI(AI绘画)

一、ComfyUI简介 ComfyUI 是一个强大的、模块化的 Stable Diffusion 界面与后端项目。该用户界面将允许用户使用基于图形/节点/流程图的界面设计和执行高级稳定的扩散管道。该项目部分其它特点如下&#xff1a; 全面支持 SD1.x&#xff0c;SD2.x&#xff0c;SDXL&#xff0c;…

Java 入门指南:Java 并发编程 —— 并发容器 ArrayBlockingQueue

BlockingQueue BlockingQueue 是Java并发包&#xff08;java.util.concurrent&#xff09;中提供的一个阻塞队列接口&#xff0c;它继承自 Queue 接口。 BlockingQueue 中的元素采用 FIFO 的原则&#xff0c;支持多线程环境并发访问&#xff0c;提供了阻塞读取和写入的操作&a…

思维导图在线制作怎么制作?5个软件教你快速进行思维导图制作

思维导图在线制作怎么制作&#xff1f;5个软件教你快速进行思维导图制作 思维导图是一种用于组织信息、梳理思路和激发创意的可视化工具。在线制作思维导图可以帮助你随时随地进行创作和分享&#xff0c;以下是五款在线思维导图工具&#xff0c;可以帮助你快速进行思维导图的制…

props与defineProps

在 Vue3 中&#xff0c;script 脚本存在两种情况。一种是 setup 函数&#xff0c;一种是 <script setup>。而针对这两种不同情况&#xff0c;Vue 也存在 props 和 defineProps 两种接收父组件传递数据的形式。 首先&#xff0c;默认已掌握 Vue2 的父子组件 props 传参&a…

五轴数控走心机指的是哪五轴

五轴数控走心机&#xff0c;作为现代机械加工领域中的高精度设备&#xff0c;其核心在于其独特的五轴联动系统。这五个轴分别是X1轴、Y1轴、Z1轴、Z2轴和X2轴&#xff0c;它们各自承担着不同的运动和控制功能&#xff0c;共同实现了对工件的复杂加工。 X1轴&#xff1a;作为向下…

北芯生命持续亏损:产能利用率不理想仍扩产能,销售费用越来越高

《港湾商业观察》黄懿 6月29日&#xff0c;深圳北芯生命科技股份有限公司&#xff08;下称“北芯生命”&#xff09;提交首轮问询回复&#xff0c;更新2023年年报财务数据&#xff0c;保荐机构为中国国际金融股份有限公司。 据悉&#xff0c;北芯生命曾向港交所递交上市申请&…

[C++]AVL树插入和删除操作的实现

AVL树又称为高度平衡的二叉搜索树,是1962年由两位俄罗斯数学家G.M.Adel’son-Vel’skii和E.M.Landis提出的。ALV树提高了二叉搜索树树的搜索效率。为此,就必须每向二叉搜索树插人一个新结点时调整树的结构,使得二叉搜索树保持平衡,从而尽可能降低树的高度,减少树的平均搜索长度…

JS简介 JS特点

JS简介 Javascript是一种由Netscape(网景)的LiveScript发展而来的原型化继承的面向对象的动态类型的区分大小写的 客户端脚本语言 &#xff0c;主要目的是为了解决服务器端语言&#xff0c;遗留的速度问题&#xff0c;为客户提供更流畅的浏览效果。 JS特点 JS是一种运行于浏览器…

注册中心 Eureka Nacos

文章目录 目录 文章目录 1. 什么是注册中心? 2.常见的注册中心 3 . Eureka 4 . Nacos 5 . Nacos与Eureka的区别 总结 1. 什么是注册中心? 在最初的架构体系中, 集群的概念还不那么流行, 且机器数量也比较少, 此时直接使用DNSNginx就可以满足几乎所有服务的发现. 相…

ABAP正则表达式 特殊字符处理

REPLACE ALL OCCURRENCES OF REGEX [[:space:]] IN <fs_purhdinfo>-cell_value WITH ."可去掉空格或回车键 REPLACE ALL OCCURRENCES OF &#xff1a; IN <fs_purhdinfo>-cell_value WITH ."可去掉空格或回车键 REPLACE ALL OCCURRENCES OF R…

如何构建高效办公管理系统——Java SpringBoot实战教程,2025年最新设计理念

✍✍计算机编程指导师 ⭐⭐个人介绍&#xff1a;自己非常喜欢研究技术问题&#xff01;专业做Java、Python、微信小程序、安卓、大数据、爬虫、Golang、大屏等实战项目。 ⛽⛽实战项目&#xff1a;有源码或者技术上的问题欢迎在评论区一起讨论交流&#xff01; ⚡⚡ Java实战 |…