当前位置: 首页 > 编程日记 > 正文

java多线程三之线程协作与通信实例

多线程的难点主要就是多线程通信协作这一块了,前面笔记二中提到了常见的同步方法,这里主要是进行实例学习了,今天总结了一下3个实例:

1、银行存款与提款多线程实现,使用Lock锁和条件Condition。     附加 : 用监视器进行线程间通信

2、生产者消费者实现,使用LinkedList自写缓冲区。

3、多线程之阻塞队列学习,用阻塞队列快速实现生产者消费者模型。    附加:用布尔变量关闭线程

在三种线程同步方法中,我们这里的实例用Lock锁来实现变量同步,因为它比较灵活直观。

实现了变量的同步,我们还要让多个线程之间进行“通话”,就是一个线程完成了某个条件之后,告诉其他线程我完成了这个条件,你们可以行动了。下面就是java提供的条件接口Condition定义的同步方法:

image

很方便的是,java的Lock锁里面提供了newConditon()方法可以,该方法返回:一个绑定了lock锁的Condition实例,有点抽象,其实把它看作一个可以发信息的锁就可以了,看后面的代码,应该就能理解了。

1、银行存款与提款多线程实现。

我们模拟ATM机器存款与提款,创建一个账户类Account(),该类包含同步方法:

存款方法:deposit()

提款方法:withdraw()

以及一个普通的查询余额的方法getbalance().

我们创建两个任务线程,分别调用两个同步方法,进行模拟操作,看代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class ThreadCooperation {private static Account account = new Account();   public static void main(String[] args){//创建线程池ExecutorService executor = Executors.newFixedThreadPool(2);executor.execute(new DepositTask());executor.execute(new WithdrawTask());}//存钱public static class DepositTask implements Runnable{@Overridepublic void run() {try {while(true){account.deposit((int)(Math.random()*1000)+1);Thread.sleep(1000);}} catch (InterruptedException e) {e.printStackTrace();}}}public static class WithdrawTask implements Runnable{@Overridepublic void run() {try{while(true){account.withdraw((int)(Math.random()*1000)+1);Thread.sleep(500);}} catch (InterruptedException e) {e.printStackTrace();}}}public static class Account{//一个锁是一个Lock接口的实例   它定义了加锁和释放锁的方法     ReentrantLock是为创建相互排斥的锁的Lock的具体实现private static Lock lock = new ReentrantLock();//创建一个condition,具有发通知功能的锁,前提是要实现了lock接口private static Condition  newDeposit = lock.newCondition();private int balance = 0;public int getBalance(){return balance;}public void withdraw(int amount){lock.lock();try {while(balance < amount){System.out.println("\t\t钱不够,等待存钱");newDeposit.await();}balance -= amount;System.out.println("\t\t取出"+amount+"块钱\t剩余"+getBalance());} catch (InterruptedException e) {e.printStackTrace();}finally{lock.unlock();}}public void deposit(int amount){lock.lock();try{balance+=amount;System.out.println("存入"+amount+"块钱");newDeposit.signalAll();   //发信息唤醒所有的线程}finally{lock.unlock();}}}
}

运行截图C1]I1L)1D0(F)I4MCX3)4$I

分析:

1、程序中需要注意的:创建一个condition,具有发通知功能的锁,前提是要实现了lock接口。

2、while(balance < amount)不能改用if判断,用if会使得线程不安全,使用if会不会进行循环验证,而while会,我们经常看到while(true),但是不会经常看到if(true).

3、调用了await方法后,要记得使用signalAll()或者signal()将线程唤醒,否则线程永久等待。

最后再来分析一下这个类的结构,有3个类,两个静态任务类实现了Runnable接口,是线程类,而另外一个类则是普通的任务类,包含了线程类所用到的方法。我们的主类在main方法前面就实例化一个Account类,以供线程类调用该类里面的同步方法。

这种构造方式是多线程常用到的一种构造方式吧。不难发现后面要手写的生产者消费者模型也是这样子构造的。这相当于是一个多线程模板。也是我们学习这个例子最重要的收获吧。

用监视器进行线程之间的通信

还有一点,接口Lock与Condition都是在java5之后出现的,在这之前,线程通信是通过内置的监视器(monitor)实现的。

监视器是一个相互排斥且具有同步能力的对象,任意对象都有可能成为一个monitor。监视器是通过synchronized关键字来对自己加锁(加锁解锁是解决线程同步最基本的思想),使用wait()方法时线程暂停并 等待条件发生,发通知则是通过notify()和notifyAll()方法。大体的模板是这样子的:

image

不难看出await()、signal()、signally()是wait()、notify()、notifyAll()的进化形态,所以不建议使用监视器。

2、生产者消费者实现,使用LinkedList自写缓冲区

这个模型一直很经典,学操作系统的时候还学过,记得linux还用PV操作去实现它,不过这东西是跨学科的。

考虑缓存区buffer的使用者,生产者和消费者,他们都能识别缓冲区是否满的,且两种各只能发出一种信号:

生产者:它能发出notEmpty()信号,即缓冲区非空信号,当它看到缓冲区满的时候,它就调用await等待。

消费者:它能发出notFull()信号,即缓冲区未满的信号,当它看到缓冲区空的时候,它也调用await等待。

看代码:

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;//生产者消费者
public class ConsumerProducer {private static Buffer  buffer= new Buffer();public static void main(String[] args){ExecutorService executor = Executors.newFixedThreadPool(2);executor.execute(new ProducerTask());executor.execute(new ConsumerTask());executor.shutdown();}public static class ProducerTask implements Runnable{@Overridepublic void run() {int i=1;try {while(true){System.out.println("生产者写入数据"+i);	buffer.write(i++);Thread.sleep((int)(Math.random()*80));} }catch (InterruptedException e) {e.printStackTrace();}}}public static class ConsumerTask implements Runnable{public void run() {try {while(true){System.out.println("\t\t消费读出数据"+buffer.read());Thread.sleep((int)(Math.random()*100));}} catch (InterruptedException e) {e.printStackTrace();}}}public static class Buffer{private static final int CAPACTIY = 4;  //缓冲区容量private java.util.LinkedList<Integer> queue = new java.util.LinkedList<Integer>();private static Lock lock = new ReentrantLock();private static Condition  notEmpty = lock.newCondition();private static Condition notFull = lock.newCondition();public void write(int value){lock.lock();try{while(queue.size()==CAPACTIY){System.out.println("缓冲区爆满");notFull.await();}queue.offer(value);notEmpty.signalAll();  //通知所有的缓冲区未空的情况}catch(InterruptedException ex){ex.printStackTrace();}finally{lock.unlock();}}@SuppressWarnings("finally")public int read(){int value = 0;lock.lock();try{while(queue.isEmpty()){System.out.println("\t\t缓冲区是空的,等待缓冲区非空的情况");notEmpty.await();}value = queue.remove();notFull.signal();}catch(InterruptedException ex){ex.printStackTrace();}finally{lock.unlock();return value;}}}
}

运行截图image

程序运行正常,不过稍微延长一下读取时间,就会出现这样的情况image

程序里面设置的容量是4,可是这里却可以存入最多5个数据,而且更合理的情况应该是初始缓冲区是空的,后面找了下这个小bug,原来是调用offer()函数应该放在检测语句之前,如果希望一开始就调用ConsumerTask,在main方法里面调换两者的顺序即可。

3、用阻塞队列快速实现生产者消费者模型

java的强大之处是它有着丰富的类库,我们学习java在某种程度上就是学习这些类库。

阻塞队列是这样的一种队列:当试图向一个满队列里添加元素  或者 从空队列里删除元素时,队列会让线程自动阻塞,且当队列满时,队列会继续存储元素,供唤醒后的线程使用。这应该说是专门为消费者生产者模型而设计的一种队列吧,它实现了Queue接口,主要方法是put()和take()方法。

EU7H[HB1$E{SB3MZ)@DT89B

java支持三个具体的阻塞队列ArrayBlockingQueue、LinkedBlockingQueue、PriorityBlockingQueue。都在java.util.concurrent包中。

简单描述上面三个阻塞队列:

ArrayBlockingQueue: 该阻塞用数组实现,按照FIFO,即先进先出的原则对数据进行排序,和数组的使用有点相似,它事先需要指定一个容量,不过即便队列超出这个容量,也是不会报错滴。

LinkeddBlockingQueue:用链表实现,默认队列大小是Integer.MAX_VALUE,也是按照先进先出的方法对数据排序,性能可能比ArrayBlockingQueue,有待研究。

PriorityBlockingQueue:用优先队列实现的阻塞队列,会对元素按照大小进行排序,也可以创建不受限制的队列,put方法永不阻塞。

ok,看代码:

import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;public class ConsumerProducerUsingBlockQueue {private static ArrayBlockingQueue<Integer>  buffer = new ArrayBlockingQueue<Integer>(2);public static void main(String[] args){ExecutorService executor = Executors.newFixedThreadPool(2);executor.execute(new Consumer());executor.execute(new Producer());try {Thread.sleep(100);executor.shutdownNow();     //暴力关闭,会报错,不推荐} catch (InterruptedException e) {// TODO Auto-generated catch blocke.printStackTrace();}}public static class Consumer implements Runnable{@Overridepublic void run() {try{int i=1;while(true){System.out.println("生成者写入:"+i);buffer.put(i++);Thread.sleep((int)(Math.random())*1000);}}catch(InterruptedException ex){ex.printStackTrace();}}}public static class Producer implements Runnable{@Overridepublic void run() {try{while(true){System.out.println("\t\t消费者取出"+buffer.take());Thread.sleep((int)(Math.random())*10000);}}catch(InterruptedException ex){ex.printStackTrace();}}}
}

运行截图:1cd4147312c04552aded4cc98ba76a10

没啥大的问题,就是在关闭线程的时候太过暴力了,会报错,线程里面的每一个函数都似乎值得研究,之前想通过Interrupt暂停,不过失败了,就直接使用线程池执行器的shoutdownNow方法来的。后面自己又用了另外一种关闭线程的方法,见下面代码

使用LinkedBlockingQueue实现消费者生产者且使用布尔变量控制线程关闭

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
public class A_Control_stop {private static LinkedBlockingQueue<String>  buffer = new LinkedBlockingQueue<String>();public static void main(String[] args){ExecutorService executor = Executors.newFixedThreadPool(2);executor.execute(new Consumer());executor.execute(new Producer());executor.shutdown();while(!executor.isTerminated()){}System.out.println("所有的的线程都正常结束");}public static class Consumer implements Runnable{private volatile boolean exit = false;@Overridepublic void run() {try{int i=0;String[] str ={"as","d","sd","ew","sdfg","esfr"};while(!exit){System.out.println("生成者写入:"+str[i]);buffer.put(str[i++]);Thread.sleep((int)(Math.random())*10);if(5==i){exit=true;}}}catch(InterruptedException ex){ex.printStackTrace();}}}public static class Producer implements Runnable{private volatile boolean exit = false;@Overridepublic void run() {try{int i=0;while(!exit){System.out.println("\t\t消费者取出"+buffer.take());i++;Thread.sleep((int)(Math.random())*10);if(5==i){exit=true;}}}catch(InterruptedException ex){ex.printStackTrace();}}}
}

截图image

关于阻塞队列,觉得这篇文章讲的不错,推荐大家看看  聊聊并发----Java中的阻塞队列

用了几天,多线程算是学了点皮毛,附注一下:这几天文章主要是参考了《java程序语言设计进阶篇第8版》,老外写的书讲的真心不错,只不过现在java都已经更新到java8了。在其他一些网站上看到自己的文章,没有说明转载什么的,估计是直接“被采集”过去了。

本文出自于博客园兰幽,转载请说明出处。

转载于:https://www.cnblogs.com/LZYY/p/3961271.html

相关文章:

Java8中Lambda表达式的10个例子

Java8中Lambda表达式的10个例子 例1 用Lambda表达式实现Runnable接口 Java代码 //Before Java 8: new Thread(new Runnable() { Override public void run() { System.out.println("Before Java8, too much code for too little to do"); } }).start(); …

OC的对象的分类

OC的对象分类 一 oc的对象分类主要分为3种 1 instance 对象&#xff1a; 实例对象就是通过alloc 出来的对象&#xff0c;一个类每一次的alloc都会产生一个新的实例对象 StudentA *a [[StudentA alloc]init];StudentA *b [[StudentA alloc]init];// 打印结果如下 地址是明显…

如何在国内上medium_在Medium上写作的风格指南

如何在国内上mediumAfter spending more than 1,000 hours writing and editing stories for our Medium publication, I’ve decided to create this living style guide for contributors. Feel free to use it for your publication as well.在花了1000多个小时为我们的《中…

C# webform上传图片并生成缩略图

其实里面写的很乱&#xff0c;包括修改文件名什么的都没有仔细去写&#xff0c;主要是想记录下缩略图生成的几种方式 &#xff0c;大家明白就好! 1 void UpImgs()2 {3 if (FileUpload1.HasFile)4 {5 string fileContentType FileUpload1.Pos…

ios中的自动释放池

自动释放池中是否有虑重功能 1 autoreleasepool { 2 UIView *view [UIView alloc] init] autorelease]; 3 [view autorelease]; 4 } 这样写在自动释放池的队列中是两个对象还是一个对象&#xff0c;就是说把view加到自动释放池的队列时&#xff0c;队列本身是…

arch linux安装_如何从头开始安装Arch Linux

arch linux安装by Andrea Giammarchi由Andrea Giammarchi In this article, youll learn how to install Arch Linux from scratch… and in about 5 minutes. So lets get to it.在本文中&#xff0c;您将学习如何从头开始安装Arch Linux&#xff0c;大约需要5分钟。 因此&am…

CoreCRM 开发实录 —— Profile

再简单的功能&#xff0c;也需要一坨代码的支持。Profile 的编辑功能主要就是修改个人的信息。比如用户名、头像、性别、电话……虽然只是一个编辑界面&#xff0c;但添加下来&#xff0c;涉及了6个文件的修改和7个新创建的文件。各种生成的和手写的代码&#xff0c;共有934行之…

iOS KVO 的实现原理

KVO 的实现原理 一 原理 1.KVO是基于runtime机制实现的 2.当某个类的属性对象第一次被观察时&#xff0c;系统就会在运行期动态地创建该类的一个派生类&#xff0c;在这个派生类中重写基类中任何被观察属性的setter 方法。派生类在被重写的setter方法内实现真正的通知机制 …

利用UltimateAndroid框架进行快速开发

UltimateAndroid是一套集成了许多现有优秀的Android开源类库并将之组合成一个整体的Android快速开发框架。框架目前主要包含的功能有View Injection,ORM,异步网络请求和图片加载&#xff0c;自动化脚本测试,磁盘LRU等功能.同时提供了类似于TripleDes、Webview快速设置、Md5处理…

溢出内容菜单_停止过度使用溢出菜单

溢出内容菜单by Daniel Burka丹尼尔伯卡(Daniel Burka) 停止过度使用溢出菜单 (Stop the overuse of overflow menus) You know those obscure menu buttons on apps and websites that reveal even more menu options? They usually have an ellipsis “…” or an arrow ▼…

KVC 和 KVO

KVC 键值编码 全称是Key-value coding&#xff0c;翻译成键值编码。它提供了一种使用字符串而不是访问器方法去访问一个对象实例变量的机制。 1.通过key&#xff08;成员变量的名称&#xff09;设置value&#xff08;成员变量的值&#xff09; - (void)setValue:(…

datasnap的客户端回传机制

最近&#xff0c;学习XE6下的DataSnap回叫技术编译代码&#xff0c;体会如下&#xff1a;第一篇 服务器与客户端回叫 从Delphi2010开始&#xff0c;DataSnap支持回叫&#xff08;Call Back&#xff09;机制。这样&#xff0c;在调用耗时较长的方法时&#xff0c;通过回叫机制…

Block 底层1

Block 本质来讲是OC 对象&#xff0c;其内部有一个isa指针。 1 Block 的声明 一言以蔽之&#xff1a; returnType &#xff08; ^blockName&#xff09;(parameterTypes) ^returnType (parameters) {}; returnType 返回的类型 可以为Void,为Void的时候可以省略^blockName …

从零学web前端_从零到前端英雄(第2部分)

从零学web前端This article is part two of the “From Zero to Front-end Hero” series. In part one, you learned how to create layouts with HTML and CSS as well as some best practices. In part two, we will focus on learning JavaScript as a standalone language…

jdk8飞行记录器配置

jdk8提供了jmc工具,应该比visualvm厉害吧 下面贴一份tomcat的配置,自己留个备份,把下面的内容粘贴到tomcat setenv.sh就可以了 nowdaydate %Y%m%d_%H%M%S test -d ../gclogs || mkdir ../gclogsif [ -r "$CATALINA_BASE/bin/setenv_custom.sh" ]; then. "$CATAL…

petaPar培训文档

自己写的实验室程序文档&#xff0c;方便后来者。 转载于:https://www.cnblogs.com/daijkstra/p/3972167.html

Block 底层值__Block修饰符

__Block 修饰符 Block 想要改变外部的变量&#xff0c;必须要用__Block 来修饰自动变量。 根据内存地址可以看出来&#xff0c;__block 所修饰的变量&#xff0c;将外部的变量在栈中的内存地址放到了堆中。 // auto 自动变量的内存分配在栈区域 stack__block int meters 100…

体检系统前端源码_给您的前端进行健康检查

体检系统前端源码by Ewa Mitulska-Wjcik伊娃米图尔斯卡(EwaMitulska-Wjcik) 给您的前端进行健康检查 (Give your Front End a Health Check) You’ve built out all your user stories and your app is working. Now’s it’s ready to submit as done, so you can move on wi…

1-runtime的Method,IMP,Property,ivar

基础定义 objc-750 的tar包 objc-private.h 定义 typedef struct objc_class *Class; typedef struct objc_object *id;#if __OBJC2__ typedef struct method_t *Method; typedef struct ivar_t *Ivar; typedef struct category_t *Category; typedef struct property_t *ob…

【编程题目】左旋转字符串 ☆

26.左旋转字符串&#xff08;字符串&#xff09;题目&#xff1a;定义字符串的左旋转操作&#xff1a;把字符串前面的若干个字符移动到字符串的尾部。如把字符串 abcdef 左旋转 2 位得到字符串 cdefab。请实现字符串左旋转的函数。要求时间对长度为 n 的字符串操作的复杂度为 O…

10、同步机制遵循的原则_我要遵循的10条原则

10、同步机制遵循的原则by Haseeb Qureshi由Haseeb Qureshi 我要遵循的10条原则 (10 Principles I Want to Live By) I just came home from a vow of silence at a meditation center in northern California. It’s a strange feeling coming back to city life after five …

2-Runtime objc_object objc_class

一 NSObject NSObject是OC 中的基类&#xff0c;除了NSProxy其他都继承自NSObject interface NSObject <NSObject> { #pragma clang diagnostic push #pragma clang diagnostic ignored "-Wobjc-interface-ivars"Class isa OBJC_ISA_AVAILABILITY; #pragma …

Spring3.0 AOP 具体解释

一、什么是 AOP。 AOP&#xff08;Aspect Orient Programming&#xff09;&#xff0c;也就是面向切面编程。能够这样理解&#xff0c;面向对象编程&#xff08;OOP&#xff09;是从静态角度考虑程序结构&#xff0c;面向切面编程&#xff08;AOP&#xff09;是从动态角度考虑程…

通过Appium获取Android app中webview

因为要测试Android app中嵌入的web页面&#xff0c;所以需要从native切换到webview。网上查了好多帖子&#xff0c;都用到类似下面代码&#xff1a; //判断是否有 WEBVIEWSet<String> contextNames driver.getContextHandles();for (String contextName : contextNames)…

javascript原理_JavaScript程序包管理器工作原理简介

javascript原理by Shubheksha通过Shubheksha JavaScript程序包管理器工作原理简介 (An introduction to how JavaScript package managers work) A few days ago, ashley williams, one of the leaders of the Node.js community, tweeted this:几天前&#xff0c;Node.js社区…

iOS base64 MD5

网络APP 只要涉及用户隐私的数据&#xff0c;均不能以明文传输。 一 base64 编码 将任意的二进制数据转为编码为 65个字符的组成。 0-9 a-z A-Z / 一共 65 个 字符 例如&#xff1a; 1 mac 自带 base64命令 可以将base64 编码的文件可以转换 –》将桌面上1.png 图片经过…

【面试虐菜】—— Oracle知识整理《收获,不止Oracle》

普通堆表不足之处&#xff1a; 表更新有日志开销表删除有瑕疵表记录太大检索较慢索引回表读开销很大有序插入难有序读出DELETE产生的undo最多&#xff0c;redo也最多&#xff0c;因为undo也需要redo保护全局临时表&#xff1a;1 高效删除记录基于事务的全局临时表commit或者ses…

每日成长17年1月

2017年1月 1月9号 一、学习了ice ice是一个跨平台调用程序&#xff0c;与语言无关的一个中间件&#xff0c;比如&#xff0c;可以通过java的代码调用 c应用程序的接口。 1月11号 一.学习了 struts2 spring mybatis 的配置。 1.首先是web.xml的配置&#xff0c;主要配置两…

网络安全从事工作分类_那么,您想从事安全工作吗?

网络安全从事工作分类by Parisa Tabriz由Parisa Tabriz 那么&#xff0c;您想从事安全工作吗&#xff1f; (So, you want to work in security?) Every once in a while, I’ll get an email from an eager stranger asking for advice on how to have a career in security …

iOS 使用钥匙串将用户密码存入本地

在 iOS 开发中&#xff0c;用户一般注册时候&#xff0c;APP会将用户的用户名和密码直接保存到本地&#xff0c;便于用户下次直接进行登录。 这样就会牵扯到一个问题&#xff0c;用户的密码不能以明文的形式存储在本地&#xff0c;使用钥匙串进行保存用户的密码较为安全。 钥…