深入理解高并发编程

并发简史

在早期的计算机中不包含操作系统,它们从头到尾只可以运行一个程序,操作系统的出现使得计算机每次能运行多个程序,并且不同的程序都在单独的进程中运行,之所以在计算机中加入操作系统来实现多个程序的同时执行,主要是基于下面几个原因:

  • 资源利用率

在某些情况下,程序必须等待某个外部操作执行完成,而等待时程序无法执行其他任何工作。因此,如果在等待同时可以运行另一个程序,那么无疑将提高资源的利用率。

  • 公平性

不同的用户和程序对于计算机上的资源有着同等的使用权。一种高效的运行方式是通过时间分片使用户和程序可以共享计算机资源,而不是一个程序从头运行到底,再启动下一个程序。

  • 便利性

通常来说,在计算多个任务时,应该编写多个程序,每个程序执行一个任务并在必要时相互通信,这比只编写一个程序来计算所有任务更容易实现。

线程优势

如果使用得当,线程可以有效降低程序的开发和维护成本,同时提升复杂应用程序的性能。在服务器应用程序中,可以提升资源利用率以及系统吞吐率,线程还可以简化JVM的实现,垃圾收集器通常在一个或多个专门的线程中运行,在许多重要的Java应用中,都在一定程度生用到了线程。

发挥多处理器的强大能力

随着现在多处理器的普及,我们的服务器目前多数都是多个核心的,由于CPU的基本调度单位是线程,因此如果在程序中只有一个线程,那么最多同时只能在一个处理器上运行。在双处理器系统上,单线程的程序只能使用一半的CPU资源,而在拥有100个处理器的系统上,将有99%的资源无法使用。另一方面,多线程程序可同时在多个处理器上执行。如果设计正确,多线程程序可以通过提高处理器资源的利用率来提升系统吞吐率。

异步事件的简化处理

服务器应用程序在接受来自多个远程客户端的连接请求时,如果为每个连接都分配各自的线程并且使用同步I/O,那么就会降低这类程序的开发难度。

在单线程应用中,如果在处理某一请求过程中出现阻塞,意味着在这个线程被阻塞的期间,对所有请求的处理都将停顿。为了避免这个问题,单线程服务器应用程序必须使用非阻塞I/O,这种I/O的复杂性要远远高于同步I/O,并且很容易出错,如果每个请求都拥有自己的处理线程,那么在处理某个请求时发生的阻塞将不会影响其他请求的处理。

线程带来的风险

Java对线程的支持其实是一把双刃剑。虽然Java提供了相应的语言和库,以及一种明确的跨平台内存模型,这些工具简化的了并发应用程序的开发,但同时也挺高了对开发人员的技术要求,因为在更多的程序中会使用线程。

线程安全问题

线程安全性非常复杂,在没有充分同步的情况下,多个线程中的操作执行顺序是不可预测的,甚至可能会出现奇怪的结果。

活跃性问题

多线程会导致一些在单线程程序中不会出现的问题,例如活跃性问题。当某个操作无法继续执行下去时,就会发生活跃性问题。在串行程序中,活跃性问题的形式之一就是无意中造成的无限循环,从而使循环之后的代码无法得到执行。还有一些其他类型的问题,例如:如果线程A在等待线程B释放其持有的资源,而线程B永远都不释放该资源,那么A就会永远的等待下去。这就是通常所说的“死锁”。

性能问题

在设计良好的并发应用程序中,线程能提升程序的性能,但无论如何,线程总会带来某种程度的运行时开销。再多线程程序中,当线程调度器临时挂起活跃线程并转而运行另一个线程时,就会频繁地出现上下文切换操作,这种操作将带来极大的开销:保存和恢复执行上下文,丢失局部性,并且CPU时间将更多地花在线程调度而不是线程运行上。当线程共享数据时,必须使用同步机制,而这些机制往往会抑制某些编译器优化,使内存缓存区中的数据无效,以及增加共享内存总线的同步流量。所有这些因素都将带来额外的性能开销。

线程无处不在

即使在程序中没有显示地创建线程,但在框架中仍可能会创建线程,因此在这些线程中调用的代码同样必须是线程安全的。 每个Java应用程序都会使用线程。当JVM启动时,它将为JVM的内部任务,例如垃圾收集,终结操作等创建后台线程,并创建一个主线程来运行main方法。

并发编程中的重要概念

同步VS异步

同步和异步通常用来形容一次方法调用。同步方法调用一开始,调用者必须等待被调用的方法结束后,调用者后面的代码才能执行。而异步调用,指的是,调用者不用管被调用方法是否完成,都会继续执行后面的代码,当被调用的方法完成后会通知调用者。比如,在超市购物,如果一件物品没了,你得等仓库人员跟你调货,直到仓库人员给你把货物

你可能感兴趣的:(jvm,java,开发语言)