Java J.U.C并发包(4) —— 并发简介

这里会记录 《Java Concurrency in Practice》(java并发变成实战)的所有知识点哟~

1.1 并发简史

操作系统的出现使得计算机每次能运行多个程序,并且不同的程序都在单独的进程中运行。之所以在计算机中假如操作系统来实现多个程序的同时执行,主要是基于以下原因:

  • 资源利用率。在某些情况下,程序必须等待某个外部操作执行完成,例如输入操作或者输出操作等,而在等待时程序无法执行其他任何工作。因此,如果在等待的同时可以运行另一个程序,那么无疑将提高资源的利用率。
  • 公平性。不同的用户和程序对计算机上的资源有着同等的使用权。一种高效的运行方式是通过粗粒度的时间分片使这些用户和程序能共享计算机资源,而不是由一个程序从头运行到尾,然后再启动下一个程序。
  • 便利性。通常来说,在计算机多个任务时,应该编写多个程序,每个程序执行一个任务并在必要时进行相互通信,这比只编写一个程序来计算所有任务更加容易实现。

线程也被称为轻量级进程。在大多数现代操作系统中,都是以线程为基本的调度单位,而不是进程。如果没有明确的协同机制,那么线程将被彼此独立执行。由于同一个进程中的所有线程都讲共享进程的内存地址空间,因此这些线程都能访问相同的变量并在同一个堆上分配对象,这就需要实现一种比在进程间共享数据粒度更细的数据共享机制。如果没有明确的同步机制来协同对共享数据的访问。那么当一个线程正在使用某个变量时,另一个线程可能同时访问这个变量,这将造成线程安全问题。

线程的优势

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

如果某个应用程序对套接字执行读操作而此时还没有数据到来,那么这个读操作将一直阻塞,直到有数据到达。在单线程应用程序中,这不仅意味着在处理请求的过程中将会停顿,而且还意味着在这个线程被阻塞期间,对所有请求的处理都讲停顿。为了避免这个问题,单线程服务器应用程序必须使用非阻塞I/O,这种I/O复杂性要远高于同步I/O,并且容易出错。然而,如果每个请求都拥有自己的处理线程,那么在处理某个请求时发生的阻塞将不会影响其他请求的处理。

线程带来的危害

1. 安全性问题

示例:非线程安全的数值序列生成器

public class UnsafeSequence {
    private int value;

    public int getNext() {
        return value;
    }

}

当有两个线程想要执行该getNext方法时,或出现安全性问题

这里写图片描述

上述示例是一个常见的并发安全问题,称为竞态条件(Race Condition)

在多线程环境下,getValue()是否会返回唯一的值,要取决于运行时对线程中操作的交替执行方式,这并不是我们希望看到的。因为多个线程要共享相同的内存地址空间,在前面的博文中讲到共享主内存,因此他们可能会访问或修改其他线程正在使用的变量。要使得多线程程序的行为可以预测,必须对共享变量的访问操作进行协同,这样才不会在线程之间发生彼此的干扰。

2. 活跃性问题

  • 安全性的含义:“永远不发生糟糕的事情”
  • 活跃性的含义:“某件正确的事情最终会发生”。当某个操作无法继续执行下去时,就会发生活跃性问题。

活跃性问题举例:

在串行程序中:无意中造成的无限循环,从而使得循环外面的代码无法得到执行
在多线程中:线程A在等待线程B释放其持有的资源,而线程B永远都不释放该自愿,那么A就会永远等待下去。

3. 性能问题

性能问题包括多个方面,如:服务时间过长,响应不灵敏,吞吐率过低,资源消耗过高,或者可伸缩性较低等。

在设计良好的并发应用程序中,线程能够提升程序的性能,但是无论如何,线程总会带来某种程序的运行时开销。

  • 当线程调度器临时挂起活跃线程并转而运行另一个线程时,就会频繁出现上下文切换操作,这种操作将带来极大的开销;
  • 保存和恢复执行上下文,丢失局部性,并且CPU时间将更多地花在线程调度而不是线程运行上;
  • 当线程共享数据时,必须使用同步机制,而这些机制往往会抑制某些编译器优化,使得内存缓存区中的数据无效,以及增加共享内存总线的同步流量。

常用的线程安全模块

Servlet和JavaServer Page(JSP)

Servlet框架用于部署网页应用程序以及分发来自HTTP客户端的请求。到达服务器的请求可能会通过一个过滤器链被分发到正确的Servlet或JSP。每个Servlet都表示一个程序逻辑组件,在高吞吐率的网站中,多个客户端可能同时请求同一个Servlet的服务。在Servlet规范中,Servlet同样需要满足被多个线程同时调用,换句话说,Servlet需要是线程安全的。

远程方法调用(Remote Method Invocation IMI)

RMI使得代码能够调用在其他JVM中运行的对象。当通过RMI调用某个远程方法时,传递给方法的参数必须被打包到一个字节流中,通过网络传输给远程JVM,然后由远程JVM拆包并传递给远程方法。
当RMI代码调用远程对象时,这个调用将在哪个线程中执行?你并不知道,但是肯定不会在你创建的线程中,而是将在一个由RMI管理的线程中调用对象。RMI会创建多少个线程?同一个远程对象上的同一个远程方法会不会在多个RMI线程中被同时调用?答案:会,但在javadoc中没有清楚地指出这一点,如果想要知道具体的原因,需要阅读RMI规范

参考资料

《java并发编程实战》

这里写图片描述

猜你喜欢

转载自blog.csdn.net/xiaojie_570/article/details/80051861