编程小贴士

给你的编程提供小点子


Java并发的四种风味:Thread、Executor、ForkJoin和Actor

Java并发编程的4种风格:Threads,Executors,ForkJoin和Actors

我们生活在一个事情并行发生的世界。自然地,我们编写的程序也反映了这个特点,它们可以并发的执行。当然除了Python代码(译者注:链接里面讲述了Python的全局解释器锁,解释了原因),不过你仍然可以使用Jython在JVM上运行你的程序,来利用多处理器电脑的强大能力。

然而,并发程序的复杂程度远远超出了人类大脑的处理能力。相比较而言,我们简直弱爆了:我们生来就不是为了思考多线程程序、评估并发访问有限资源以及预测哪里会发生错误或者瓶颈。

面对这些困难,人类已经总结了不少并发计算的解决方案和模型。这些模型强调问题的不同部分,当我们实现并行计算时,可以根据问题做出不同的选择。

在这篇文章中,我将会用对同一个问题,用不同的代码来实现并发的解决方案;然后讨论这些方案有哪些好的地方,有哪些缺陷,可能会有什么样的陷阱在等着你。

我们将介绍下面几种并发处理和异步代码的方式:

• 裸线程

• Executors和Services

• ForkJoin框架和并行流

• Actor模型

为了更加有趣一些,我没有仅仅通过一些代码来说明这些方法,而是使用了一个共同的任务,因此每一节中的代码差不多都是等价的。另外,这些代码仅仅是展示用的,初始化的代码并没有写出来,并且它们也不是产品级的软件示例。

对了,最后一件事:在文章最后,有一个小调查,关于你或者你的组织正在使用哪种并发模式。为了你的工程师同胞们,请填一下调查!

任务

任务:实现一个方法,它接收一条消息和一组字符串作为参数,这些字符串与某个搜索引擎的查询页面对应。对每个字符串,这个方法发出一个http请求来查询消息,并返回第一条可用的结果,越快越好。

如果有错误发生,抛出一个异常或者返回空都是可以的。我只是尝试避免为了等待结果而出现无限循环。

简单说明:这次我不会真正深入到多线程如何通讯的细节,或者深入到Java内存模型。如果你迫切地想了解这些,你可以看我前面的文章利用JCStress测试并发。

那么,让我们从最直接、最核心的方式来在JVM上实现并发:手动管理裸线程。

方法1:使用“原汁原味”的裸线程

解放你的代码,回归自然,使用裸线程!线程是并发最基本的单元。Java线程本质上被映射到操作系统线程,并且每个线程对象对应着一个计算机底层线程。

自然地,JVM管理着线程的生存期,而且只要你不需要线程间通讯,你也不需要关注线程调度。

每个线程有自己的栈空间,它占用了JVM进程空间的指定一部分。

线程的接口相当简明,你只需要提供一个Runnable,调用.start()开始计算。没有现成的API来结束线程,你需要自己来实现,通过类似boolean类型的标记来通讯。

在下面的例子中,我们对每个被查询的搜索引擎,创建了一个线程。查询的结果被设置到AtomicReference,它不需要锁或者其他机制来保证只出现一次写操作。开始吧!

1
2
3
4
5
6
7
8
9
10
11
private static String getFirstResult(String question, List<String> engines) {
 AtomicReference<String> result = new AtomicReference<>();
 for(String base: engines) {
   String url = base + question;
   new Thread(() -> {
     result.compareAndSet(null, WS.url(url).get());
   }).start();
 }
 while(result.get() == null); // wait for some result to appear
 return result.get();
}

使用裸线程的主要优点是,你很接近并发计算的操作系统/硬件模型,并且这个模型非常简单。多个线程运行,通过共享内存通讯,就是这样。

自己管理线程的最大劣势是,你很容易过分的关注线程的数量。线程是很昂贵的对象,创建它们需要耗费大量的内存和时间。这是一个矛盾,线程太少,你不能获得良好的并发性;线程太多,将很可能导致内存问题,调度也变得更复杂。

然而,如果你需要一个快速和简单的解决方案,你绝对可以使用这个方法,不要犹豫。

方法2:认真对待Executor和CompletionService

另一个选择是使用API来管理一组线程。幸运的是,JVM为我们提供了这样的功能,就是Executor接口。Executor接口的定义非常简单:

1
2
3
4
5
public interface Executor {
void execute(Runnable command);
}

它隐藏了如何处理Runnable的细节。它仅仅说,“开发者!你只是一袋肉,给我任务,我会处理它!”

更酷的是,Executors类提供了一组方法,能够创建拥有完善配置的线程池和executor。我们将使用 newFixedThreadPool(),它创建预定义数量的线程,并不允许线程数量超过这个预定义值。这意味着,如果所有的线程都被使用的话,提交的 命令将会被放到一个队列中等待;当然这是由executor来管理的。

在它的上层,有ExecutorService管理executor的生命周期,以及CompletionService会抽象掉更多细节,作为已完成任务的队列。得益于此,我们不必担心只会得到第一个结果。

下面service.take()的一次调用将会只返回一个结果。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
private static String getFirstResultExecutors(String question, List<String> engines) {
 ExecutorCompletionService<String> service = new ExecutorCompletionService<String>(Executors.newFixedThreadPool(4));
 for(String base: engines) {
   String url = base + question;
   service.submit(() -> {
     return WS.url(url).get();
   });
 }
   try {
     return service.take().get();
   }
   catch(InterruptedException | ExecutionException e) {
     return null;
   }
}

如果你需要精确的控制程序产生的线程数量,以及它们的精确行为,那么executor和executor服务将是正确的选择。例如,需要仔细考虑的 一个重要问题是,当所有线程都在忙于做其他事情时,需要什么样的策略?增加线程数量或者不做数量限制?把任务放入到队列等待?如果队列也满了呢?无限制的 增加队列大小?

感谢JDK,已经有很多配置项回答了这些问题,并且有着直观的名字,例如上面的Executors.newFixedThreadPool(4)。

线程和服务的生命周期也可以通过选项来配置,使资源可以在恰当的时间关闭。唯一的不便之处是,对新手来说,配置选项可以更简单和直观一些。然而,在并发编程方面,你几乎找不到更简单的了。

总之,对于大型系统,我个人认为使用executor最合适。

方法3:通过并行流,使用ForkJoinPool (FJP)

Java 8中加入了并行流,从此我们有了一个并行处理集合的简单方法。它和lambda一起,构成了并发计算的一个强大工具。

如果你打算运用这种方法,那么有几点需要注意。首先,你必须掌握一些函数编程的概念,它实际上更有优势。其次,你很难知道并行流实际上是否使用了超过一个线程,这要由流的具体实现来决定。如果你无法控制流的数据源,你就无法确定它做了什么。

另外,你需要记住,默认情况下是通过ForkJoinPool.commonPool()实现并行的。这个通用池由JVM来管理,并且被JVM进程内的所有线程共享。这简化了配置项,因此你不用担心。

1
2
3
4
5
6
7
8
private static String getFirstResult(String question, List<String> engines) {
 // get element as soon as it is available
 Optional<String> result = engines.stream().parallel().map((base) -> {
   String url = base + question;
   return WS.url(url).get();
 }).findAny();
 return result.get();
}

看上面的例子,我们不关心单独的任务在哪里完成,由谁完成。然而,这也意味着,你的应用程序中可能存在一些停滞的任务,而你却无法不知道。在另一篇关于并行流的文章中,我详细地描述了这个问题。并且有一个变通的解决方案,虽然它并不是世界上最直观的方案。

ForkJoin是一个很好的框架,由比我更聪明的人来编写和预先配置。因此当我需要写一个包含并行处理的小型程序时,它是我的第一选择。

它最大的缺点是,你必须预见到它可能产生的并发症。如果对JVM没有整体上的深入了解,这很难做到。这只能来自于经验。

方法4:雇用一个Actor

Actor模型是对我们本文中所探讨的方法的一个奇怪的补充。JDK中没有actor的实现;因此你必须引用一些实现了actor的库。

简短地说,在actor模型中,你把一切都看做是一个actor。一个actor是一个计算实体,就像上面第一个例子中的线程,它可以从其他actor那里接收消息,因为一切都是actor。

在应答消息时,它可以给其他actor发送消息,或者创建新的actor并与之交互,或者只改变自己的内部状态。

相当简单,但这是一个非常强大的概念。生命周期和消息传递由你的框架来管理,你只需要指定计算单元是什么就可以了。另外,actor模型强调避免全局状态,这会带来很多便利。你可以应用监督策略,例如免费重试,更简单的分布式系统设计,错误容忍度等等。

下面是一个使用Akka Actors的例子。Akka Actors有Java接口,是最流行的JVM Actor库之一。实际上,它也有Scala接口,并且是Scala目前默认的actor库。Scala曾经在内部实现了actor。不少JVM语言都实现了actor,比如Fantom。这些说明了Actor模型已经被广泛接受,并被看做是对语言非常有价值的补充。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
static class Message {
 String url;
 Message(String url) {this.url = url;}
}
static class Result {
 String html;
 Result(String html) {this.html = html;}
}
static class UrlFetcher extends UntypedActor {
 @Override
 public void onReceive(Object message) throws Exception {
   if (message instanceof Message) {
     Message work = (Message) message;
     String result = WS.url(work.url).get();
     getSender().tell(new Result(result), getSelf());
   } else {
     unhandled(message);
   }
 }
}
static class Querier extends UntypedActor {
 private String question;
 private List<String> engines;
 private AtomicReference<String> result;
 public Querier(String question, List<String> engines, AtomicReference<String> result) {
   this.question = question;
   this.engines = engines;
   this.result = result;
 }
 @Override public void onReceive(Object message) throws Exception {
   if(message instanceof Result) {
     result.compareAndSet(null, ((Result) message).html);
     getContext().stop(self());
   }
   else {
     for(String base: engines) {
       String url = base + question;
       ActorRef fetcher = this.getContext().actorOf(Props.create(UrlFetcher.class), "fetcher-"+base.hashCode());
       Message m = new Message(url);
       fetcher.tell(m, self());
     }
   }
 }
}
private static String getFirstResultActors(String question, List<String> engines) {
 ActorSystem system = ActorSystem.create("Search");
 AtomicReference<String> result = new AtomicReference<>();
 final ActorRef q = system.actorOf(
   Props.create((UntypedActorFactory) () -> new Querier(question, engines, result)), "master");
 q.tell(new Object(), ActorRef.noSender());
 while(result.get() == null);
 return result.get();
}

Akka actor在内部使用ForkJoin框架来处理工作。这里的代码很冗长。不要担心。大部分代码是消息类Message和Result的定义,然后是两个 不同的actor:Querier用来组织所有的搜索引擎,而URLFetcher用来从给定的URL获取结果。这里代码行比较多是因为我不愿意把很多东 西写在同一行上。Actor模型的强大之处来自于Props对象的接口,通过接口我们可以为actor定义特定的选择模式,定制的邮箱地址等。结果系统也 是可配置的,只包含了很少的活动件。这是一个很好的迹象!

使用Actor模型的一个劣势是,它要求你避免全局状态,因此你必须小心的设计你的应用程序,而这可能会使项目迁移变得很复杂。同时,它也有不少优点,因此学习一些新的范例和使用新的库是完全值得的。

反馈时间:你使用什么?

你最常用的并发方式是什么?你理解它背后的计算模式是什么吗?仅仅使用一个包含Job或者后台任务对象的框架来自动地为你的代码添加异步计算能力?

为了收集更多信息,以找出我是否应该继续更深入地讲解一些不同的并发模式,例如,写一篇关于Akka如何工作,以及它Java接口的优点和缺点,我创建了一个简单的调查。亲爱的读者,请填一下调查表。我非常感谢你的互动!

总结

这篇文章中我们讨论了在Java应用中添加并行的几种不同方法。从我们自己管理Java线程开始,我们逐渐地发现更高级的解决方案,执行不同的executor服务、ForkJoin框架和actor计算模型。

不知道当你面临真实问题时该如何选择?它们都有各自的优缺点,你需要在直观和易用性、配置和增加/减少机器性能等方面做出选择。

124 Responses to “ Java并发的四种风味:Thread、Executor、ForkJoin和Actor ”

  1. see pron说道:

    7N1A6x You, my friend, ROCK! I found just the info I already searched all over the place and simply couldn at locate it. What a perfect web site.

  2. Mantra Yoga说道:

    Thank you for your article post.Really thank you! Great.

  3. provider for the on-line advertising and marketing.

  4. coloringbook说道:

    This is one awesome blog.Thanks Again. Will read on

  5. Lung Detox说道:

    Really enjoyed this article post.Thanks Again. Awesome.

  6. You made some fine points there. I did a search on the topic and found nearly all persons will agree with your blog.

  7. Im obliged for the blog.Really thank you! Want more.

  8. Oluwadamilare说道:

    Thank you ever so for you article post.Really thank you! Great.

  9. Thanks so much for the post.Really thank you! Really Cool.

  10. hamptonsite说道:

    This site was how do you say it? Relevant!! Finally I have found something that helped me. Thank you!

  11. casino online说道:

    wonderful post, very informative. I wonder why the other specialists of this sector don at notice this. You must continue your writing. I am confident, you have a huge readers a base already!

  12. buy cialis说道:

    Normally I don at read post on blogs, however I wish to say that this write-up very pressured me to check out and do so! Your writing style has been amazed me. Thank you, very great post.

  13. we came across a cool internet site that you just may well appreciate. Take a search in the event you want

  14. website说道:

    Time period may be the a lot of special tool to, so might be the organic options. Internet looking is definitely simplest way to preserve moment.

  15. W88说道:

    Wow, superb blog layout! How long have you been blogging for? you make blogging look easy. The overall look of your website is great, as well as the content!

  16. You must participate in a contest for probably the greatest blogs online. I all advocate this internet site!

  17. Penn Clothing说道:

    Or maybe a representative speaking on behalf of the American University,

  18. Wow! This could be one particular of the most useful blogs We have ever arrive across on this subject. Actually Magnificent. I am also an expert in this topic so I can understand your effort.

  19. It as not that I want to duplicate your web page, but I really like the layout. Could you let me know which design are you using? Or was it tailor made?

  20. Woking taxi说道:

    This is really interesting, You are a very skilled blogger. I ave joined your feed and look forward to seeking more of your wonderful post. Also, I have shared your website in my social networks!

  21. Thanks a lot for the post.Really thank you! Really Great.

  22. Ep coc be tong说道:

    My brother recommended I might like this blog. He was entirely right. This post truly made my day. You can not imagine simply how much time I had spent for this information! Thanks!

  23. Hoa Tuoi Van Nam说道:

    looking for. Would you offer guest writers to write content available for you?

  24. That is a really good tip particularly to those fresh to the blogosphere. Brief but very precise information Thank you for sharing this one. A must read article!

  25. My brother suggested I might like this blog. He was entirely right. This post actually made my day. You cann at imagine simply how much time I had spent for this info! Thanks!

  26. more information说道:

    I think this is among the most significant info

  27. you are really a good webmaster, you have done a well job on this topic!

  28. Wohh exactly what I was looking for, regards for posting.

  29. PhD Green Card说道:

    Thanks for the blog post.Thanks Again. Awesome.

  30. Pretty nice post. I just stumbled upon your blog and wanted to say that I ave truly enjoyed browsing your blog posts. In any case I all be subscribing

  31. Nwokolo Collins说道:

    I was really confused, and this answered all my questions.

  32. uggs outlet sale cheap ugg boots ugg outlet online uggs uk uggs on sale uk uggs discount ugg boots goedkope uggs kopen

  33. like this说道:

    You are my aspiration, I possess few blogs and rarely run out from brand .

  34. Nwokolo说道:

    in the same niche. Your blog provided us beneficial information to work on.

  35. Muchos Gracias for your blog article. Great.

  36. site style is wonderful, the articles is really excellent :

  37. klm promo code说道:

    Laughter and tears are both responses to frustration and exhaustion. I myself prefer to laugh, since there is less cleaning up to do afterward.

  38. pretty valuable stuff, overall I believe this is well worth a bookmark, thanks

  39. ebay promo code说道:

    Very nice post. I just stumbled upon your blog and wanted to say that I ave truly enjoyed browsing your blog posts. After all I will be subscribing to your feed and I hope you write again very soon!

  40. This blog was how do I say it? Relevant!! Finally I ave found something which helped me. Thanks a lot.

  41. trash cans说道:

    whoah this blog is fantastic i love reading your articles. Keep up the good work! You know, many people are looking around for this information, you could help them greatly.

  42. mp3cutters说道:

    Wow, amazing blog layout! How long have you been blogging for? you made blogging look easy. The overall look of your website is great, as well as the content!

  43. Youtube说道:

    Superb Article My brother suggested I might like this web site. He was totally right. This post truly made my day. You can not imagine simply how much time I had spent for this info! Thanks!

  44. to learn more说道:

    Thanks for the blog article.Really looking forward to read more. Want more.

  45. You have remarked very interesting details ! ps decent web site.

  46. Thanks-a-mundo for the article post.Really thank you! Will read on

  47. We all speak a little about what you should speak about when is shows correspondence to simply because Maybe this has much more than one meaning.

  48. Im no pro, but I imagine you just crafted the best point. You undoubtedly know what youre talking about, and I can really get behind that. Thanks for being so upfront and so truthful.

  49. tell popeyes说道:

    Muchos Gracias for your blog article.Much thanks again. Awesome.

  50. thiet ke website说道:

    Incredible points. Great arguments. Keep up the great spirit.

  51. What as up, its good post regarding media print, we all understand media is a

  52. Right now it sounds like Expression Engine is the preferred blogging platform available right now. (from what I ave read) Is that what you are using on your blog?

  53. Major thanks for the article post. Awesome.

  54. 123moviesez说道:

    This blog is no doubt interesting and amusing. I have picked up helluva useful stuff out of this blog. I ad love to go back again soon. Thanks a lot!

  55. That is a really good tip particularly to those new to the blogosphere. Brief but very accurate information Appreciate your sharing this one. A must read article!

  56. Miracle Ndu说道:

    Packing Up For Storage аАТ’аЂа‹ Yourself Storage

  57. tips说道:

    Terrific work! This is the type of info that should be shared around the internet. Shame on the search engines for not positioning this post higher! Come on over and visit my site. Thanks =)

  58. mmbanifo说道:

    of course we of course we need to know our family history so that we can share it to our kids a

  59. you ave got a fantastic weblog right here! would you wish to make some invite posts on my weblog?

  60. Hosting说道:

    Recommeneded websites Here you all find some sites that we think you all appreciate, just click the links over

  61. you ave got a great weblog right here! would you prefer to make some invite posts on my weblog?

  62. Very good info. Lucky me I came across your website by chance (stumbleupon). I ave saved it for later!

  63. Polainas tejidas说道:

    Really enjoyed this post.Really looking forward to read more. Will read on

  64. Crochet bolero说道:

    Thanks so much for the article.Really thank you! Cool.

  65. Arctic Circle说道:

    Thanks so much for the post.Thanks Again. Much obliged.

  66. local说道:

    write about here. Again, awesome website!

  67. worth说道:

    Very good blog article.Much thanks again. Want more.

  68. this website说道:

    Wow, amazing blog layout! How long have you been blogging for? you made blogging look easy. The overall look of your website is great, let alone the content!

  69. Waec exam说道:

    It as very straightforward to find out any matter on net as compared to books, as I found this article at this web page.

  70. ark ps4 server说道:

    It as not that I want to copy your web-site, but I really like the style and design. Could you tell me which style are you using? Or was it especially designed?

  71. just curious if you get a lot of spam feedback?

  72. Wow, superb weblog format! How long have you ever been blogging for? you made running a blog look easy. The overall glance of your website is great, let alone the content!

  73. Real estate说道:

    Wow! In the end I got a webpage from where I know

  74. Thank you ever so for you article post.Thanks Again. Great.

  75. hotels说道:

    perform thаА аЂа• opposite аА аЂа•ffeаАааАТƒt.

  76. What web host are you using? Can I get your affiliate link to your host?

  77. tips de ventas说道:

    Im obliged for the blog post.Really thank you! Fantastic.

  78. Meshack说道:

    There is evidently a lot to identify about this. I assume you made various nice points in features also.

  79. This is really interesting, You are a very skilled blogger. I have joined your feed and look forward to seeking more of your fantastic post. Also, I have shared your site in my social networks!

  80. Your means of explaining all in this paragraph is genuinely fastidious,all can easily be real sentient of it, Gratitude a lot.

  81. accident lawyer说道:

    This is a great blog. Thank you for the very informative post.

  82. Really appreciate you sharing this article.Really looking forward to read more. Much obliged.

  83. There as definately a lot to know about this issue. I like all the points you have made.

  84. Looking around I like to surf around the online world, regularly I will go to Stumble Upon and follow thru

  85. Wow, this post is good, my sister is analyzing these kinds of things, so I am going to let know her.

  86. IaаАа’б‚Т€ТšаЂаŒаАа’б‚Т€ТžаБТžll complain that you have copied materials from another source

  87. know more说道:

    Thanks-a-mundo for the blog.Much thanks again. Great.

  88. just go to说道:

    Thanks for the help in this question, I too consider, that the easier, the better

  89. website. Reading this information So i am glad to convey that I have a very excellent uncanny feeling

  90. Thanks for the article.Really thank you! Cool.

  91. wow, awesome article.Really looking forward to read more. Fantastic.

  92. Muchos Gracias for your blog.Thanks Again. Awesome.

  93. Temple说道:

    Thanks for sharing your info. I truly appreciate your efforts and I will be waiting for your further post thank you once again.

  94. Outstanding post, I conceive website owners should larn a lot from this website its rattling user genial.

  95. very nice submit, i actually love this website, keep on it

  96. Ita??a?аАа’аАТ‚аЂ s actually a great and helpful piece of info. I am satisfied that you simply shared this helpful info with us. Please keep us informed like this. Thank you for sharing.

  97. Thanks for sharing, this is a fantastic article post.Really thank you! Fantastic.

  98. Thanks so much for the article post.Thanks Again. Will read on buy ventolin

  99. social说道:

    This is really interesting, You are a very skilled blogger. I ave joined your feed and look forward to seeking more of your great post. Also, I ave shared your web site in my social networks!

  100. Share now说道:

    Thanks-a-mundo for the blog post.Thanks Again. Really Great.

  101. just go to说道:

    we like to honor lots of other world-wide-web web sites around the internet, even if they aren

  102. I will likely be coming back to your blog for even more soon.

  103. Free ads说道:

    Well I definitely liked studying it. This post procured by you is very practical for good planning.

  104. pretty valuable stuff, overall I consider this is well worth a bookmark, thanks

  105. view说道:

    ought to take on a have a look at joining a word wide web based romantic relationship word wide web website.

  106. just go to说道:

    Simply wanna input that you have a very nice web site , I love the style and design it actually stands out.

  107. Very interesting points you have mentioned, thankyou for putting up.

  108. click here说道:

    There is clearly a bundle to identify about this. I believe you made some good points in features also.

  109. pretty beneficial stuff, overall I consider this is well worth a bookmark, thanks

  110. buy说道:

    It as really very complex in this full of activity life to listen news on TV, thus I simply use the web for that reason, and obtain the newest news.

  111. Thanks, I have been hunting for details about this subject for ages and yours is the best I ave found so far.

  112. Wohh precisely what I was looking for, appreciate it for posting.

  113. check here说道:

    This unique blog is really educating and factual. I have found a bunch of helpful advices out of this source. I ad love to come back again and again. Thanks a bunch!

  114. more info说道:

    Wow, marvelous blog format! How long have you ever been running a blog for? you made blogging glance easy. The overall look of your website is magnificent, let alone the content material!

  115. This internet internet page is genuinely a walk-through for all of the information you wanted about this and didn at know who to ask. Glimpse here, and you will surely discover it.

  116. my situs说道:

    I really liked your blog article.Really looking forward to read more. Really Great.

  117. lawyer miami fl说道:

    I wanted to thank you for this fantastic article, I certainly loved each and every small bit of it. I ave bookmarked your internet site to look at the latest stuff you post.

  118. Muchos Gracias for your blog article.Thanks Again. Cool.

  119. genuine parts说道:

    The Firefox updated tab comes up everytime i start firefox. What do i do to stop it?

  120. check here说道:

    very good submit, i certainly love this website, keep on it

  121. good morning说道:

    Loving the info on this website , you have done outstanding job on the articles.

  122. I think this is a real great blog post.Thanks Again. Fantastic.

  123. It is nearly not possible to find knowledgeable folks about this topic, but the truth is sound like do you realize what you are coping with! Thanks

Leave a Reply

Your email address will not be published.

You may use these HTML tags and attributes: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>