JAVA-初步认识-第十四章-线程间通信-示例

一. 引言

之前讲述了线程的基本使用,卖票和存钱。卖票相当于把资源都释放出来,被别人获取到。而存钱,则是把数据都存进去。

现在,我们将线程进行了改变。以前是多个线程在执行同一个动作,无论是继承还是实现,都是一个run方法。换句话说,就是一个线程任务,多线程在同时执行一个任务。只不过它们是分别存放在了自己不同的线程栈里面,再进行统一运行,但是动作是一样的,比如说四个人都是卖票。

现在要讲述的是线程间通信。何谓通信,线程还是多个,但是运行的任务却变得不一样了。有个特点,它们处理的资源是一样的。

以前是处理同一个资源,同一个动作,现在不是了。

现在把线程间通信给大家讲述一下。

二. 线程通信程序的构建和问题

具体的内容我们是清楚地,但是叫通信有点奇怪。

先讲述具体的例子,再将例子抽象成程序。

用现实语句描述是这样的,Resource中有name和sex,input负责不断的更新name和sex,而output负责将原先的name和sex输出。为了更好的处理这两个动作,它们二者可以相互切换。

现在怎么将这个情况变成程序呢?

我们是对Resource中的name和sex进行操作,数据比较多,对其进行封装成对象,这样方便处理。

input在输入的同时,output在输出,两者同时操作。如果output不及时输出,那么将有部分数据被覆盖。这样一来就涉及多线程技术了。在这里的多线程中,input和output的任务还不一样。同时运行是没有问题的,但是任务完全不一样。任务不一样,就意味着我们要进行单独地封装。有两个任务对象,就表明有两个run方法,要分别封装在两个类当中(为什么任务也要单独封装成类?设定在资源类中不行么?我知道了,应该是两个run方法不能在同一个类中。如果可以的话,就是重载,重载怎么调用?)。因此到这里,有三个类,Resource类,input类,output类。

三个类中,我们先完善了输出类。输出不是一次就行的,因此设计了while(true),至于为什么是这样设计,以后会讲述到。在输出时,输出的是name和sex,但是这两者都是其他类的,因此要创建对象来调用。

紧接着,我们看一下输入,先不做切换,只是做一下简单的动作。

输入也不是一次的,也是多次。同时也要创建对象来接收name和sex数据。但这这样写不合适,因为输入和输出用的是两个资源,各自有各自的new Resource();这个就是问题。我们要求的是输入和输出是同一个资源。

有人说干脆静态,将变量name和sex静态(我猜说的变量的静态),之前卖票的时候讨论过这个问题,认为是设置成对象比较靠谱。只是静态变量不是很好。

现在资源不一样怎么办呢?将其变成单例,这个是可以的。但是单例一样有个局限性,就是只能出现一个对象,就和说的卖票一样,一个对象还是一百张票(我觉着可能是限制性太大了,只能是一个对象?)

可以肯定的是在输入类中,不能new对象(要和输出保持同一个对象),但是还要操作资源。可行的方法就是传参递,把参数传递进来。

怎么做呢?搞个函数传递。在外面把对象建立好,分别传递给输入和输出。(这个外面建立的对象,貌似要起的作用挺多,同时照顾两个类的操作)

能接收参数的方法有两种形式,要么是一般方法值,要么是构造函数。我们这是任务,处理资源,这就意味着任务一对象初始化就是资源,用构造函数就可以了。一构造对象,就需要有东西,就必须在对象里初始化完毕,就跟我们说线程一样,线程对象一创建,就必须有任务。这个思想来自于线程。

这里为了实现输入的特点,在这里面实现切换,输入两个名字,实现间隔切换,好像输入很多人名字这种效果。

怎么切换呢?

对于x的两种清楚,分开来赋值。那么要怎么切换x呢?
有人说x%2,这样可以实现0和1的切换,想法挺好,x得++。记住了,你想模以2变化,你必须实现x的变化,x不变化它怎么模以2变化呢?还要将这个值记下来。整体的循环是在while(true)的方法体内。

现在开始描述主函数。首先要有资源,没有资源什么都没法做(我的理解是要用来传递用的),Resource r=new Resource();接下来要有任务,接着就是任务对象,任务也有资源处理,Input in=new Input(r); 资源有了,任务有了,你得有路径,Thread t1=new Thread(in),

创建资源→创建任务→创建线程,执行任务→开启线程。有任务的时候,必须明确资源,有路径的时候,必须明确任务。

先要停止,ctrl+alt+c。DOS结果出现了问题,丽丽后面有nan,而mike后面有女女女女女。

出现了问题,我们要去解决,但是首先我们必须知道这个问题怎么来的。

三. 了解线程通信程序问题的原因和解决措施

这里专业术语叫做线程安全问题。

成因怎么分析?

在线程运行的代码中是否有共享数据?是否有多条语句在操作共享数据?name和sex是资源的内容,而资源是共享的,在这出现的问题。r是资源,我在操作资源中的两个属性,r.name和r.sex,而且在多条语句中操作,if中有,else中也有。

在争抢的过程中,input先输入了mike nan,第二次刚输入丽丽,cpu执行权就被output拿走了,就是在这出现的问题。(这是否就意味着要引入同步?)

DOS结果还是出现了问题,(我觉着是同步代码块括的范围太广了?)

现在是加了同步,却没有解决问题,是我同步加的不对,还是同步解决不了这个问题?

怎么解决问题?当你碰到线程安全问题时,你想要的无非是同步解决,可以你加同步了,问题依旧怎么办?要去考虑同步的前提。

前提合并完就是一句话,一个锁里面是否有多个线程。换句话说,就是多个线程是否都在同一个锁当中。

我们看上面的截图,是同步,但是同步代码块里面就一个线程,为什么么?输出线程不在同步代码块中,所以我们应该保证输出线程也应该在同步里面。你不在同步,光同步输入有什么用?什么意思啊,你输入一半的时候,我不能过去取。

还是有问题,现在是将线程往同步里面放,但是这里用的是不同的锁。现在我们就不写obj了,不好使了。有的人说用this,this白扯,这是两个类,而this代表本类。有人说,用静态锁,输入类中写Input.class,输出写Output.class. 这和扯一样,

用Input.class是可以,如果用Resource.class绝对是没问题,它是唯一的。在这里不需要用class字节码文件对象,在这只需要保证对象唯一就能用,正好资源就是唯一的,两者处理的都是同一个资源,往里放r就可以了。

DOS运行的结果全是一种,有问题。

再操作的过程中除了if,else语句需要同步外,还有语句在操作资源,

下图中的输出语句也在操作资源,下面这句和if同时操作资源。

我们应该将同步放在里面,

为什么这么说?如果将同步放置在while(true)的外面,那么线程进来后,就出不去了,一直在循环。

这样改之后,就没有问题了。

现在问题没有了,但是结果是一大片,一大片。为什么会是连续的一片?

输入线程它获取到执行权的时候,它不会只扔一次,它能执行好多次,那就意味着输入线程拿到执行权先往里面放了一个mike nan,放完以后,它还拿着执行权,紧跟着就赋值了丽丽 女,这就导致前一个数据被覆盖了。接着又往里赋值mike nan,丽丽 女,等赋值的差不多了,执行权切到输出,资源里面要么是mike nan,要么是丽丽 女,由于是同步,肯定不会出现mike 女之类的,输出的时候,它取得是最后赋值的数据,由于拿着执行权,因此输出也不是一次。

时间: 2024-11-07 07:18:37

JAVA-初步认识-第十四章-线程间通信-示例的相关文章

JAVA-初步认识-第十四章-线程间通信-多生产者多消费者问题-JDK1.5解决办法

一. 在1.5版本中,将原先的形式进行了改变,但是功能并没有任何变化,那么这么做的原因是什么? 以前,我们一个锁上只有一组监视器,这组监视器既监视着生产者,又监视着消费者.这组监视器能将生产者和消费者全都wait,也能将生产者和消费者全都唤醒.或者notify也行,它也能将其中一条线程唤醒,而其中一条不能确定是谁,有可能是本方,也可能是对方. 现在我们的线程进行了分类,一组负责生产,一组负责消费.我们希望生产者能够唤醒消费者,消费者唤醒生产者.如果搞两个监视器,一组监视生产者,一组监视消费者,这

JAVA-初步认识-第十四章-线程间通信-等待唤醒机制-代码优化

一. 上一节中的代码写的并不是很眼镜,如下图中的属性,应该都是私有的.我们不应该直接访问资源中的属性,是因为它具备了不安全性. 瞎赋值怎么办呢?为了可控,意味着资源里面的属性需要被私有化,并对外提供方法访问.因此上节中的代码要进行改写. 首先对资源描述类进行修改,至于为什么set方法中写有两个形参,是因为name和sex同时要做赋值,因此直接将它们定义在一起. 而且类中提供了直接输出name和sex的方法,后面的程序中就不需要写那么长的输出语句了.这个输出问题比较简单 关键问题在哪儿呢?如果这么

Java并发工具类(四)线程间交换数据的Exchanger

简介 Exchanger(交换者)是一个用于线程间协作的工具类.Exchanger用于进行线程间的数据交换.它提供一个同步点,在这个同步点两个线程可以交换彼此的数据.这两个线程通过exchange方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方. Exchanger的应用场景 1,Exchanger可以用于遗传算法,遗传算法里需要选出两个人作为交配对象,这时

201671010107 2016-2017-2 《Java程序设计》第十四章学习心得

通过这一章的学习,我知道了程序,进程和线程之间的关系,知道了Java实现多线程的两种途径:1.创建Thread类的子类:2.在程序中定义实现Runnable接口的类.线程的中断,线程的六种状态,多线程调度,守护线程,线程的同步等.

java 面向对象编程--第十四章 多线程编程

1.  多任务处理有两种类型:基于进程和基于线程. 2.  进程是指一种“自包容”的运行程序,由操作系统直接管理,直接运行,有自己的地址空间,每个进程一开启都会消耗内存. 3.  线程是进程内部单一的顺序控制流.一个进程拥有多个线程.多个线程共享一个进程的内存空间. 4.  基于进程的特点是允许计算机同时运行两个或更多的程序. 5.  基于线程的多任务处理环境中,线程是最小的处理单位. 6.  基于进程所需的开销更少:每个进程都需要操作系统为其分配独立的内存空间:同意进程中的所有线程都在同意内存

[总结] 第十四章 线程

线程 一个线程是进程内部的分支 .线程是共享一个进程的内存空间.\n Thread 类 (线程) 1.写一个类继承Thread类,重写run():方法,\n 2.new 出这个类\n 3.调用这个类的start()方法,开启线程,该方法会为线程分配资源,然后自动调用this,run()方法,如果直接调用run方法不会报错,但只是普通的方法调用而已,并没有开启线程 第二种方式(如果一个类继承了一个父类): runnable 接口, 1.写一个类实现runnable接口,重写run()方法. 2.先

【Java并发编程】之十二:线程间通信中notifyAll造成的早期通知问题(含代码)

如果线程在等待时接到通知,但线程等待的条件还不满足,此时,线程接到的就是早期通知,如果条件满足的时间很短,但很快又改变了,而变得不再满足,这时也将发生早期通知.这种现象听起来很奇怪,下面通过一个示例程序来说明问题. 很简单,两个线程等待删除List中的元素,同时另外一个线程正要向其中添加项目.代码如下: [java] view plaincopy import java.util.*; public class EarlyNotify extends Object { private List 

转:【Java并发编程】之十二:线程间通信中notifyAll造成的早期通知问题(含代码)

转载请注明出处:http://blog.csdn.net/ns_code/article/details/17229601 如果线程在等待时接到通知,但线程等待的条件还不满足,此时,线程接到的就是早期通知,如果条件满足的时间很短,但很快又改变了,而变得不再满足,这时也将发生早期通知.这种现象听起来很奇怪,下面通过一个示例程序来说明问题. 很简单,两个线程等待删除List中的元素,同时另外一个线程正要向其中添加项目.代码如下: [java] view plain copy import java.

转:【Java并发编程】之十一:线程间通信中notify通知的遗漏(含代码)

转载请注明出处:http://blog.csdn.net/ns_code/article/details/17228213 notify通知的遗漏很容易理解,即threadA还没开始wait的时候,threadB已经notify了,这样,threadB通知是没有任何响应的,当threadB退出synchronized代码块后,threadA再开始wait,便会一直阻塞等待,直到被别的线程打断. 遗漏通知的代码 下面给出一段代码演示通知是如何遗漏的,如下: [java] view plain co