多线程与并发编程入门基础,多图详解

目录

1  概述

1.1 多线程概念

1.2 多线程的应用场景

2 线程与进程

2.1 概念

2.2 进程与线程的联系与区别:

3 并行并发

3.1 并行并发概念

3.2 并行并发

4 程序运行,线程调度

4.1 抢占式调度详解

4.2 主线程

4.3 线程调度

5 线程的状态

5.1 状态示意

5.2 调度示意

6 线程机制 

6.1 线程通信方法:

6.2 线程的分类:

7 线程使用(续)


1  概述

1.1 多线程概念

 如果不能从根本上更新当前CPU的架构(在很长一段时间内还不太可能),那么继续提高CPU性能的方法就是超线程CPU模式。那么,作业系统、应用程序要发挥CPU的最大性能,就是要改变到以多线程编程模型为主的并行处理系统和并发式应用程序。

  所以,掌握多线程编程模型,不仅是目前提高应用性能的手段,更是下一代编程模型的核心思想。多线程编程的目的,就是"最大限度地利用CPU资源",当某一线程的处理不需要占用CPU而只和I/O,OEMBIOS等资源打交道时,让需要占用CPU资源的其它线程有机会获得CPU资源。从根本上说,这就是多线程编程的最终目的。

Thread类中有这样的明确定义:线程是程序中执行的线程,Java虚拟机允许程序同时运行多个执行线程。

1.2 多线程的应用场景

  • 程序中出现需要等待的操作,比如网络操作、文件IO等,可以利用多线程充分使用处理器资源,而不会阻塞程序中其他任务的执行
  • 程序中出现可分解的大任务,比如耗时较长的计算任务,可以利用多线程来共同完成任务,缩短运算时间
  • 程序中出现需要后台运行的任务,比如一些监测任务、定时任务,可以利用多线程来完成

什么是多线程呢?即就是一个程序中有多个线程在同时执行。

通过下图来区别单线程程序与多线程程序的不同:

l 单线程程序:即,若有多个任务只能依次执行。当上一个任务执行结束后,下一个任务开始执行。如,去网吧上网,网吧只能让一个人上网,当这个人下机后,下一个人才能上网。

l 多线程程序:即,若有多个任务可以同时执行。如,去网吧上网,网吧能够让多个人同时上网。

2 线程与进程

 线程(thread),有时被称为轻量级进程(Lightweight Process,LWP),是程序执行流的最小单元。一个标准的线程由线程ID,当前指令指针(PC),寄存器集合和堆栈组成。
    另外,线程是进程中的一个实体,是被系统独立调度和分派的基本单位,线程自己不拥有系统资源,只拥有一点在运行中必不可少的资源,但它可与同属一个进程的其它线程共享进程所拥有的全部资源。
    一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。由于线程之间的相互制约,致使线程在运行中呈现出间断性。线程也有就绪、阻塞和运行三种基本状态。 
  线程是程序中一个单一的顺序控制流程.在单个程序中同时运行多个线程完成不同的工作,称为多线程.  

2.1 概念

1. 进程

进程是资源(CPU、内存等)分配的基本单位,它是程序执行时的一个实例。程序运行时系统就会创建一个进程,并为它分配资源,然后把该进程放入进程就绪队列,进程调度器选中它的时候就会为它分配CPU时间,程序开始真正运行。

2. 线程

线程是一条执行路径,是程序执行时的最小单位,它是进程的一个执行流,是CPU调度和分派的基本单位,一个进程可以由很多个线程组成,线程间共享进程的所有资源,每个线程有自己的堆栈和局部变量。线程由CPU独立调度执行,在多CPU环境下就允许多个线程同时运行。同样多线程也可以实现并发操作,每个请求分配一个线程来处理。

一个正在运行的软件(如迅雷)就是一个进程,一个进程可以同时运行多个任务( 迅雷软件可以同时下载多个文件,每个下载任务就是一个线程), 可以简单的认为进程是线程的集合。

线程是一条可以执行的路径。多线程就是同时有多条执行路径在同时(并行)执行。

程序:是为完成特定任务,用某种语言编写的一组指令的集合,即指一段静态的代码,静态对象。
进程:是程序的一次执行过程,或是正在运行的一个程序,是一个动态的过程,有它自身的产生,存在和消亡的过程。-------生命周期
线程:进程可进一步细化为线程,是一个程序内部的一条执行路径

2.2 进程与线程的联系与区别:

1.作用

进程是资源分配的最小单位

线程是程序执行的最小单位

2.速度

进程有自己的独立地址空间

而线程是共享进程中的数据的,使用相同的地址空间,因此CPU切换一个线程的花费远比进程要小得多,同时创建一个线程的开销也比进程要小得多

3.关系

进程中第一个线程是主线程,主线程创建其他线程,其他线程也可以创建线程,线程之间是平等的

进程有父进程,子进程,独立的内存空间,唯一的进程标识符,pid

4.创建

创建新的线程很容易

创建新的进程需要对父进程做一次复制

5.操作

一个线程可以操作同一进程的其他线程

进程只能操作其子进程

  • 一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程是操作系统可识别的最小执行和调度单位。

  • 资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量,即每个线程都有自己的堆栈和局部变量。

  • 处理机分给线程,即真正在处理机上运行的是线程。

3 并行并发

3.1 并行并发概念

  • 顺序:代码从上而下按照固定的顺序执行,只有上一件事情执行完毕,才能执行下一件事。就像物理电路中的串行,假如有十件事情,一个人来完成,这个人必须先做第一件事情,然后再做第二件事情,最后做第十件事情,按照顺序做。
  • 并行:多个操作同时处理,他们之间是并行的。假如十件事情,两个人来完成,每个人在某个时间点各自做各自的事情,互不影响

  • 并发:将一个操作分割成多个部分执行并且允许无序处理,假如有十件事情,如果有一个人在做,这个人可能做一会这个不想做了,再去做别的,做着做着可能也不想做了,又去干其它事情了,看他心情想干哪个就干哪个,最终把十件事情都做完。如果有两个人在做,他们俩先分一下,比如张三做4件,李四做6件,他们各做自己的,在做自己的事情过程中可以随意的切换到别的事情,不一定要把某件事情干完再去干其它事情,有可能一件事做了N次才做完。

1.同步(Synchronous)与异步(Asynchronous)

    同步和异步通常形容一次方法的调用。同步方法调用开始后调用者必须等到方法调用返回才能进行后续行为。异步方法则像一个消息的传递,调用方法后立即返回而方法体则在后台继续运行,调用者无需等待继续后续操作。

2.并发(Concurrency)和并行(Parallelism)

 并发和并行都能表示两个或多个任务一起执行,但是并发偏重于任务交替执行,多个任务之间很可能是串行的,而并行是真正意义上的"同时执行"。

 严格的来说并行的多个任务是真实的同时执行,而并发是交替的,有系统在任务之间进行切换,即多个任务之间是串行并发,但对人来说会造成多任务是并行的错觉。

通常一台电脑只有一个cpu,多个线程属于并发执行,如果有多个cpu,多线程并发执行有可能变成并行执行。

3.2 并行并发

  • 线程安全:经常用来描绘一段代码。指在并发的情况之下,该代码经过多线程使用,线程的调度顺序不影响任何结果。这个时候使用多线程,我们只需要关注系统的内存,cpu是不是够用即可。反过来,线程不安全就意味着线程的调度顺序会影响最终结果,如不加事务的转账代码:
void transferMoney(User from, User to, float amount){
  to.setMoney(to.getBalance() + amount);
  from.setMoney(from.getBalance() - amount);
}
  • 同步:Java中的同步指的是通过人为的控制和调度,保证共享资源的多线程访问成为线程安全,来保证结果的准确。如上面的代码简单加入@synchronized关键字。在保证结果准确的同时,提高性能,才是优秀的程序。线程安全的优先级高于性能。

4 程序运行,线程调度

l 分时调度

所有线程轮流使用 CPU 的使用权,平均分配每个线程占用 CPU 的时间。

l 抢占式调度

优先让优先级高的线程使用 CPU,如果线程的优先级相同,那么会随机选择一个(线程随机性),Java使用的为抢占式调度。

4.1 抢占式调度详解

大部分操作系统都支持多进程并发运行,现在的操作系统几乎都支持同时运行多个程序。比如:现在我们上课一边使用编辑器,一边使用录屏软件,同时还开着画图板,dos窗口等软件。此时,这些程序是在同时运行,”感觉这些软件好像在同一时刻运行着“。

实际上,CPU(中央处理器)使用抢占式调度模式在多个线程间进行着高速的切换。对于CPU的一个核而言,某个时刻,只能执行一个线程,而 CPU的在多个线程间切换速度相对我们的感觉要快,看上去就是在同一时刻运行。

其实,多线程程序并不能提高程序的运行速度,但能够提高程序运行效率,让CPU的使用率更高。

4.2 主线程

当我们在dos命令行中输入java空格类名回车后,启动JVM,并且加载对应的class文件。虚拟机并会从main方法开始执行我们的程序代码,一直把main方法的代码执行结束。如果在执行过程遇到循环时间比较长的代码,那么在循环之后的其他代码是不会被马上执行的。如下代码演示:

class Demo{
String name;
Demo(String name){
this.name = name;
}

void show() {
for (int i=1;i<=10000 ;i++ ) {
System.out.println("name="+name+",i="+i);
}
}
} 

class ThreadDemo {
public static void main(String[] args) {
    Demo d = new Demo("小强");
         Demo d2 = new Demo("旺财");
d.show();
d2.show();
System.out.println("Hello World!");
}
}

若在上述代码中show方法中的循环执行次数很多,这时在d.show();下面的代码是不会马上执行的,并且在dos窗口会看到不停的输出name=小强,i=值,这样的语句。为什么会这样呢?

原因是:jvm启动后,必然有一个执行路径(线程)从main方法开始的,一直执行到main方法结束,这个线程在java中称之为主线程。当程序的主线程执行时,如果遇到了循环而导致程序在指定位置停留时间过长,则无法马上执行下面的程序,需要等待循环结束后能够执行。

那么,能否实现一个主线程负责执行其中一个循环,再由另一个线程负责其他代码的执行,最终实现多部分代码同时执行的效果?

能够实现同时执行,通过Java中的多线程技术来解决该问题。

Java中每个线程都有优先级属性,具有较高优先级的线程优先于优先级较低的线程执行,每个线程可能也可能不会被标记为守护进程。当在某个线程中运行创建一个新的 Thread对象时,新的线程的优先级最初设置为等于创建线程的优先级,并且当且仅当创建线程是守护进程时才是守护进程线程。
线程的优先级用数字来表示,范围从1~10,主线程的默认优先级为5 :

Thread.MIN_PRIORITY=1;Thread.MAX_PRIORITY=10;Thread.NORM_PRIORITY=5 )

当Java虚拟机启动时,通常会有一个非守护程序线程(通常调用某个指定类的名为 main的方法)
Java虚拟机继续执行线程直到发生以下任一情况:

  • 已调用类 Runtime的exit方法,并且安全管理器已允许执行退出操作。
  • 所有非守护程序线程的线程都已经死亡,要么通过调用返回run方法,要么抛出一个超出run 方法传播的异常。

4.3 线程调度

调度策略:
时间片:线程的调度采用时间片轮转的方式
抢占式:高优先级的线程抢占CPU
Java的调度方法:
1.对于同优先级的线程组成先进先出队列(先到先服务),使用时间片策略
2.对高优先级,使用优先调度的抢占式策略

线程的优先级

等级:
MAX_PRIORITY:10
MIN_PRIORITY:1
NORM_PRIORITY:5

方法:
getPriority():返回线程优先级
setPriority(int newPriority):改变线程的优先级

注意!:高优先级的线程要抢占低优先级的线程的cpu的执行权。但是仅是从概率上来说的,高优先级的线程更有可能被执行。并不意味着只有高优先级的线程执行完以后,低优先级的线程才执行。

5 线程的状态

5.1 状态示意

  1. 创建(new)状态: 准备好了一个多线程的对象,即执行了new Thread(); 创建完成后就需要为线程分配内存
  2. 就绪(runnable)状态: 调用了start()方法, 等待CPU进行调度
  3. 运行(running)状态: 执行run()方法
  4. 阻塞(blocked)状态: 暂时停止执行线程,将线程挂起(sleep()、wait()、join()、没有获取到锁都会使线程阻塞), 可能将资源交给其它线程使用
  5. 死亡(terminated)状态: 线程销毁(正常执行完毕、发生异常或者被打断interrupt()都会导致线程终止)

5.2 调度示意

 

各种状态一目了然,值得一提的是"blocked"这个状态:
线程在Running的过程中可能会遇到阻塞(Blocked)情况

  1. 调用join()和sleep()方法,sleep()时间结束或被打断,join()中断,IO完成都会回到Runnable状态,等待JVM的调度。
  2. 调用wait(),使该线程处于等待池(wait blocked pool),直到notify()/notifyAll(),线程被唤醒被放到锁定池(lock blocked pool ),释放同步锁使线程回到可运行状态(Runnable)
  3. 对Running状态的线程加同步锁(Synchronized)使其进入(lock blocked pool ),同步锁被释放进入可运行状态(Runnable)。

此外,在runnable状态的线程是处于被调度的线程,此时的调度顺序是不一定的。Thread类中的yield方法可以让一个running状态的线程转入runnable。

6 线程机制 

6.1 线程通信方法:

synchronized, wait, notify 是任何对象都具有的同步工具。让我们先来了解他们

他们是应用于同步问题的人工线程调度工具。讲其本质,首先就要明确monitor的概念,Java中的每个对象都有一个监视器,来监测并发代码的重入。在非多线程编码时该监视器不发挥作用,反之如果在synchronized 范围内,监视器发挥作用。

wait/notify必须存在于synchronized块中。并且,这三个关键字针对的是同一个监视器(某对象的监视器)。这意味着wait之后,其他线程可以进入同步块执行。

当某代码并不持有监视器的使用权时去wait或notify,会抛出java.lang.IllegalMonitorStateException。也包括在synchronized块中去调用另一个对象的wait/notify,因为不同对象的监视器不同,同样会抛出此异常。

  • volatile

    多线程的内存模型:main memory(主存)、working memory(线程栈),在处理数据时,线程会把值从主存load到本地栈,完成操作后再save回去(volatile关键词的作用:每次针对该变量的操作都激发一次load and save)。

volatile

针对多线程使用的变量如果不是volatile或者final修饰的,很有可能产生不可预知的结果(另一个线程修改了这个值,但是之后在某线程看到的是修改之前的值)。其实道理上讲同一实例的同一属性本身只有一个副本。但是多线程是会缓存值的,本质上,volatile就是不去缓存,直接取值。在线程安全的情况下加volatile会牺牲性能。

wait()/ notify()/ notifayAll():此三个方法定义在Object类中的,因为这三个方法需要用到锁,而锁是任意对象都能充当的,所以这三个方法定义在Object类中。

由于wait,notify,以及notifyAll都涉及到与锁相关的操作
wait(在进入锁住的区域以后阻塞等待,释放锁让别的线程先进来操作)---- Obj.wait 进入Obj这个锁住的区域的线程把锁交出来原地等待通知
notify(由于有很多锁住的区域,所以需要将区域用锁来标识,也涉及到锁) ----- Obj.notify 新线程进入Obj这个区域进行操作并唤醒wait的线程

所以wait,notify需要使用在有锁的地方,也就是需要用synchronize关键字来标识的区域,即使用在同步代码块或者同步方法中,且为了保证wait和notify的区域是同一个锁住的区域,需要用锁来标识,也就是锁要相同的对象来充当

6.2 线程的分类:

  • 用户线程:如果主线程main停止掉,不会影响用户线程,用户线程可以继续运行。
  • 守护线程:如果主线程死亡,守护线程如果没有执行完毕也要跟着一块死(就像皇上死了,带刀侍卫也要一块死),GC垃圾回收线程就是守护线程

java中的线程分为两类:1.守护线程(如垃圾回收线程,异常处理线程),2.用户线程(如主线程)

若JVM中都是守护线程,当前JVM将退出。(形象理解,唇亡齿寒)

7 线程使用(续)

部分内容转载此大佬,里面包括了详细的线程使用方法,可以参考查阅。

https://blog.csdn.net/vbirdbest/article/details/81282163

 

 

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值