一、并发
并发程序是指在运行中有两个及以上的任务同时在处理,与之相关的概念并行,是指在运行中有两个及以上的任务同时执行,差别是在于处理和执行。在单核CUP中两个及以上任务的处理方式是让它们交替的进入CUP执行,这种对执行的处理方式就是并发。并行只能发生在多核CUP中,每个CUP核心拿到一个任务同时执行,并行是并发的一个子集
与串行程序相比并发编程的优点:
1):提高硬件资源的利用率(特别是IO资源),提高系统的响应速度、减少客户端等待、增加系统吞吐量
2):解决特定领域的问题,比如GUI系统,WEB服务器等。
如何实现并发:
1):进程并发,PHP(pcntl_fork)、PYTHON(multiprocessing)中很常见基于多进程并发。
2):线程并发,JAVA、C#以线程作为执行体进行并发。
3):协程并发,GOLANG、SCALA中基于协程的并发,虽然协程是在线程中进程调度的,但是协程有自己的寄存器和栈,调度切换完全在用户态进行。可以简单的理解协程与线程间是N:1的关系,所以协程避免了线程创建和上下文切换的系统开销,理论上可以支持更大的并发量。
二、线程实现
在操作系统中线程是包含在进程中的消费资源较少、运行迅速的最小执行单元,根据操作系统内核是否对线程可感知,把线程分为内核线程和用户线程。编程语言的线程实现都是基于这两种线程之上。
1):基于内核线程(Kernel-Level Thread,KLT)
使用内核线程的一种高级接口--轻量级进程(Light Weight Process,LWP)实现的线程(通常意义上的线程),它与内核线程是一对一的关系。线程的创建,初始化,同步,切换(用户态、内核态)都需要内核调度器(Scheduler)进行调度,消耗内核资源,每一个轻量级进程都需要一个内核线程对应,所以这线程能创建的数量是也是有限的。
2):基于用户线程
建立在用户空间的上的线程,内核对此无感知。线程的创建、调度在用户态完成,不需要系统内核支援。由于没有系统内核的支援,所有的线程操作都需要用户程序自己处理。线程的创建、切换和调度都是需要考虑的问题,而且由于操作系统只把处理器资源分配到进程,如“阻塞如何处理”,“多处理器系统中如何将线程映射到其它处理器上”这类问题解决起来将会异常困难,甚至不可能完成。
3):基于用户线程和内核线程混合
即使用内核线程(轻量级进程),也使用用户线程。用户线程依然建立在用户空间上,线程的创建、调度、处理器映射能够得到内核线程的支援,实现简单。用户线程与轻量级进程(内核线程)是N:M的对应关系,可以支持大规模的并发。
三、线程通信
线程间通过协作才能合力完成一个任务,协作的基础就是通信。常用的线程间通信的方式有两种。
1):共享内存
设置一个共享变量,多个线程都可以对共享变量进行操作,共享变量就行通信的中介。共享内存通信方式实现简单,数据的共享还使得线程间的通信不需要数据的传送,直接对内存进行访问,加快了程序的执行效率。
但是多个线程操作同一个共享变量,势必会造成“数据争用”。竞争条件下必须让共享变量进入临界区进行保护,否则会产生数据不一致。
共享内存通信过程是隐式的,但是同步操作是显示的,开发者必须自行判断何时进行线程互斥,何时对变量操作加锁处理。
2):消息传递
线程间通过传递信息进行通信。这种通信模型实现起来复杂,线程之间没有公共状态,线程之间必须通过明确的发送消息来显式进行通信,消息传递会产生系统开销,控制消息的有效性、先后关系,接到消如何通知线程处理,大数据量频繁的传递如果控制效率等等问题的处理都要销毁系统资源。
但是由于消息的发送必须在接收之前,不存在数据不一致的问题。
共享内存通信过程是显式的,但是同步是隐式进行的,开发者不需要进行临界区判断与线程互斥操作。
四、JAVA并发
JAVA1.2之前的线程是基于用户线程实现的,之后的版本都是采用轻量级进程(内核线程)实现的。JVM中采用NPTL(Native POSIX Thread Library)机制,JVM本身不创建线程,使用操作系统提供的接口进行线程的调度和管理,从Thread类源码中可以看到线程启动,中断等方法都是native的。
每一个JAVA线程都对应者一个内核线程,所以线程的创建、调度、上下文切换都需要系统内核的支持,会消耗系统资源。
JAVA线程间通信是通过共享内存实现的,锁在线程并发中有着举足轻重地位,使用JAVA多线程时需要非常小心的处理同步问题。
“线程与锁”模型是JAVA语言的并发模型。这也是大多数语言都支持的模型,由于其基本接近硬件本身运行的模式,可以解决的问题领域很多有着很高的运行效率,一直都是并发编程的首选。缺点是使用这样模型需要开发者时刻警惕线程安全问题,处理复杂的线程协作问题,关注计算资源的开销问题。
五:小结
JAVA并发编程需要面对两个问题:
1):资源消耗问题,包括线程的创建、上下文切换对资源的消耗,锁的互斥操作对资源的消耗,常用的解决方法有池化资源,根据计算类型保有适量线程,锁优化策略等。
2):线程安全问题,要想让并发程序正确的执行,需要解决原子性,可见性、有序性的问题,常用的保障线程安全的方法有加锁、不共享状态、不可变对象。
JAVA并发编程的支持:
1):JAVA内存模型(JMM Java Memory Model) ,通过final、volatile、synchronized的内存语义,happens-before原则,解决多线程中原子性,可见性、有序性问题。
2):JAVA并发编程包(J.U.C java.util.concurrent),大师之作,提供了更高效的锁、更优化的并发数据结构、更方便的同步工具,更实用的线程池为高效的并发提供了有力的支持。
码字不易,转载请保留原文连接https://www.jianshu.com/p/067702fbf256