Java重温-并发11_01

2017-08-07  本文已影响17人  46fdc45388ac

并发章节内容在书本中的章节位置本来没有这么靠前的,考虑到最近在重写永州项目,刚好有一些涉及到并发的内容,所以把该章节内容提前了。

之前对于并发只是做到了概念上的理解,并没有太多实践经验。而且概念里,把并发是当做一个很复杂的模块对待,对于几种经典的容易碰到的并发场景,只是思考过如何应对却并没有实践过很好的解决方案。几个之前遇到过的场景如下:

1.HMFY项目时遇到了N多玩家抢占桌子打比赛的场景。当时的设计是把桌子设定成map的value值,初始化成唯一资源,也就是N多玩家竞争资源的情景。当时(三四年前)尝试过很多“想象”的解决方案,最终也没有很好的解决问题。现在回想,一个简单的解决方案就是把资源设置成线程安全的,然后把竞争方法设置成synchronized的就可以应对用户量不是太多时候的资源竞争。

2.HMFY项目时,比赛计算的问题。两个玩家坐上桌子后需要开始计算比赛内容;联赛定时器开始时,需要同时计算N多比赛内容。当时也采用了线城池相关的内容设计,败在没有做数据库访问的控制,性能很差。现在回想,比较好的策略是,做数据库的访问控制,线程池来计算实时比赛,通过内存数据来切换上下文。同时,设计一套规则来存储比赛过程,对于非实时的联赛,采取预先计算好比赛过程,联赛开始时以录像播放的方式来展现会比较好。如果是实时比赛呢?设想的解决方案是设计好专门的比赛服务器,用这种横向拓展的方式来避免突增的性能瓶颈。

3.永州项目中,玩家和桌子其实是静态数据,而每个玩家又会并发的进行一些游戏指令。这里的幸运之处在于打牌的场景并发问题还不是特别严重,毕竟最多只是4人,且消息基本都是一轮一轮来的。当前沿用的是旧的代码逻辑,也就是静态数据使用线程安全的容器,而指令操作不做并发处理。前几天曾尝试对于指令操作做一个synchronized的同步控制,结果发现反而引发了同步问题。具体表现为a玩家点碰操作的“同时”b玩家点过操作,那么最后结果会是b的过发生在a的碰之后,是等a的碰完毕后执行的,所以过操作跳过了操作优先级的控制,广播了一个莫名其妙的过导致前台错乱。推想引发的原因是synchronized的控制导致过操作在碰操作之后执行,所以会出现优先级错乱。而没有同步控制时,由于碰的计算需要时间,过会在第一时间完成优先级的检验而被忽视掉,所以不容易出现问题。对于并发,也和YX交流了一次,发现大家对于并发的理解层次其实差不太多,都没有深入到内部去。或许正如书的作者传达的意思那样,使用简单的操作指令来处理并发问题其实很简单,但是要真正能手写最原生的并发控制则需要对于底层的深入理解,即便如作者那么牛的人,也没能达到那个程度。

闲话不说,先简述一下书中章节传递出的精华点。这里需要说明的是,此次的解读并没有深入同步追踪书中提供的几个经典的场景范例,那几个场景范例是本章节的真正精华所在。后续会利用周末时间来模拟实践下,零散时间段就不来做这种需要高脑力要求连续性的活了。

1.并发“具有可论证的确定性,但是实际上具有不可确定性”

2.用并发解决的问题大体上可以分成“速度”和“设计可管理性”两种

3.并发通常是提高运行在单处理器上的程序的性能。这里是因为阻塞的存在,如果没有并发,顺序执行将会一直陷入线程阻塞。从性能的角度来看,如果没有任务会阻塞,那么在单处理器的机器上使用并发将没有任何意义。

4.单处理器系统中的性能提高的常见示例是事件驱动的编程。

5.实现并发最直接的方式是在操作系统级别使用进程。

6.Java采取了更加传统的方式,在顺序型语言的基础上提供对线程的支持。线程机制是在由执行程序表示的单一进程中创建任务。这种方式产生的一个好处就是操作系统的透明性。

7.在单CPU机器上使用多任务的程序在任意时刻仍旧只在执行一项工作,因此从理论上将,肯定可以不用任何任务而编写出相同的程序。Java的线程机制是抢占式的,这表示调度机制会周期性的中端线程。

8.并发编程使我们可以将程序划分成多个分离的,独立运行的任务。使用多线程机制,这些独立任务中每一个都将由执行线程来驱动。一个线程就是程序中的一个单一的顺序控制流,因此,单个进程可以拥有多个并发执行的任务。

9.实现Runnable接口编写run方法可以定义任务。而Thread.yield()是对线程调度器的建议,让出执行的优先级。让Runnable定义的任务执行起来的方法是,将他交给一个Thread,然后通过调度start方法开始执行。

10.Java SE5的concurrent包中的Executor可以用来管理Thread对象,从而简化并发编程。

11.CachedThreadPool、FixedThreadPool、SingleThreadExecutor为几个常用的线程池

12.Runnable不会有返回值,Callable方法则有返回值

13.线程中的顺序行为依赖于底层实现,编程时并不能把这当做可靠依赖。

14.volatile参数可以努力确保不进行任何编译器优化

15.后台线程(daemon)的特性是,当所有非后台线程结束时,程序终止也同时杀死所有的后台线程。当有任何后台线程还在运行时,程序就不会终止。(也就是非主线程的意思)

16.优选Executor而非Thread来解决线程问题。(因为前者更具备可靠性,后者为了保证执行顺序可能需要引入一些不稳定的写法)

17.jion方法,某个线程a在另外一个线程t上调用t.join()时,a会被挂起直至t结束才恢复(t.isAlive()返回false时)

18.线程组不是一个优秀的设计,最好忽略

19.使用Executor可以捕获线程中的异常。它的实现方式是允许使用Thread.UncaughtExceptionHandler接口来定义一个异常处理器。

20.经典的生产者和消费者模型来实践共享受限资源问题

21.synchronized关键字是java提供的解决资源冲突问题的内置方案。可以配合锁一起使用

22.原子性和易变性。(避免依靠自己的一己之力来弄这个东东)

23.原子类

24.临界区(基于锁)

上一篇下一篇

猜你喜欢

热点阅读