并发编程:共享模型之管程

2023-11-23 08:44

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

目录

管程

临界区

竞态条件

案例

通过synchronized阻塞解决

synchronized添加位置

设计模式之保护性暂停

Join原理

修改线程状态的几种方法

单向改变不可返回的状态

双向可改变的状态

多把锁

线程活跃性

死锁

定位死锁

活锁

饥饿

ReentrantLock

可重入

可打断

锁超时

固定顺序运行线程


管程

所谓管程:指的是管理共享变量以及对共享变量的操作过程,让它们支持并发。翻译为 Java 就是管理类的成员变量和成员方法,让这个类是线程安全的。

临界区

一段代码块中如果存在对共享资源的多线程读写操作,那么称这段代码块为临界区。

竞态条件

多个线程在临界区执行,由于代码的执行顺序不同导致结果无法预测。称之为发生了竞态条件。

案例

在操作系统中CPU使用的是分片来决定线程的执行,这样会进行线程的上下文切换。

以一个自增的例子来讲,它并不是一个原子操作,在字节码文件中分为了4步。首先是读取静态变量的值i,将i存进本地变量表,对i进行自增操作,将i自增结果返回静态变量。

那么就存在一个问题,当线程执行完自增后,还未将结果返回,但是时间片结束后静态变量值并没有发生改变,此时去执行其他线程,那么其他线程对i值又进行一次自增操作,并将结果返回后,再次回到未执行完的线程,此时执行了两次自增,但是得到的结果为自增一次的结果。其运行视图参考下图。

以上问题的发生是因为线程对共享资源的读写时指令操作交错导致的。

通过synchronized阻塞解决

通过互斥,在同一时刻中只有一个线程获取一把锁,其他线程获取时,会进入阻塞状态从而保证持有锁的线程安全执行临界区的代码,不用担心线程上下文的问题。

synchronized添加位置

添加在代码中,根据对谁添加锁取决于传入的参数

synchronized(){
}

添加在方法上,锁住的是this

public void synchronized a(){
}等价于
synchronized(this){
}

如果是静态方法,锁住的是类对象,与非静态方法锁住的不是同一个对象

设计模式之保护性暂停

线程与线程之间的通信,可以通过第三方类来实现,两个线程监听同一个类中的response属性,一个线程设值一个线程取值,类似于消息队列。

public class demo3 {public static void main(String[] args) {Result result = new Result();new Thread(()->{System.out.println(LocalDateTime.now()+"获取消息" );System.out.println(result.getResponse());System.out.println(LocalDateTime.now());},"t1").start();new Thread(()->{try {Thread.sleep(3000);} catch (InterruptedException e) {e.printStackTrace();}result.setResponse("发送消息");},"t2").start();}
}class Result{Object response;public Object getResponse() {synchronized (this){while(response==null){try {this.wait();} catch (InterruptedException e) {e.printStackTrace();}}}return response;}public void setResponse(Object response) {synchronized (this){this.response = response;this.notifyAll();}}
}

Join原理

默认传参为0,即永久等待,如果设置了最大等待时间,那么截取开始时间,减去结束时间,与参数相比较,wait()方法内时间应设置为当前剩余时间。

join与保护性暂停区别在于,join是等待线程结束,保护性暂停是等待结果返回。

修改线程状态的几种方法

单向改变不可返回的状态

创建出线程调用start方法,从new状态转化为runnable状态。

线程运行结束,从runnable转化为terminated状态。

双向可改变的状态

wait():将runnable状态转化为waiting状态。通过notify()方法将其转化为runnable(如果争抢到锁的话)或blocked状态(被唤醒但是没有争抢到锁)。

join():需要等待某个线程运行结束后才会接着执行,需要等待的线程将会进入waiting状态。

pack():将runnable状态转化为waiting状态。通过interrupt()转化为runnable。

synchronized():如果竞争锁失败,将runnable转化为blocked状态。

多把锁

当只有一把锁时,并发度低,等于串行。为了提高并发度,可以将锁进行粒度细分。比如,一个房子,可以用来睡觉和学习,如果把房子作为锁对象,那么有人睡觉的时候就不能有人学习。将房子细分为卧室与书房,作为两个锁对象。睡觉的人与学习的人各持一把锁,提高并发度。

优点:增强并发度

缺点:当存在多把锁时,可能会存在死锁的情况。

线程活跃性

死锁

t1线程需要获取锁A,接下来要获取锁B。但是此时线程t2正在持有锁B,那么t1获取锁B失败,进入阻塞队列等待锁B的释放,t2此时需要获取锁A,但是锁A由t1持有,t2获取不到,因此也进入阻塞队列,导致死锁的情况发生

定位死锁

使用jconsole工具或jps锁定id再用jstack定位。

活锁

不断修改对方的结束条件导致无法结束线程。比如说线程t1执行count--。t2线程执行count++。t1的结束条件是while(count<0)。t2的结束条件是while(count>20)。导致t1与t2都无法结束线程。

解决方案:对两个线程设置随机的睡眠时间,使其变成两个交错运行的线程。

饥饿

由于线程优先级别太低而得不到CPU执行调度导致无法结束线程。

ReentrantLock

可重入锁。相对于synchronized作比较ReentrantLock具有以下特点

  • 可中断
  • 可设置超时时间
  • 可设置为公平锁
  • 支持多个条件变量

但是这两种都支持可重入

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

可重入

如果某线程已经获取到A对象的锁,在后续代码中,不释放A锁的情况下再次获取A锁,那么是可以的。

如果是不可重入锁即使已经拿到了A对象的锁也无法接着获取A锁。

可打断

此时不可以使用reentrant.lock()方法,而是使用reentrant.lockInterruputibly()方法来进行加锁,如果没有竞争,那么可以直接获取到锁。如果存在竞争,则会进入阻塞队列,在阻塞过程中,如果被interruput()打断,则会抛出异常。

public static void main(String[] args){ReentrantLock lock = new ReentrantLock();Thread t1 = new Thread(()->{try{//尝试去获取锁System.out.println("尝试获取锁");lock.lockInteruputibly();}catch(Exception e){System.out.println("获取锁失败");return;}try{System.out.println("获取锁成功");}finally{lock.unlock();}},"t1");//主线程加锁.抢占锁,导致t1抢锁失败。lock.lock();t1.start();sleep(1);System.out.println("打断t1");t1.interuput();
}

运行结果如下: 

尝试获取锁

打断t1

获取锁失败

锁超时

使用tryLock()。如果规定时间没有获取到锁,可以选择主动结束来避免死锁

public static void main(){ReentrantLock lock = new ReentrantLock();Thread t1 = new Thread(()->{if(!lock.tryLock()){System.out.println("获取锁失败,返回");return;}try{System.out.println("获取锁成功");}finally{lock.unLock();}},"t1");lock.lock();System.out.println("主线程加锁");t1.start();
}

运行结果如下: 

主线程加锁

获取锁失败,返回

tryLock()方法也是可以被打断后抛出异常。

固定顺序运行线程

对于多个线程情况下想要控制线程的运行先后顺序。

可以通过一个静态布尔类型的变量runned来控制默认值为false,然后第一个要优先运行的线程需要将其设置为true表示已经运行过了,如果其他线程要优于第一个线程启动的话,要对runned值进行判断,如果为false则进入wait队列。等待第一个要启动的线程执行结束后唤醒其他线程。

另一种方式是通过pack与unpack方法,第一个要启动的线程进行unpack第二个线程则是在执行前进行pack操作。对于多线程情况下,将所有线程进行pack后,主线程主动将第一个线程unpark后,将后续的线程对象进行unpark(Thread)。

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



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

相关文章

0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型的操作流程

《0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeekR1模型的操作流程》DeepSeekR1模型凭借其强大的自然语言处理能力,在未来具有广阔的应用前景,有望在多个领域发... 目录0基础租个硬件玩deepseek,蓝耘元生代智算云|本地部署DeepSeek R1模型,3步搞定一个应

Deepseek R1模型本地化部署+API接口调用详细教程(释放AI生产力)

《DeepseekR1模型本地化部署+API接口调用详细教程(释放AI生产力)》本文介绍了本地部署DeepSeekR1模型和通过API调用将其集成到VSCode中的过程,作者详细步骤展示了如何下载和... 目录前言一、deepseek R1模型与chatGPT o1系列模型对比二、本地部署步骤1.安装oll

java父子线程之间实现共享传递数据

《java父子线程之间实现共享传递数据》本文介绍了Java中父子线程间共享传递数据的几种方法,包括ThreadLocal变量、并发集合和内存队列或消息队列,并提醒注意并发安全问题... 目录通过 ThreadLocal 变量共享数据通过并发集合共享数据通过内存队列或消息队列共享数据注意并发安全问题总结在 J

Spring AI Alibaba接入大模型时的依赖问题小结

《SpringAIAlibaba接入大模型时的依赖问题小结》文章介绍了如何在pom.xml文件中配置SpringAIAlibaba依赖,并提供了一个示例pom.xml文件,同时,建议将Maven仓... 目录(一)pom.XML文件:(二)application.yml配置文件(一)pom.xml文件:首

如何在本地部署 DeepSeek Janus Pro 文生图大模型

《如何在本地部署DeepSeekJanusPro文生图大模型》DeepSeekJanusPro模型在本地成功部署,支持图片理解和文生图功能,通过Gradio界面进行交互,展示了其强大的多模态处... 目录什么是 Janus Pro1. 安装 conda2. 创建 python 虚拟环境3. 克隆 janus

本地私有化部署DeepSeek模型的详细教程

《本地私有化部署DeepSeek模型的详细教程》DeepSeek模型是一种强大的语言模型,本地私有化部署可以让用户在自己的环境中安全、高效地使用该模型,避免数据传输到外部带来的安全风险,同时也能根据自... 目录一、引言二、环境准备(一)硬件要求(二)软件要求(三)创建虚拟环境三、安装依赖库四、获取 Dee

C#多线程编程中导致死锁的常见陷阱和避免方法

《C#多线程编程中导致死锁的常见陷阱和避免方法》在C#多线程编程中,死锁(Deadlock)是一种常见的、令人头疼的错误,死锁通常发生在多个线程试图获取多个资源的锁时,导致相互等待对方释放资源,最终形... 目录引言1. 什么是死锁?死锁的典型条件:2. 导致死锁的常见原因2.1 锁的顺序问题错误示例:不同

DeepSeek模型本地部署的详细教程

《DeepSeek模型本地部署的详细教程》DeepSeek作为一款开源且性能强大的大语言模型,提供了灵活的本地部署方案,让用户能够在本地环境中高效运行模型,同时保护数据隐私,在本地成功部署DeepSe... 目录一、环境准备(一)硬件需求(二)软件依赖二、安装Ollama三、下载并部署DeepSeek模型选

PyCharm接入DeepSeek实现AI编程的操作流程

《PyCharm接入DeepSeek实现AI编程的操作流程》DeepSeek是一家专注于人工智能技术研发的公司,致力于开发高性能、低成本的AI模型,接下来,我们把DeepSeek接入到PyCharm中... 目录引言效果演示创建API key在PyCharm中下载Continue插件配置Continue引言

NFS实现多服务器文件的共享的方法步骤

《NFS实现多服务器文件的共享的方法步骤》NFS允许网络中的计算机之间共享资源,客户端可以透明地读写远端NFS服务器上的文件,本文就来介绍一下NFS实现多服务器文件的共享的方法步骤,感兴趣的可以了解一... 目录一、简介二、部署1、准备1、服务端和客户端:安装nfs-utils2、服务端:创建共享目录3、服