多线程高并发相关基础概念

程序、进程、线程

程序:
         程序是静态的概念,windows下通常指exe文件,程序也可以说是编译后的文件(.jar、.war等);操作系统打开一共应用程序,实际上是新打开了一个进程。

进程:
         进程是动态的概念,是程序运行时的状态。一个程序可以有多个进程,进程之间互不影响彼此隔离。

线程:
         线程是进程里的一个基本任务,每个线程都有自己的功能,线程是CPU分配与调度的基本单位(即:CPU分配调度是面向于线程的;线程抢占CPU时间片)。

 

并发与并行

一台计算机可能有多个CPU。

并发:
        多个线程抢占同一个CPU的时间片。

并行:
         不同CPU同时执行不同的线程。

而实际上,并行的同时往往带着并发:

注:线程的调度单位是线程。

注:单核(单CPU)多线程时,只存在并发;多核(多CPU)多线程时,并发与并行往往同时存在。

注:一般的,我们所说的并发,指的是并发与并行。

注:多线程抢占CPU时间片时,优先级越高的线程,抢占到CPU时间片的概率就越大。可以通过xxx.setPriority(x)来设
     置线程xxx的优先级x,其中x的范围是[1,10]之间的整数。1,优先级最低,10,优先级最高。优先级越高,抢
     占到CPU时间片的可能性越大(注意:优先级高只是抢占到的可能性高而已,优先级高的也并不一定能抢占到)

 

同步与异步

异步、同步可参考AJAX原理。给出两张图一看便知。

同步:

异步:

 

临界区

临界区用来表示一种公共资源与共享数据,可以被多个线程使用。

注:同一时间只能有一个线程访问临界区,其余线程必须等待。

 

死锁、饥饿、活锁

死锁:
         多个线程彼此竞争相同的资源,而又不愿彼此礼让,进而出现死锁。

注:死锁是在多线程情况下最严重的问题,在多线程对公共资源(文件、数据等)进行操作时,彼此不释放自己的资源,而
      试图去操作其他线程的资源,而形成交叉引用,就会产生死锁。

说明死锁(示例):
       线程A需要对象x和对象y的锁,而线程B需要对象y和对象x的锁,假设线程A在获得对象x的锁时线程B获得y的锁;此时,线程A和B就同时进入的阻塞状态,等待对方线程释放自己需要对象的锁,即:此时,线程A和线程B进入死锁状态。

解决(避免)死锁的建议:

      ◎尽量减少公共资源的引用,用完马上释放。

      ◎减少synchronized等的使用,采用副本的方式代替。

      ◎引用公共资源时,多个线程都按照用一个顺序进行引用。

饥饿:
        某些线程在与其他线程进行CPU时间片抢占时,(由于线程优先级等问题)总是没有抢到CPU时间片,那么就会使这些线程处于饥饿状态。

活锁:
       与死锁相反,多个线程彼此过度礼让,都不去抢占资源,进而形成线程阻塞。

严重程度(由高到低): 死锁 -> 饥饿 -> 活锁

 

线程安全

        在拥有共享数据的多条线程并发执行的程序中,线程安全的代码会通过同步机制保证各个线程都可以正常且正确的执行,不会出现数据污染等意外情况。

线程安全的三大特性:

        原子性:一个操作要么全部执行,且执行过程中不会被任何因素打断;要么都不执行。

        可见性:当多个线程访问同一个变量时,一个线程修改了这个变量的值,其余线程能够立即“看到”修改后的值。

        有序性:如果在本线程内观察,所有的操作都是有序的;如果在一个线程观察另一个线程,所有操作都是无序的。

注:这里对有序性进行一下说明,假设现有(按语句从上到下的顺序)语句1,语句2,语句3,语句4;其中语句3的执行依赖
     于语句2;那么CPU进行重排序后,可能会排为2/3/1/4或2/3/4/1/或1/2/3/4或4/2/3/1或1/4/2/3或4/1/2/3;但是绝不可
     能把语句3排在语句2前,因为语句3的执行以来于语句2。在Java中我们可以使用关键字volatile来防止CPU重排序,
     其实我们在编写代码时并不需要主动的使用该关键字,因为JVM默认使用了该关键字来防止CPU重排序。

 

Java内存模型

注:JVM Memory是在我们真实的内存中开辟出的一块儿空间。

注:堆是相对稳定的、静态的存储。

注:上图只是简单说明(没列出程序计数器、本地方法栈等),在后面的JVM学习中,会再详细介绍Java内存模型。

注:方法区与堆的关系较复杂,可认为其处于堆中,具体的在后面的JVM学习中,会再详细介绍。

 

后台线程(守护线程、服务线程)

        假设当前有用户线程(非守护线程)A和守护线程B,当A线程先运行完时(包括结束A进程的操作所花费的时间运行完),B线程无论是否运行完,都会消亡。如果B先运行完,则对A不会有任何影响。

注:守护线程一般都不会比用户线程提前结束。

注:可通过线程对象的.setDaemon(true)方法,来将该线程设置为守护线程。

注:如果有多个用户线程,那么当所有的用户线程都结束后,守护线程才会结束(如果守护线程不提前结束的话)。

 

线程的五种状态

     ◎新生

     ◎就绪

     ◎运行

     ◎阻塞

     ◎消亡

注:有时也可以说四种状态:新生、可运行(就绪 + 运行)、阻塞、消亡。

给出关系图:

线程的部分方法(更多请查阅手册):

       xxx.join();方法:其中xxx为线程名。当执行到此语句时,其它线程暂时阻塞,只有等此线程所有的都运行完后,其余线程才恢复可运行状态。

       xxx.sleep(x);方法:其中xxx为线程名,x为毫秒数。当执行到此语句时,此线程会阻塞x毫秒,然后再恢复可运行状态。

       xxx.yield();方法: 其中xxx为线程名。当前线程主动交出正在运行的资源(即:主动让自己的抢占到的可能性降低了),但是并不阻塞,可以继续抢占。

 

微笑声明:本文是学习笔记,主要学习自以下视频

微笑学习视频
           《Java多线程与并发实战视频课程》,齐毅 

微笑参考笔记
           《程序员成长笔记(一)》,笔者JustryDeng

微笑如有不当之处,欢迎指正

微笑本文已经被收录进《程序员成长笔记(三)》,笔者JustryDeng

猜你喜欢

转载自blog.csdn.net/justry_deng/article/details/82991073