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

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

相关文章

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

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

Golang的CSP模型简介(最新推荐)

《Golang的CSP模型简介(最新推荐)》Golang采用了CSP(CommunicatingSequentialProcesses,通信顺序进程)并发模型,通过goroutine和channe... 目录前言一、介绍1. 什么是 CSP 模型2. Goroutine3. Channel4. Channe

使用Nginx来共享文件的详细教程

《使用Nginx来共享文件的详细教程》有时我们想共享电脑上的某些文件,一个比较方便的做法是,开一个HTTP服务,指向文件所在的目录,这次我们用nginx来实现这个需求,本文将通过代码示例一步步教你使用... 在本教程中,我们将向您展示如何使用开源 Web 服务器 Nginx 设置文件共享服务器步骤 0 —

Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)

《Python基于火山引擎豆包大模型搭建QQ机器人详细教程(2024年最新)》:本文主要介绍Python基于火山引擎豆包大模型搭建QQ机器人详细的相关资料,包括开通模型、配置APIKEY鉴权和SD... 目录豆包大模型概述开通模型付费安装 SDK 环境配置 API KEY 鉴权Ark 模型接口Prompt

Python使用pysmb库访问Windows共享文件夹的详细教程

《Python使用pysmb库访问Windows共享文件夹的详细教程》本教程旨在帮助您使用pysmb库,通过SMB(ServerMessageBlock)协议,轻松连接到Windows共享文件夹,并列... 目录前置条件步骤一:导入必要的模块步骤二:配置连接参数步骤三:实例化SMB连接对象并尝试连接步骤四:

C#反射编程之GetConstructor()方法解读

《C#反射编程之GetConstructor()方法解读》C#中Type类的GetConstructor()方法用于获取指定类型的构造函数,该方法有多个重载版本,可以根据不同的参数获取不同特性的构造函... 目录C# GetConstructor()方法有4个重载以GetConstructor(Type[]

Linux使用粘滞位 (t-bit)共享文件的方法教程

《Linux使用粘滞位(t-bit)共享文件的方法教程》在Linux系统中,共享文件是日常管理和协作中的常见任务,而粘滞位(StickyBit或t-bit)是实现共享目录安全性的重要工具之一,本文将... 目录文件共享的常见场景基础概念linux 文件权限粘滞位 (Sticky Bit)设置共享目录并配置粘

大模型研发全揭秘:客服工单数据标注的完整攻略

在人工智能(AI)领域,数据标注是模型训练过程中至关重要的一步。无论你是新手还是有经验的从业者,掌握数据标注的技术细节和常见问题的解决方案都能为你的AI项目增添不少价值。在电信运营商的客服系统中,工单数据是客户问题和解决方案的重要记录。通过对这些工单数据进行有效标注,不仅能够帮助提升客服自动化系统的智能化水平,还能优化客户服务流程,提高客户满意度。本文将详细介绍如何在电信运营商客服工单的背景下进行

Andrej Karpathy最新采访:认知核心模型10亿参数就够了,AI会打破教育不公的僵局

夕小瑶科技说 原创  作者 | 海野 AI圈子的红人,AI大神Andrej Karpathy,曾是OpenAI联合创始人之一,特斯拉AI总监。上一次的动态是官宣创办一家名为 Eureka Labs 的人工智能+教育公司 ,宣布将长期致力于AI原生教育。 近日,Andrej Karpathy接受了No Priors(投资博客)的采访,与硅谷知名投资人 Sara Guo 和 Elad G

Linux 网络编程 --- 应用层

一、自定义协议和序列化反序列化 代码: 序列化反序列化实现网络版本计算器 二、HTTP协议 1、谈两个简单的预备知识 https://www.baidu.com/ --- 域名 --- 域名解析 --- IP地址 http的端口号为80端口,https的端口号为443 url为统一资源定位符。CSDNhttps://mp.csdn.net/mp_blog/creation/editor