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

2024-09-05 00:28

本文主要是介绍Java并发线程 共享模型之管程 5,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

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;}@Overridepublic 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;}@Overridepublic 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;}@Overridepublic String toString() {return "筷子{" + name + '}';}
}

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

特性4. 公平锁

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

特性5 条件变量

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

这篇关于Java并发线程 共享模型之管程 5的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/1137462

相关文章

javax.net.ssl.SSLHandshakeException:异常原因及解决方案

《javax.net.ssl.SSLHandshakeException:异常原因及解决方案》javax.net.ssl.SSLHandshakeException是一个SSL握手异常,通常在建立SS... 目录报错原因在程序中绕过服务器的安全验证注意点最后多说一句报错原因一般出现这种问题是因为目标服务器

Java实现删除文件中的指定内容

《Java实现删除文件中的指定内容》在日常开发中,经常需要对文本文件进行批量处理,其中,删除文件中指定内容是最常见的需求之一,下面我们就来看看如何使用java实现删除文件中的指定内容吧... 目录1. 项目背景详细介绍2. 项目需求详细介绍2.1 功能需求2.2 非功能需求3. 相关技术详细介绍3.1 Ja

springboot项目中整合高德地图的实践

《springboot项目中整合高德地图的实践》:本文主要介绍springboot项目中整合高德地图的实践,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一:高德开放平台的使用二:创建数据库(我是用的是mysql)三:Springboot所需的依赖(根据你的需求再

spring中的ImportSelector接口示例详解

《spring中的ImportSelector接口示例详解》Spring的ImportSelector接口用于动态选择配置类,实现条件化和模块化配置,关键方法selectImports根据注解信息返回... 目录一、核心作用二、关键方法三、扩展功能四、使用示例五、工作原理六、应用场景七、自定义实现Impor

SpringBoot3应用中集成和使用Spring Retry的实践记录

《SpringBoot3应用中集成和使用SpringRetry的实践记录》SpringRetry为SpringBoot3提供重试机制,支持注解和编程式两种方式,可配置重试策略与监听器,适用于临时性故... 目录1. 简介2. 环境准备3. 使用方式3.1 注解方式 基础使用自定义重试策略失败恢复机制注意事项

SpringBoot整合Flowable实现工作流的详细流程

《SpringBoot整合Flowable实现工作流的详细流程》Flowable是一个使用Java编写的轻量级业务流程引擎,Flowable流程引擎可用于部署BPMN2.0流程定义,创建这些流程定义的... 目录1、流程引擎介绍2、创建项目3、画流程图4、开发接口4.1 Java 类梳理4.2 查看流程图4

一文详解如何在idea中快速搭建一个Spring Boot项目

《一文详解如何在idea中快速搭建一个SpringBoot项目》IntelliJIDEA作为Java开发者的‌首选IDE‌,深度集成SpringBoot支持,可一键生成项目骨架、智能配置依赖,这篇文... 目录前言1、创建项目名称2、勾选需要的依赖3、在setting中检查maven4、编写数据源5、开启热

Java对异常的认识与异常的处理小结

《Java对异常的认识与异常的处理小结》Java程序在运行时可能出现的错误或非正常情况称为异常,下面给大家介绍Java对异常的认识与异常的处理,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参... 目录一、认识异常与异常类型。二、异常的处理三、总结 一、认识异常与异常类型。(1)简单定义-什么是

SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志

《SpringBoot项目配置logback-spring.xml屏蔽特定路径的日志》在SpringBoot项目中,使用logback-spring.xml配置屏蔽特定路径的日志有两种常用方式,文中的... 目录方案一:基础配置(直接关闭目标路径日志)方案二:结合 Spring Profile 按环境屏蔽关

Java使用HttpClient实现图片下载与本地保存功能

《Java使用HttpClient实现图片下载与本地保存功能》在当今数字化时代,网络资源的获取与处理已成为软件开发中的常见需求,其中,图片作为网络上最常见的资源之一,其下载与保存功能在许多应用场景中都... 目录引言一、Apache HttpClient简介二、技术栈与环境准备三、实现图片下载与保存功能1.