java多线程设计模式之-SingleThreadExecution模式

来源:互联网 发布:老虎证券 电脑软件 编辑:程序博客网 时间:2024/05/19 04:56

   之前学习的程序大多是单个线程运行,不会涉及到多个线程之间的通信以及多个线程之间的相互影响,按照之间的面向对象的设计方法,我们可以模拟一个场景,当我们进出门口的程序,进出门口时需要记录这个人的姓名和出生地(模拟用例):

   设计一个“门”类,用于记录进出口的人员,代码编写如下:

public class Gate {private int counter = 0;private String name = "Nobody";private String address = "Nowhere";public void pass(String name,String address){this.counter++;this.name=name;this.address=address;check();}private void check(){if(name.charAt(0) != address.charAt(0)){System.out.println("*****BROKEN*****" + toString());}}public String toString(){return "No."+counter+":"+name+","+address;}}
代码中定义了一个check()方法,此方法用于判断人的名字和住址的名字是否是一致的,同时toString()方法用于输出次数,姓名和地址。同时设计一个用户线程类UserThread类:

public class UserThread extends Thread {private final Gate gate;private final String myname;private final String myaddress;public UserThread(Gate gate,String myname,String myaddress){this.gate=gate;this.myname=myname;this.myaddress=myaddress;}public void run(){System.out.println(myname+" BEGIN ");while(true){gate.pass(myname, myaddress);}}
这个类继承了Thread类,并重写了run()方法,不停的调用pass()方法,输出结果。进而设计一个main类作为测试类测试使用:

public class Main {public static void main(String[] args){System.out.println("Testing Gate, hit CRTL + C to exit.");Gate gate = new Gate();new UserThread(gate,"Alice","Alaska").start();new UserThread(gate,"Bobby","Brazil").start();new UserThread(gate,"Chris","Canada").start();}}
测试结果如下:

可以结果出现错误,不是按照我们最初的设计来执行的,即便是名字和出生地的首字母一样还是会输出错误,这是为什么呢,设计的也没有错误呀?

原因是Gate类是非线程安全的,当时实例化完成三个人以及其相对应的出生地,不停地调用pass方法时,Gste类中相应的记录的字段不停的被重写,以Alice和Bobby为例,当Alice写入完成后,在调用toString()方法之前,pass方法再次写入Bobby,从而将Alice覆盖掉,值变为了Bobby,调用toString()方法时得到的是Bobby,同时调用check()方法时这事也会显示BROKEN,因为Alice的出生地和Bobby的出生地是不一致的,所以会执行BROKEN语句。其实,对于name字段,Alice和Bobby也会存在数据竞争,数据竞争中获胜的一方会先写入值,所以对于name字段的值是无法预测的,然而我们怎么改变才能够将结果按照我们预先设定的情况来执行呢?

我们可以把Gate类变为线程安全类,我们只需要修改此类,变为安全类即可,使用sychornized修饰相应的代码块或者是方法即可,修改后的Gate类为:

public class Gate {private int counter = 0;private String name = "Nobody";private String address = "Nowhere";public synchronized void pass(String name,String address){this.counter++;this.name=name;this.address=address;check();}private void check(){if(name.charAt(0) != address.charAt(0)){System.out.println("*****BROKEN*****" + toString());}}public  String toString(){return "No."+counter+":"+name+","+address;}}

将pass()方法用sychornized修饰,保证在一个线程进行操作的时候,其它的线程不能够进入方法进行操作,这样线程就不能够相互交错执行了,也就不会再显示BROKEN了,细心一点会发现,toString()方法没有被sychornized修饰,有人会说这个也需要被修饰呀,如果其它的线程调用这个类的方法时,也会出现多个线程相互交互执行,比如当某个i线程在某个线程修改字段前调用了toString方法,其打印出来的内容就不在时自己预想的输入的那样,是的,关键的问题在于类的状态是否发生了改变。

所以,什么时候可以使用SingleThreadExecuton模式呢?

前提,当存在共享资源时,并且存在不安全的方法去访问共享资源。

1.单个线程无需synchronized修饰,单线程不会破坏程序的安全性,若使用synchronized来修饰方法的化,要花费更多的时间,当多个线程去访问共享的资源时,就需要注意使用singleThreadExecuton模式设计。当然并不是所以有的多线程都这样设计,当多线程中所有线程都是完全独立的操作,那就不必要使用这种设计方式。

2.当共享资源的状态改变时,就需要使用SingleThreadExecuton设计方法,来保证在一个操作的线程中,其他线程无法再对共享资源进行操作。

3.当需要确保安全性时,需要使用这种设计方式。Java中的集合类大多都是非线程安全的,其主要从提高程序的运行速度来考虑,因此在自己使用Java集合类时一定要查看API文档检查是否是线程安全的。

Java提供了以下几种方法,来确保集合类是线程安全的:

synchronizedCollection();

synchronizedList();

synchronizedMap();

synchronizedSet();

synchronizedSortedMap();

synchronizedSortedSet();

  当时用SingleThreadExcuton设计模式,难免会产生存性和死锁的问题,就好比两个人吃牛肉,只有一副刀叉,你拿着刀子,等待着另一个人用完叉子,你在使用,而他也在等待你用玩刀子再给他,这样类似于两个线程持续的等待下去,无法运行,这时候就发生了死锁。

   发生死锁的条件有三个:

1.存在着多个共享资源;

2.线程持有某个共享资源的锁的同时,还需要获取其它共享资源的锁;

3.获取共享资源的锁的顺序不固定;

   当然,破坏死锁发生的条件中的任何一个条件就可以防止死锁的发生。

同时还要注意继承反常的问题,当继承的子类中含有不安全的方法其访问共享资源时,就会出现不安全性,若要保证安全,则需要将子类中的不安全的方法用synchronized修饰,这样就可以确保贡献资源的安全性。

一般情况下:这种设计模式会降低程序的性能,因为线程需要获取对象的锁,该处理会花费时间。同时还会有线程之间的冲突而引起的线程之间的等待,对于这一点,可以逐步的缩小临界范围,降低线程之间冲突的概率。例如,ConcurrentHashMap和Hashable两者的功能相似,都是线程安全的,其相应的方法就不一样了。

   Hashable中所有的方法都是采用的SingleThthreadExecution模式设计的,前者则是将内部的数据结构分成多段,针对各段落的操作的线程不会相互干扰,这样看来,前者更容易发生线程冲突。


拓展:过于synchronized

synchronized方法和synchronized代码块都可以看作是再“{”获取锁,在“}”释放锁,为此我们来比较一下和显示的处理所的代码作比较:

假设有lock()和unlock()方法分别代表锁的获取和释放,在一个处理的方法中,在两个方法间有return表达式,则将不会调用后文的unlock()方法,那么锁就有可能不会释放,然而显示处理锁的代码中,我们可以采用try···catch···finally,在finally中调用unlock()方法,这样就可以类似的像synchronized修饰的那样进行工作了。

synchronized保护,保护什么,以什么单位来进行保护,使用哪个锁进行保护,这是需要在设计时需要考虑的,从多线程的角度来看,被修饰的部分的是不可分割的操作,通常被认为是原子操作。




原创粉丝点击