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

目录

管程

临界区

竞态条件

案例

通过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.hqwc.cn/news/206915.html

如若内容造成侵权/违法违规/事实不符,请联系编程知识网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

安卓隐私指示器学习笔记

最近了解到Google 在Android12上新增了权限指示器&#xff0c;可以在信号栏的右侧显示当前访问录音机和Camera的应用&#xff0c;点击后可以跳转到相应应用的权限界面&#xff0c;消费者可以控制权限的开启和关闭。国内手机厂商最近几年都在增加隐私看板供能&#xff0c;消费者…

【endnote】如何将参考文献放到想放的位置

1. 方式 直接将生成的文献全选拖到想放的位置 注意&#xff1a;不要使用ctrlx这种操作。 2.具体操作 2.1 新建测试文档 如下图&#xff1a; 2.2 引用两篇文献】 如下图&#xff1a; 2.3 测试 如下图&#xff0c;选中所有已经引用的文献。 拖拽到想要防止的位置。 新…

详解Python中哈希表的使用。站在开发者角度,与大家一起探究哈希的世界。

文章目录 1. 前言2. 哈希表2.1 哈希函数2.2 哈希算法2.3 常见哈希算法2.4 哈希冲突 3.总结关于Python技术储备一、Python所有方向的学习路线二、Python基础学习视频三、精品Python学习书籍四、Python工具包项目源码合集①Python工具包②Python实战案例③Python小游戏源码五、面…

ELF header

1. ELF header定义 ELF header的定义可以在 /usr/include/elf.h 中找到。Elf32_Ehdr是32位 ELF header的结构体。Elf64_Ehdr是64位ELF header的结构体。 所以&#xff0c;ELF header在ELF文件中的大小与位置是确定的&#xff0c;位置位于文件头部&#xff0c;大小则是Elf_Ehdr…

​极氪,中国传统汽车品牌电动化的样板间

这篇文章早就想写了&#xff0c;因为太忙的原因就一直跳票&#xff0c;正好最近两件事的出现&#xff0c;又触发了想写这篇文章的冲动。 两件事主要是&#xff1a; 一&#xff0c;10 月份各家陆续公布了单月销量以及累计销量&#xff1b; 二&#xff0c;极氪在北京正式发布了 …

jmeter中调用python代码

1、安装pyinstaller pip install -i https://pypi.tuna.tsinghua.edu.cn/simple pyinstaller 2、将py脚本打包 pyinstaller -F venv/get_image/OCR_jmeter_api.py 3、jmeter中添加OS Process Sampler并调用dist下的程序 4、执行jmeter

【高级网络程序设计】Week2-1 Sockets

一、The Basics 1. Sockets 定义An abstraction of a network interface应用 use the Socket API to create connections to remote computers send data(bytes) receive data(bytes) 2. Java network programming the java network libraryimport java.net.*;similar to…

网络安全之渗透测试入门准备

渗透测试入门所需知识 操作系统基础&#xff1a;Windows&#xff0c;Linux 网络基础&#xff1a;基础协议与简单原理 编程语言&#xff1a;PHP&#xff0c;python web安全基础 渗透测试入门 渗透测试学习&#xff1a; 1.工具环境准备&#xff1a;①VMware安装及使用&#xff1b…

OSG文字-HUD显示汉字示例(3)

显示文字是一种非常实用的技术&#xff0c;可以用来把一些重要的文字始终显示在屏幕上。HUD的全称是HeadsUpDisplay&#xff0c;即抬头显示&#xff0c;这种技术最早应用在军事战斗机上。 创建HUD显示的基本步骤如下: <1> 创建一个osg::Camera对象&#xff0c;设置视图、…

苍穹外卖项目笔记(4)——菜品管理

菜品管理 主要功能模块&#xff1a;新建菜品、修改菜品、启用禁用菜品、菜品的分页查询、删除菜品 代码&#xff1a;GitHub - Echo0701/take-out 1 公共字段自动填充 公共字段指的是业务表中有一些相同的字段&#xff0c;比如创建人、创建时间、修改人、修改时间等&#xff…

赞!优雅的Python多环境管理神器!易上手易操作!

前言 Python 的不同版本之间常常存在依赖关系和兼容性问题&#xff0c;为了方便开发人员在 不同项目中使用不同的版本 。 如果大家使用过Python版本管理工具&#xff0c;肯定大多数人使用的都是Anaconda&#xff0c;它是一个优秀的数据科学开发环境&#xff0c;本身也提供了丰…

有一台电脑一部手机就可以在网上赚钱,这些项目你也可以学会

很多人都希望能够在家中或者闲暇的时候&#xff0c;能够在网上赚钱&#xff0c;而网络给了我们这样的可能。只要有一台电脑和一部手机&#xff0c;你就可以开始你的赚钱之旅。这些项目并不难&#xff0c;只要你肯学&#xff0c;就一定能够成功。 1、美工设计 这个副业主要是推荐…