JAVA并发一:Java 并发编程简介


简介:

​        操作系统的出现使得计算机每次能运行多个程序,并且不同的程序都在单独的进程中运行:操作系统为各个独立执行的进程分配好资源,包括内存,文件句柄以及安全证书等,在不同的进程之间可以通过一些粗粒度的通信机制来交换数据,包括:套接字、信号处理器、共享内存、信号量以及文件等;一种高效的运行方式是通过粗粒度的时间分片(Time Slicing)使这些用户和程序能共享计算机资源,而不是由一个程序从头到尾运行,在计算多个任务时,每个程序执行一个任务并在必要时相互通信。

​        线程被称为轻量级进程,在大多数现代操作系统中,以线程为基本的调度单位,而不是进程,若没有明确的协同机制,那么线程将被彼此独立执行。线程会共享进程范围内的资源,例如内存句柄和文件句柄,但每个线程都有各自的程序计数器、栈以及局部变量等。由于同一个进程中的所有线程都将共享进程内存地址空间,因此这些线程都能访问系统的变量并在同一个堆上分配对象,这就需要实现一种比在进程间共享数据力度更细的数据共享机制,如果没有明确的同步机制来协同对共享数据的访问,那么当一个线程正在使用某个变量时,另一个线程可能同时访问这个变量,将造成不可预测的后果。

优势:

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

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

风险:

​ 1、安全性问题:

        由于多个线程要共享相同的内存地址空间,并且是并发运行,因此它们可能会访问或修改其他线程正在使用的变量,导致结果是没有同步情况下,线程会由于无法预料的数据变化而发生错误。

​         如果没有同步,无论是编译器、硬件还是运行时,都可以随意安排操作的执行时间和顺序,例如对寄存器或者处理器中的变量进行缓存,而这些被缓存的变量对于其他线程来说是暂时(永久)不可见的。

@NotThreadSafe
public class UnsafeSequence{
  private int value;
    public int getNext(){
        return value++;
    }
}

        事实上 value++ 包含三个独立的操作:读取 value,value+1,将计算结果写入value;

        由于运行时多个线程之间的操作交替执行,因此可能同时执行读操作,从而得到相同值并将其加1,结果是,在不同线程的调用中返回相同的数值。

2、活跃性问题:

​         活跃性意味着某件正确的事情最终会发生,在串行程序中,即无限循环,从而使循环之后的代码无法得到执行,例如线程A在等待线程B释放其持有的资源,而线程B永远都不释放该资源,那么A就会永久地等待。

​ 3、性能问题:

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

 

场景解析:

         当 JVM 启动时,将为 JVM 的内部任务(例,垃圾回收,终结操作等)创建后台线程,并创建一个主线程来运行 main 方法。

         AWT 和 Swing:AWT 和 Swing 的用户界面框架创建线程来管理用户界面事件。当用户出发某个 UI 动作时,在事件线程中就会有一个事件处理器被调用以执行用户请求的操作,如果事件处理器需要访问由其他线程同时访问的应用程序状态(例如编辑某个文档),那么该事件处理器,以及访问这个状态的所有其他代码都必须采用一种线程安全方式来访问该状态。

         Timer:Timer 将创建线程来执行延迟任务。TimerTask 将在 Timer 管理的线程中执行,若 Task 访问了其他线程访问的数据,那么 Task 需要以线程安全的方式来访问数据,其他类也必须采用线程安全的方式来访问该数据,实现该目标的最简单方式是将线程安全封装在共享对象内部确保 Task 访问的对象本身是线程安全。

        Servlet 和 JavaServer Page:到达服务器的请求会通过一个过滤器链被分发到正确的Servlet或 JSP,当多个客户端同时请求同一个Servlet服务时或者Servlet被多个线程同时调用,则必须确保线程安全;即使确保只有一个线程调用某个Servlet,该Servlet可能会访问与其他多个Servlet共享的信息,例如保存在ServletContext中或者会话HttpSession中的对象,也必须正确协同对这些对象的访问实现线程安全。

        远程方法调用(RMI,RMI能够调用在其他 JVM 中运行的对象):当调用远程对象时,将在一个由 RMI 管理的线程中调用该对象,同一个远程对象上的同一个远程方法会在多个 RMI 线程中被同时调用,因此必须正确协同在多个对象中共享的状态,以及对远程对象本身状态的访问(由于同一个对象可能会在多个线程中被同时访问),必须确保其自身的线程安全性。

 

更多内容,关注微信公众号,每周一更~

评论:

1 条评论,访客:1 条,站长:0 条
  1. 760663542zn
    760663542zn发布于: 

    优点中提到“单线程服务器应用程序必须使用非阻塞I/O,而其复杂性要远远高于同步I/O”,我不太理解非阻塞I/O和同步I/O之间的关系,但是我在别的博文中曾看到过,其实非阻塞I/O也是同步I/O,两者并不是互斥的关系

发表评论