前言
Single Threaded Execution是指以一个线程执行,简单来说就是在多线程中限制同时只让一个线程运行
线程不安全
首先来模拟一个线程不安全的例子
目前上面的Gate类是线程不安全的
测试类
测试结果(截取部分)
test begin:
B1 BEGIN
A1 BEGIN
brokenNo.1305356:A1 ,A2
brokenNo.1347999:B1 ,B2
brokenNo.1357011:A1 ,A2
brokenNo.1370176:B1 ,B2
brokenNo.1377075:B1 ,B2
从结果来看,明显发生了错误,即出现了线程不安全,但是也有点不对劲
为什么会出错了
问题的出错地方就在Gate类中,因为其是线程不安全的,打个比方
线程A1 线程A2 this.name值 this.address值
count++ count++ 之前的值 之前的值
this.name=name A1 之前的值
this.name=name A2 之前的值
this.address=address A2 B2
this.address=address A1 B2
check() check() A1 B2
发生错误
以上只是发生错误的一种情况,事实上只有当数据量非常大时,极有可能发生错误,所以才会出现错误的结果,并且是在count达到大数量的时候出现的
那么如何修改了?
因为发生在Gate类的pass和toString方法中,所以只要把这个两个方法声明为同步就可以了
public synchronized void pass(String name,String address){}
public synchronized String toString(){}
对应的测试结果
test begin:
A1 BEGIN
B1 BEGIN
C1 BEGIN
归纳single Threaded
上面例子的解决方案就是该模式的一种应用,可以将其抽象为以下几个参与者
SharedResource(共享资源)参与者:可有多个线程访问的类,如Gate
SafeMethod:从多个线程同时调用也不会发生问题
UnsafeMethod:从多个线程同时调用会发生问题,需要防范的方法
所以必须将上述不安全的方法加以同步锁,当时如果通过其他方法改变不安全方法中数据也会发生错误,就像大门上锁,但是窗户开着一样,所以有时也是一个隐患的问题
那么此种模式何时适用了?
多线程时、数据可被多个线程访问的时候、状态可能变化的时候、需要确保安全性的时候
生命线与死锁
使用该模式,可能会发生死锁的危险
而死锁是指:两个线程分别获取了锁定,互相等待另一个线程解除锁定的线程。发生死锁时,哪个线程都无法继续执行下去,所以程序会失去生命线
最经典的死锁问题就是汤勺与叉子模型,问题大概是这样的
假设A与B同吃一碗面,盘子旁有一支汤勺与一支叉子,而吃面必须同时需要汤勺和叉子
而现在叉子被其中一人假设是A拿走,而汤勺被B拿走,就会造成如下现象:
A和B一直等待对方放下叉子(或者汤勺),即一直处于等待,僵持阶段,从而程序无法继续运行,故称为死锁现象
而只要上述模式达到下面的条件,就会出现死锁
1.具有多个SharedResource参与者
2.线程锁定一个SharedResource时,还没接触前就去锁定另一个SharedResource
3.获取SharedResource参与者的顺序不固定
当然只要破坏上述三个条件之一就可以避免死锁的发生
下面就开始模拟死锁
首先是表示餐具的类(这里只有叉子和汤勺)
开始用餐的类
测试类
测试结果:
test begin:
Atakes up[Spoon]left.
A takes up[Fork]right.
A is eating
A puts down[Fork]right.
Aputs down[Spoon]left.
Atakes up[Spoon]left.
A takes up[Fork]right.
A is eating
A puts down[Fork]right.
Aputs down[Spoon]left.
重复。。直到
Atakes up[Spoon]left.
Btakes up[Fork]left.
该处程序停止不动了,也就是a和b分别拿着汤勺和叉子,即处于死锁状态了。
一种最简单的方法就是改变顺序,即以相同顺序拿餐具
即:
Tool spoon=new Tool(“Spoon”);
Tool fork=new Tool(“Fork”);
new EaterThread(“A”, spoon, fork).start();
new EaterThread(“B”, spoon,fork).start();
最后程序就会一直进行下去,不过这样的话,前面的共享资源就不存在了!