线程与线程间通信

一、基本概念以及线程与进程之间的区别联系:
关于进程和线程,首先从定义上理解就有所不同
1、进程是什么?
是具有一定独立功能的程序、它是系统进行资源分配和调度的一个独立单位,重点在系统调度和单独的单位,也就是说进程是可以独 立运行的一段程序。
2、线程又是什么?
线程进程的一个实体,是CPU调度和分派的基本单位,他是比进程更小的能独立运行的基本单位,线程自己基本上不拥有系统资源。
在运行时,只是暂用一些计数器、寄存器和栈 。
他们之间的关系
1、一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程(通常说的主线程)。
2、资源分配给进程,同一进程的所有线程共享该进程的所有资源。
3、线程在执行过程中,需要协作同步。不同进程的线程间要利用消息通信的办法实现同步。
4、处理机分给线程,即真正在处理机上运行的是线程。
5、线程是指进程内的一个执行单元,也是进程内的可调度实体。
从三个角度来剖析二者之间的区别
1、调度:线程作为调度和分配的基本单位,进程作为拥有资源的基本单位。
2、并发性:不仅进程之间可以并发执行,同一个进程的多个线程之间也可以并发执行。
3、拥有资源:进程是拥有资源的一个独立单位,线程不拥有系统资源,但可以访问隶属于进程的资源。.
二、多线程间通信方式:
1、共享变量
2、wait/notify机制
3、Lock/Condition机制
4、管道
三、共享变量
线程间发送信号的一个简单方式是在共享对象的变量里设置信号值。线程A在一个同步块里设置boolean型成员变量hasDataToProcess为true,线程B也在同步块里读取hasDataToProcess这个成员变量。这个简单的例子使用了一个持有信号的对象,并提供了set和check方法:
public class MySignal{
protected boolean hasDataToProcess = false;
public synchronized boolean hasDataToProcess(){
return this.hasDataToProcess;
}
public synchronized void setHasDataToProcess(boolean hasData){
this.hasDataToProcess = hasData;
}
}
线程A和B必须获得指向一个MySignal共享实例的引用,以便进行通信。如果它们持有的引用指向不同的MySingal实例,那么彼此将不能检测到对方的信号。需要处理的数据可以存放在一个共享缓存区里,它和MySignal实例是分开存放的。
四、wait()/notify机制
为了实现线程通信,我们可以使用Object类提供的wait()、notify()、notifyAll()三个方法。调用wait()方法会释放对该同步监视器的锁定。这三个方法必须由同步监视器对象来调用,这可分成两种情况:
•对于使用synchronized修饰的同步方法,因为该类的默认实例是(this)就是同步监视器,所以可以直接调用这三使用个方法。
•对于synchronized修饰的同步代码块,同步监视器是synchronized括号里的对象,所以必须使用该对象调用这三个方法。
假设系统中有两条线程,这两条线程分别代表取钱者和存钱者。现在系统有一种特殊的要求,系统要求存款者和取钱者不断的实现存款和取钱动作,而且要求每当存款者将钱存入指定账户后,取钱者立即将钱取走.不允许存款者两次存钱,也不允许取钱者两次取钱。
我们通过设置一个旗标来标识账户中是否已有存款,有就为true,没有就标为false。具体代码如下:
首先我们定义一个Account类,这个类中有取钱和存钱的两个方法,由于这两个方法可能需要并发的执行取钱、存钱操作,所有将这两个方法都修改为同步方法.(使用synchronized关键字)。
public class Account {
private String accountNo;
private double balance;
//标识账户中是否有存款的旗标
private boolean flag=false;
public Account() {
super();
}
public Account(String accountNo, double balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
}
public synchronized void draw (double drawAmount){
try {
if(!flag){
this.wait();
}else {
//取钱
System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAmount);
balance=balance-drawAmount;
System.out.println("余额 : "+balance);
//将标识账户是否已有存款的标志设为false
flag=false;
//唤醒其它线程
this.notifyAll();
}
} catch (Exception e) {
e.printStackTrace();
}
}
public synchronized void deposit(double depositAmount){
try {
if(flag){
this.wait();
}
else{
System.out.println(Thread.currentThread().getName()+"存钱"+depositAmount);
balance=balance+depositAmount;
System.out.println("账户余额为:"+balance);
flag=true;
//唤醒其它线程
this.notifyAll();
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}
}
}
接下来创建两个线程类,分别为取钱和存钱线程!
取钱线程类:
public class DrawThread implements Runnable {
private Account account;
private double drawAmount;
public DrawThread(Account account, double drawAmount) {
super();
this.account = account;
this.drawAmount = drawAmount;
}
public void run() {
for(int i=0;i<100;i++){
account.draw(drawAmount);
}
}
}
存钱线程类:
public class depositThread implements Runnable{
private Account account;
private double depositAmount;
public depositThread(Account account, double depositAmount) {
super();
this.account = account;
this.depositAmount = depositAmount;
}
public void run() {
for(int i=0;i<100;i++){
account.deposit(depositAmount);
}
}
}
最后我们测试一下这个取钱和存钱的操作
public class TestDraw {
public static void main(String[] args) {
//创建一个账户
Account account=new Account();
new Thread(new DrawThread(account, 800),"取钱者").start();
new Thread(new depositThread(account, 800),"存款者甲").start();
new Thread(new depositThread(account, 800),"存款者乙").start();
new Thread(new depositThread(account, 800),"存款者丙").start();
}
}
大致的输出结果:
存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者丙存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者丙存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者丙存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者丙存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0 存款者甲存钱800.0 账户余额为:800.0 取钱者 取钱:800.0 余额 : 0.0
五、Lock/Condition机制
如何程序不使用synchronized关键字来保持同步,而是直接适用Lock对像来保持同步,则系统中不存在隐式的同步监视器对象,也就不能使用wait()、notify()、notifyAll()来协调线程的运行.
当使用LOCK对象保持同步时,JAVA为我们提供了Condition类来协调线程的运行。关于Condition类,JDK文档里进行了详细的解释.,再次就不啰嗦了。
我们就拿Account类进行稍微的修改 一下吧!
import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
public class Account {
//显示定义Lock对象
private final Lock lock=new ReentrantLock();
//获得指定Lock对象对应的条件变量
private final Condition con=lock.newCondition();
private String accountNo;
private double balance;
//标识账户中是否有存款的旗标
private boolean flag=false;
public Account() {
super();
}
public Account(String accountNo, double balance) {
super();
this.accountNo = accountNo;
this.balance = balance;
}
public void draw (double drawAmount){
//加锁
lock.lock();
try {
if(!flag){
// this.wait();
con.await();
}else {
//取钱
System.out.println(Thread.currentThread().getName()+" 取钱:"+drawAmount);
balance=balance-drawAmount;
System.out.println("余额 : "+balance);
//将标识账户是否已有存款的标志设为false
flag=false;
//唤醒其它线程
// this.notifyAll();
con.signalAll();
}
} catch (Exception e) {
e.printStackTrace();
}
finally{
lock.unlock();
}
}
public void deposit(double depositAmount){
//加锁
lock.lock();
try {
if(flag){
// this.wait();
con.await();
}
else{
System.out.println(Thread.currentThread().getName()+"存钱"+depositAmount);
balance=balance+depositAmount;
System.out.println("账户余额为:"+balance);
flag=true;
//唤醒其它线程
// this.notifyAll();
con.signalAll();
}
} catch (Exception e) {
// TODO: handle exception
e.printStackTrace();
}finally{
lock.unlock();
}
}
}
输出结果和上面是一样的! 只不过这里 显示的使用Lock对像来充当同步监视器,使用Condition对象来暂停指定线程,唤醒指定线程!
六、管道
管道流是JAVA中线程通讯的常用方式之一,基本流程如下:
1)创建管道输出流PipedOutputStream pos和管道输入流PipedInputStream pis
2)将pos和pis匹配,pos.connect(pis);
3)将pos赋给信息输入线程,pis赋给信息获取线程,就可以实现线程间的通讯了
import java.io.IOException;
import java.io.PipedInputStream;
import java.io.PipedOutputStream;
public class testPipeConnection {
public static void main(String[] args) {
/**
* 创建管道输出流
*/
PipedOutputStream pos = new PipedOutputStream();
/**
* 创建管道输入流
*/
PipedInputStream pis = new PipedInputStream();
try {
/**
* 将管道输入流与输出流连接 此过程也可通过重载的构造函数来实现
*/
pos.connect(pis);
} catch (IOException e) {
e.printStackTrace();
}
/**
* 创建生产者线程
*/
Producer p = new Producer(pos);
/**
* 创建消费者线程
*/
Consumer1 c1 = new Consumer1(pis);
/**
* 启动线程
*/
p.start();
c1.start();
}
}
/**
* 生产者线程(与一个管道输入流相关联)
*
*/
class Producer extends Thread {
private PipedOutputStream pos;
public Producer(PipedOutputStream pos) {
this.pos = pos;
}
public void run() {
int i = 0;
try {
while(true)
{
this.sleep(3000);
pos.write(i);
i++;
}
} catch (Exception e) {
e.printStackTrace();
}
}
}
/**
* 消费者线程(与一个管道输入流相关联)
*
*/
class Consumer1 extends Thread {
private PipedInputStream pis;
public Consumer1(PipedInputStream pis) {
this.pis = pis;
}
public void run() {
try {
while(true)
{
System.out.println("consumer1:"+pis.read());
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
程序启动后,就可以看到producer线程往consumer1线程发送数据
consumer1:0 consumer1:1 consumer1:2 consumer1:3 ......
管道流虽然使用起来方便,但是也有一些缺点
1)管道流只能在两个线程之间传递数据
线程consumer1和consumer2同时从pis中read数据,当线程producer往管道流中写入一段数据后,每一个时刻只有一个线程能获取到数据,并不是两个线程都能获取到producer发送来的数据,因此一个管道流只能用于两个线程间的通讯。不仅仅是管道流,其他IO方式都是一对一传输。
2)管道流只能实现单向发送,如果要两个线程之间互通讯,则需要两个管道流
可以看到上面的例子中,线程producer通过管道流向线程consumer发送数据,如果线程consumer想给线程producer发送数据,则需要新建另一个管道流pos1和pis1,将pos1赋给consumer1,将pis1赋给producer,具体例子本文不再多说。
II.进程与进程间通信
一、进程间通信方式
(1)管道(Pipe):管道可用于具有亲缘关系进程间的通信,允许一个进程和另一个与它有共同祖先的进程之间进行通信。
(2)命名管道(named pipe):命名管道克服了管道没有名字的限制,因此,除具有管道所具有的功能外,它还允许无亲缘关 系 进程间的通信。命名管道在文件系统中有对应的文件名。命名管道通过命令mkfifo或系统调用mkfifo来创建。
(3)信号(Signal):信号是比较复杂的通信方式,用于通知接受进程有某种事件发生,除了用于进程间通信外,进程还可以发送 信号给进程本身;linux除了支持Unix早期信号语义函数sigal外,还支持语义符合Posix.1标准的信号函数sigaction(实际上,该函数是基于BSD的,BSD为了实现可靠信号机制,又能够统一对外接口,用sigaction函数重新实现了signal函数)。
(4)消息(Message)队列:消息队列是消息的链接表,包括Posix消息队列system V消息队列。有足够权限的进程可以向队列中添加消息,被赋予读权限的进程则可以读走队列中的消息。消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺
(5)共享内存:使得多个进程可以访问同一块内存空间,是最快的可用IPC形式。是针对其他通信机制运行效率较低而设计的。往往与其它通信机制,如信号量结合使用,来达到进程间的同步及互斥。
(6)内存映射(mapped memory):内存映射允许任何多个进程间通信,每一个使用该机制的进程通过把一个共享的文件映射到自己的进程地址空间来实现它。
(7)信号量(semaphore):主要作为进程间以及同一进程不同线程之间的同步手段。
(8)套接口(Socket):更为一般的进程间通信机制,可用于不同机器之间的进程间通信。起初是由Unix系统的BSD分支开发出来的,但现在一般可以移植到其它类Unix系统上:Linux和System V的变种都支持套接字。
以上这篇详谈java线程与线程、进程与进程间通信就是小编分享给大家的全部内容了,希望能给大家一个参考,也希望大家多多支持。
# 进程
# 线程
# 通信
# java多线程之线程
# 进程和Synchronized概念初解
# java中进程与线程_三种实现方式总结(必看篇)
# 简述Java中进程与线程的关系_动力节点Java学院整理
# java 线程详解及线程与进程的区别
# 输出java进程的jstack信息示例分享 通过线程堆栈信息分析java线程
# java高并发之理解进程和线程
# 取钱
# 余额为
# 多个
# 旗标
# 已有
# 信号量
# 中有
# 设为
# 两次
# 这两个
# 给大家
# 相关联
# 来实现
# 这三个
# 就可以
# 自己的
# 则需
# 加锁
# 的是
# 都是
相关文章:
头像制作网站在线观看,除了站酷,还有哪些比较好的设计网站?
建站之星多图banner生成与模板自定义指南
建站之星客服服务时间及联系方式如何?
官网自助建站平台指南:在线制作、快速建站与模板选择全解析
制作网站的过程怎么写,用凡科建站如何制作自己的网站?
如何快速生成ASP一键建站模板并优化安全性?
安云自助建站系统如何快速提升SEO排名?
太原网站制作公司有哪些,网约车营运证查询官网?
网站视频制作书签怎么做,ie浏览器怎么将网站固定在书签工具栏?
小型网站制作HTML,*游戏网站怎么搭建?
家族网站制作贴纸教程视频,用豆子做粘帖画怎么制作?
小视频制作网站有哪些,有什么看国内小视频的网站,求推荐?
电影网站制作价格表,那些提供免费电影的网站,他们是怎么盈利的?
C++时间戳转换成日期时间的步骤和示例代码
中山网站制作网页,中山新生登记系统登记流程?
如何实现建站之星域名转发设置?
香港服务器选型指南:免备案配置与高效建站方案解析
建站之星如何实现网站加密操作?
如何快速打造个性化非模板自助建站?
济南网站制作的价格,历城一职专官方网站?
c# 服务器GC和工作站GC的区别和设置
黑客如何通过漏洞一步步攻陷网站服务器?
枣阳网站制作,阳新火车站打的到仙岛湖多少钱?
制作电商网页,电商供应链怎么做?
装修招标网站设计制作流程,装修招标流程?
如何在阿里云ECS服务器部署织梦CMS网站?
手机钓鱼网站怎么制作视频,怎样拦截钓鱼网站。怎么办?
建站上市公司网站建设方案与SEO优化服务定制指南
在线ppt制作网站有哪些软件,如何把网页的内容做成ppt?
韩国网站服务器搭建指南:VPS选购、域名解析与DNS配置推荐
建站之星安装失败:服务器环境不兼容?
如何快速查询网址的建站时间与历史轨迹?
魔方云NAT建站如何实现端口转发?
JS中使用new Date(str)创建时间对象不兼容firefox和ie的解决方法(两种)
测试制作网站有哪些,测试性取向的权威测试或者网站?
建站之星如何助力企业快速打造五合一网站?
微信小程序 五星评分(包括半颗星评分)实例代码
定制建站平台哪家好?企业官网搭建与快速建站方案推荐
电商网站制作公司有哪些,1688网是什么意思?
建站10G流量真的够用吗?如何应对访问高峰?
如何在阿里云虚拟机上搭建网站?步骤解析与避坑指南
c++怎么实现高并发下的无锁队列_c++ std::atomic原子变量与CAS操作【详解】
网页制作模板网站推荐,网页设计海报之类的素材哪里好?
网站制作网站,深圳做网站哪家比较好?
建站之星×万网:智能建站系统+自助建站平台一键生成
网站制作怎么样才能赚钱,用自己的电脑做服务器架设网站有什么利弊,能赚钱吗?
TestNG的testng.xml配置文件怎么写
如何彻底删除建站之星生成的Banner?
利用JavaScript实现拖拽改变元素大小
如何通过可视化优化提升建站效果?
*请认真填写需求信息,我们会在24小时内与您取得联系。