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

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

相关文章

揭秘Python Socket网络编程的7种硬核用法

《揭秘PythonSocket网络编程的7种硬核用法》Socket不仅能做聊天室,还能干一大堆硬核操作,这篇文章就带大家看看Python网络编程的7种超实用玩法,感兴趣的小伙伴可以跟随小编一起... 目录1.端口扫描器:探测开放端口2.简易 HTTP 服务器:10 秒搭个网页3.局域网游戏:多人联机对战4.

Java并发编程必备之Synchronized关键字深入解析

《Java并发编程必备之Synchronized关键字深入解析》本文我们深入探索了Java中的Synchronized关键字,包括其互斥性和可重入性的特性,文章详细介绍了Synchronized的三种... 目录一、前言二、Synchronized关键字2.1 Synchronized的特性1. 互斥2.

Linux samba共享慢的原因及解决方案

《Linuxsamba共享慢的原因及解决方案》:本文主要介绍Linuxsamba共享慢的原因及解决方案,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux samba共享慢原因及解决问题表现原因解决办法总结Linandroidux samba共享慢原因及解决

Java的IO模型、Netty原理解析

《Java的IO模型、Netty原理解析》Java的I/O是以流的方式进行数据输入输出的,Java的类库涉及很多领域的IO内容:标准的输入输出,文件的操作、网络上的数据传输流、字符串流、对象流等,这篇... 目录1.什么是IO2.同步与异步、阻塞与非阻塞3.三种IO模型BIO(blocking I/O)NI

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

Python异步编程中asyncio.gather的并发控制详解

《Python异步编程中asyncio.gather的并发控制详解》在Python异步编程生态中,asyncio.gather是并发任务调度的核心工具,本文将通过实际场景和代码示例,展示如何结合信号量... 目录一、asyncio.gather的原始行为解析二、信号量控制法:给并发装上"节流阀"三、进阶控制

Redis中高并发读写性能的深度解析与优化

《Redis中高并发读写性能的深度解析与优化》Redis作为一款高性能的内存数据库,广泛应用于缓存、消息队列、实时统计等场景,本文将深入探讨Redis的读写并发能力,感兴趣的小伙伴可以了解下... 目录引言一、Redis 并发能力概述1.1 Redis 的读写性能1.2 影响 Redis 并发能力的因素二、

Nginx实现高并发的项目实践

《Nginx实现高并发的项目实践》本文主要介绍了Nginx实现高并发的项目实践,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧... 目录使用最新稳定版本的Nginx合理配置工作进程(workers)配置工作进程连接数(worker_co

C#集成DeepSeek模型实现AI私有化的流程步骤(本地部署与API调用教程)

《C#集成DeepSeek模型实现AI私有化的流程步骤(本地部署与API调用教程)》本文主要介绍了C#集成DeepSeek模型实现AI私有化的方法,包括搭建基础环境,如安装Ollama和下载DeepS... 目录前言搭建基础环境1、安装 Ollama2、下载 DeepSeek R1 模型客户端 ChatBo

SpringBoot快速接入OpenAI大模型的方法(JDK8)

《SpringBoot快速接入OpenAI大模型的方法(JDK8)》本文介绍了如何使用AI4J快速接入OpenAI大模型,并展示了如何实现流式与非流式的输出,以及对函数调用的使用,AI4J支持JDK8... 目录使用AI4J快速接入OpenAI大模型介绍AI4J-github快速使用创建SpringBoot