并发编程学习(1)——简介

并发简述

操作系统的出现使得计算机每次能运行多个运行程序,并且不同的程序都在单独的进程中运行;操作系统为各个独立执行的进程分配各种资源,包括内存、文件句柄以及安全证书等。如果需要的话,在不同的进程之间可以通过一些粗粒度的通信机制来交换数据,包括:套接字、信号处理器、共享内存、信号量以及文件等。

之所以在计算机中加入操作系统来实现多个程序的同时执行,主要是基于以下原因:

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

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

线程的优势

  • 发挥多处理器的强大能力
  • 建模的简单性,每个线程执行一种类型的任务,相比于一个线程执行多个任务在程序的设计上要简单很多
  • 异步事件的简化处理,服务器应用程序接受多个来自远程客户端的套接字连接请求时,如果为每个连接都分配其各自的线程并使用同步I/O,那么会降低这些程序的开发难度。
  • 响应更灵敏的用户界面

线程带来的风险

  • 安全性问题:线程安全性问题是很复杂的,在没有充足同步的情况下,多个线程中的操作执行顺序是不可预测的,甚至会产生奇怪的结果。
@NotThreadSafe
public class UnsafeSequence {
    private int value;
    public int getNext() {
        return value++;
    }
}

上述代码不安全在于,如果执行的时机不对,两个线程调用getNext时会得到相同的值。自增操作其实三个独立的操作,读、增、写。由于运行时可能将多个线程之间的操作交替执行,因此两个线程可能执行读操作,从而使它们得到相同的值,并将这个值加1,结果不同的线程调用返回了相同的数值,如下图所示:
image
将getNext修改为一个同步方法可以修复UnsafeSequence中的错误,如果没有同步,那么无论是编译器、硬件还是运行时,都可以随意安排操作的执行时间和顺序,比如编译器的重排序和对寄存器、处理器中的变量进行缓存。这些缓存的变量对其他线程来说是暂时甚至是永久的不可见的。

  • 活跃性问题:在开发并发代码时,一定要注意线程安全性时不可破坏的。安全性不仅对多线程程序很重要,对于单线程程序同样重要。安全性的含义是永远不发生糟糕的事情,活跃性则关注于另一个目标,某件正确的事情最终会发生。死锁、饥饿、活锁等都是常见的活跃性问题。

  • 性能问题:与活跃性问题密切相关的是性能问题。活跃性意味着某件正确的事情最终会发生,但不够好,因为我们希望正确的事情尽快发生。性能问题包括多个方面,比如:服务时间过长,响应不灵敏,吞吐率太低,资源消耗过高或者可伸缩性较低等。多线程环境中,频繁的上下文切换(context switch)会带来极大的开销;共享数据的同步机制会使得内存缓存区数据失效,抑制编译器优化,一起多了数据同步的一些操作,这些往往都会带来更大的性能开销。

线程应用广泛

  • JVM启动时,内部任务(垃圾回收、中介操作等)创建后台线程,并创建一个主线程来运行main方法
  • AWT和Swing的用户界面框架将创建线程来管理用户界面事件
  • Servlet 和RMI(Remote Method Invocation)都会创建线程池并调用线程中的方法
  • Timer 将创建线程来执行延迟任务

猜你喜欢

转载自blog.csdn.net/sinat_30333853/article/details/80246855
今日推荐