下面我们看,借助几个例子,来看一下这个
前面讲的这套方法,如何用这种方法进行控制驱动部分的设计 第一个例子呢是多线程产生动画的一个例子
这个实际上我们这前面讲到那个完整的游戏的例子的时候也提到,只不过这个
实际上不是一个很实用的一个例子,但是主要是通过例子我们可以来
理解一下这个我们前面我们讲的那些线程,特别是线程
这种控制流实现,是如何实现的,以及在Java这种 编程机制下,如何应用这个Guarded,就是说
不通过,两个线程之间用这种自发的方式来实现这种协调
不通过第三者协调,专门的协调线程来进行协调,这种方式
这是一个什么意思呢?就是我们现在是有这么一个需求,就是说
在一个板子上,或者说一个屏幕上 首先呢有一个线程要写,有两个线程在写
然后写一个线程写一个红的,一个线程写一个蓝的 方块,然后呢写完之后呢两个线程释放掉资源之后
由第三个线程来把它从内存的数据结构中把它复制到屏幕上,这样就显示出来了
然后接下来它办完之后呢,这个 另外,它就休眠了,这个搬运线程休眠,然后呢另外一个
这个线程呢,另外两个线程又出来之后进行写第二帧数据
也就是说,把这个原来的这个擦掉,然后再计算一个偏移量之后再计算,这样的话就会显示 这就会这个显示出来。
那么这个系统的这个控制流 的设计呢,简单的来说就比较简单了这个系统,所以我们这个设计的时候呢
首先呢是一个主线程,这个是一个就相当于我们当前的一个
这个线程不是协调者啊,它本身就,主要是用来这个,就相当于,本身这个
这个主窗体,它呢聚合了 thread player
1 player 2 player 3,三个线程 它实际上在需要的时候呢将这三个线程产生出来就完了,它没有提供任何的这个
三个线程之间这个协调的问题,另外呢还有两个什么呢 一个这个内存中的一个main,还有一个什么呢,一个
设备,是吧,一个就是说这个,它们这个数据结构是一样的 这个第三个线程在搬运的时候,只需要将main这个线程
这个有main这块这个 数据结构,把它复制到这个graphics上就可以了,这样就 显示到屏幕上。
但是光有这个,如何实现这个三个线程之间的协调问题? 是吧?这三个线程之间属于这个
需要进行协调这一类,类型。
不是说这三个线程之间这个 跑起来之后各自不管个人了,个人运行个人的就可以了。
它们之间需要一个密切的协调 这个协调呢,一方面是数据的协调,一方面是这个步骤的协调,是吧?必须是player 1
和player 2,player 1 和player 2 之间谁先谁后没关系 这个它先写它先写都没有关系
但是对于任何一帧数据来说,player 1 和player 2之间,谁先写谁后写没关
但是呢,写完之后必须呢后面这个线程马上是player 3
把它 搬运回去,搬运到屏幕上,这个线程,它搬运完之后呢,接着player 1 和player 2 之间呢,player
1 和player 2 接着再过来再写,这个顺序是不能,必须是严格遵守的
不能说是player 1 写完之后player 2 写,然后呢写
完之后呢它再写一块,然后这个写了两块,写了两个 循环之后,第三个线程才来搬,那样就使得这个线
这个整个过程就不够流畅,为解决这个问题,实际上这个没有必要 是吧?实际上我们主要是为了什么,非要三个线程,两个线程就可以了
为什么搞这两,搞三个线程,我们主要是为了让它变得复杂一点,然后呢来这个
体现我们这种协调,协调的这个机制。
我们用什么协调方式呢?我们就 是用了第三种,第二种协调方式,用Guarded这种方式,是吧?用它们三个线程之间-
自发这种 这个协调,借助一种这个 Guarded,标记为Guarded的这种
方法,这个方法呢,实际上是有一个flag类 来这个表示的,这个flag类有什么作用呢?就是
说任何的一个player在进行运行的时候呢,它会 调用这里边的一些Guarded的操作,它在调用的时候呢
这里面有个机制就是说,它在调用的时候呢,其它的线程 必须得保证其它线程没有调用这里边的所有的操作,如果调用的话
那么它就会自动的会进入休眠状态,这是它Guarded的一个 一个语义。
这是借助于Java里边同步的那种 一种机制来很好的对应过来,这样的话进行实验,如果
你这个编译器不支持这种方式你就没法用了,你就得用其它的方式
下面我们具体看一下这个通过顺序图来描述这个线程之间如何产生的以及如何交互的
这个很简单的,就是这个主线程啊,产生完三个线程就不管了,然后它们三个
自生自灭,自我调节,主线程没有任何的协调作用 这个产生完之后第一个线程来说,第一个线程呢是这个player
1,它负责写那个红色的一个小的方块 它写之前呢,首先是什么时候它开始这个
进行这个激活状态,那就是由其它线程notify它,通知它
唤醒它,然后它就起来,唤醒之后它所做的第一件 工作就是要调用flag
1 的这个askf1 askf1 的时候呢,实际上这个f1
是个特殊的,askf1 是个Guarded的一个方法,是吧?它在调用的时候不允许其它方法再调用其它这个类里-
边其它的方法 所以这就便于它们之间协调 它调用一下,如果f1=1,那么就表示着什么呢?
当前的这个板子,实际上是被那个正在,不适合你现在正
在写,因为现在正在是第三个线程正在搬运的时候,搬运的时候你写是不允许的 这样的话呢它就让它在休眠,让这个线程休眠
一旦f1=0 的话那么实际上就是什么呢?
就是可以写了,这时候f1 已经搬完了,这时候你可以再写了,你这时候写的时候呢
这个这些过程呢我们后面再说啊,这个写的过程由后面来进行描述,写完之后 要释放资源。
释放掉f1,那就是置f1=1,同时呢
notify这两个线程进行这个,把这两个线程叫醒,然后让它这两个线程起来跟它一- 块iii
但是这个notify 1 和 2 之后呢,这儿我们有一个什么比较巧妙的地方就是说
它在向这两个线程发送notify叫醒唤醒这个消息 之后,它本身没有直接进行休眠
而是什么呢,而是它又回到了这个循环里边 再次请求f1,由于这时候f1
已经被自己置为 1 了,所以它 这时候才进入休眠状态,而由另外两个线程呢再进行协调
需要注意的是,另外就是说这个notify是一个同步消息,这里边为什么同步消息,- 就是因为
它在notify的时候这个player 1 不做任何事情,它就等待这个消息
那么player 2 已经处在休眠状态,就在那儿死等着
这个通知消息来唤醒它,所以这是个同步消息。
好,下面这是第二个 player 2,player 2 跟player
1 很像,也是被叫醒之后它要调用一个 这个Guarded方法标注的同步消息,叫askf2,f2
这个f2 的时候呢是这个正好是它如果说 f2=1
然后它就等待,否则的话它就开始 做自己的业务,最后也是一样。
它跟这个刚才 那个这个区别就在于,第一个是这个 在这个叫醒的时候呢首先要查询一下f1
是否等于1 第二个是查询f2=1,那么第三个线程是什么呢?它们与那个
它的工作关系和那两个是互斥的,那两个本身来说呢谁先做谁后做是没关系的 这时候呢这个player
3 出的搬运线程,实际上是叫醒之后,被叫醒之后
呢它是要查询一下,是否f1=0 或者说是f2=0 只要有一个等于
0 那就表示那个正在写,它就不能搬,否则的话呢
那么就是说什么呢,f1=1 并且f2=1 这两个都已经写完并且要退出来的时候
它才能进行搬运这个操作,搬运完之后同时释放掉,也是释放掉这个 相当于把f1 f2
都置为1,然后通知另外两个线程起来 这个这样的话呢这个它们就产生一个非常好的一个协调作用,也就是说
尽管它们是三个线程,按理说应该是这个各自执行跟各自没有关系,但是借助一种同步机制
它们可以很好的实现,第一个线程写 第二个线程写,然后呢第三个线程
搬,搬完之后呢第一个再写,第二个再写,当然也可能是第二个写,第一个再
写,这个都没关系,但是呢必须是这两个都写完之后第三个才能搬 是这么一种情况。
这个在这个真正工作的时候 是如何工作呢?player 1 是首先呢将原来的那块
自己写的那块那个小的一个正方形给它擦掉,涂黑,然后计算一个偏移量之后在新的位置- 上再重新
写一个画一个红色的矩形,而player 2 呢是正好也是一样,只不过它
画一个蓝色的矩形,player 3 呢实际上就是将这个image 中的一个参数
这个把这个image复制到设备上,这个屏幕设备上去。
这是一个简单 状态图,体现player 1 的一个状态,这个状态图呢是产生,new产生之后,然后这个进行就绪
就绪的时候,如果f1=1,然后这种 这个事件,这是一个条件事件,激发它进入休眠状态
如果被其它的一个player 2 和player 3 其它线程叫醒的时候它就进入就绪
如果f1 进入等于0 的话那么它进入运行状态,那就是它进行工作状态
工作完之后呢,然后这个运行完执行完一个 release
f1 之后呢又进入这个就绪状态,这是它的一个简单状态图来描述
下面我们就通过演示的方式,我们来看一下用java语言
如何来实现这个控制驱动不同的方案,以及这个 运行效果。