Java并发编程实战--简介
导读:在Java编程中,编写正确的并发编程本身就是一件难事,与串行程序相比,并发程序很容易出错。在Java中如何编写Java并行的编程呢--线程。线程可以使复杂的程序变得简单,简化复杂程序的系统开发。处理器的增多,高效的使用并发变得尤为重要。
一、简介
1.1并发简史
在最初的计算机中,计算机只有一个主线程(原因是没有操作系统),运行所有的程序都是这样,在这样的环境之下,系统运行不仅受到了限制,而且对于计算机本事来说,更是一种资源的浪费。
操作系统的出现,使现在的电脑开始拥有了多线程,多个程序之间相互独立。操作系统为多个正在执行的进程进行独立分配,,其中包括内存、文件句柄、安全证书。如果需要的话,不同的进程之间,会有一些粗粒度的通信机制来交换数据,如:套接字、信号处理器、共享内存、信号量、文件。
计算机中加入操作系统实现多个程序同时执行,目的:
资源利用率--当程序同时进行两个时,必须进行一个等待另外一个,多线程无疑解决了这个问题。
公平性--不同的用户和程序对于计算机上的资源拥有这相同的使用权利,一种高效的运行方式,是通过粗粒度的时间分片使这些用户和程序共享计算机资源,不是一个程序从开始到结束,然后再启动下一个程序。
便利性--编写多个程序,在执行一个任务的时候可以互相通信。
线程,也被成为轻量级进程,如果没有明确的协同机制,那么线程,将彼此独立执行。由于同一个进程中,所有的线程都将共享同一个进程的内存空间,因此这些线程都将在同一个堆上分配对象。这就需要实现一种比进程间共享数据粒度数据更细的 数据共享机制。如果没有明确的同步机制协同对数据的访问,那么当一个线程正在使用一个变量的时候,另一个线程可能同时访问这个变量,这就是一个非常有必要解决的问题。
1.2线程的优势
A. 有效的降低程序的开发和维护成本,提高复杂应用程序性能。
B. 线程可以将大部分的异步工作流转换成串行工作流,模拟人类的工作方式和交互方式。
C. 降低代码复杂度。
D. 在服务器应用程序中,提升资源利用率以及系统吞吐量,简化JVM实现--因为JVM实现用到了专门的线程。
1.2.1 发挥多处理器的强大性能
由于基本的调度单位是线程,如果程序中只有一个线程,则最多在一个处理器上运行。在双处理器系统上,单线程的程序只能使用一半的系统资源,那么若是在一百核处理器,99%资源将无法使用。程序设计正确,就可以节省资源,提高利用率。
同时,多线程可以同时完成多个状态例如同步I/O流操作时,另外一个可以继续运行。
1.2.2建模的简单性
如果为模型中每一个任务都分配一个线程,那么可能形成一种串行执行的假象。通过使用线程,可以将复杂并且异步的工作流进一步分解为简单而且同步的工作流,每一个工作流在一个单独的线程中运行,并在特定的同步位置交换。
我们可以使用一些特定的框架来实现这一些细节,例如:servlet和RMI(远程方法调用),他们可以实现细节如线程创建、请求管理、负载均衡,并在正确的时刻将请求分配给正确的应用组件。编写servlet的编程人员不需要知道同一时刻有多少请求被处理,也不需要了解套接字的输入流或输出流是否被阻塞。当调用servlet的service方法请求Web时可以以同步方式来处理这个请求,就好像是一个单线程程序。
1.2.3异步事件的简化处理
服务器在接受多个远程客户端套接字请求时,为每个链接都分配了其各自线程,并且使用同步I/O,则降低这类程序的开发难度。如果程序对套接字的执行读操作还没有来到,读操作则一直被阻塞,直到数据到达。在单线程中,这不仅意味着处理请求的过程中将停顿,而且还意味着在线程被阻塞期间,所有请求的处理将被停顿。处理这一类问题,在单线程中必须使用非阻塞I/O,而非阻塞I/O的复杂度远远高于阻塞I/O,容易出错。
现在的操作系统,线程的数量已经得到很大的提升,在很多的客户端,每一个分配一个线程,也是可行的。
1.2.4响应更灵敏的用户界面
在现在的GUI框架中例如AWT和Swing框架,采用了事件分发线程。当某个用户界面事件发生时,在事件线程中,将调用应用程序的事件处理器。由于大多数的GUI框架都是单线程子系统,因此目前为止,任然存在主事件循环,但现在在GUI 控制下并在自己的线程中运行,而不是在应用程序的控制下。
1.3线程带来的风险
Java对多线程的支持其实是一把双刃剑,Java提供了相应的语言和库,以及明确的跨平台内存模型--该模型实现了Java来发“编写一次随处运行”并发应用模型,简化了开发,提高了编程人员的要求。
1.3.1 安全性问题
首先我们看一段代码:
在图中可以看到,如果两个线程同时访问方法getNext,就会得到相同的值。
解决办法:那么我们就可以将这个方法改为一个同步的方法。
如果没有同步,那么无论是编辑器,硬件还是运行时,都可以随意安排操作的执行时间和顺序,例如寄存器或处理器变量进行缓存,这些缓存的变量相对于其他线程来说暂时(甚至)永久是不可见的。虽然这些技术有助于实现跟优秀的性能,并且也是只得采用的方法,但也会带来负担,需要知道哪一些会被多个线程共享。不破坏线程安全性。
1.3.2线程活泼性
例如线程A等待线程B释放其特有资源,然而线程B 却一直不释放,那么A将永远等待下去。在以后的文章中会解释这样的事情如何避免。