精华内容
下载资源
问答
  • java 进程,线程及状态转换

    千次阅读 2016-05-28 20:25:22
    进程:是程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程,是一个动态的实体,它有自己的生命周期。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消...

    程序

    一段静态的代码,一组指令的有序集合,它本身没有任何运行的含义,它只是一个静态的实体,是应用软件执行的蓝本。

    进程

    是程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程,是一个动态的实体,它有自己的生命周期。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务而被撤消。反映了一个程序在一定的数据 集上运行的全部动态过程。通过进程控制块(PCB)唯一的标识某个进程。同时进程占据着相应的资源(例如包 括cpu的使用 ,轮转时间以及一些其它设备的权限)。是系统进行资源分配和调度的一个独立单位。

    进程(道路)与线程(车道)

    假如我们把城市里的整条道路看成是一个“进程”的话,那么由白色虚线分隔开来的各个车道就是进程中的各个“线程”。

    1. 线程(车道)共享了进程(道路)的公共资源(土地资源)。
      这些线程(车道)必须依赖于进程(道路),也就是说,线程不能脱离于进程而存在(就像离开了道路,车道也就没有意义了)。
    2. 这些线程(车道)之间可以并发执行(各个车道你走你的,我走我的),也可以互相同步(某些车道在交通灯亮时禁止继续前行或转弯,必须等待其它车道的车辆通行完毕)。
    3. 这些线程(车道)之间依靠代码逻辑(交通灯)来控制运行,一旦代码逻辑控制有误(死锁,多个线程同时竞争唯一资源),那么线程将陷入混乱,无序之中。
    4. 这些线程(车道)之间谁先运行是未知的,只有在线程刚好被分配到CPU时间片(交通灯变化)的那一刻才能知道。

    程序和进程之间的主要区别

    区别状态是否具有资源是否有唯一标识是否具有并发性
    进程动态
    程序静态

    进程与线程的区别

    1. 线程是程序执行(CPU调度)的最小单位,而进程是操作系统分配资源的最小单位;

    2. 一个进程由一个或多个线程组成,线程是一个进程中代码的不同执行路线;

    3. 进程之间相互独立,但同一进程下的各个线程之间共享程序的内存空间(包括代码段,数据集,堆等)及一些进程级的资源(如打开文件和信号等),某进程内的线程在其他进程不可见;

    4. 调度和切换:线程上下文切换比进程上下文切换要快得多

    线程和进程关系示意图

    在这里插入图片描述

    为何不使用多进程而是使用多线程?

    线程廉价,线程启动比较快,退出比较快,对系统资源的冲击也比较小。而且线程彼此分享了大部分核心对象(File Handle)的拥有权.

    如果使用多重进程,但是不可预期,且测试困难.

    进程的基本状态

    1.就绪(Ready)状态

    当进程已分配到除CPU以外的所有必要资源后,只要在获得CPU,便可立即执行,进程这时的状态就称为就绪状态。在一个系统中处于就绪状态的进程可能有多个,通常将他们排成一个队列,称为就绪队列。

    2.执行状态

    进程已获得CPU,其程序正在执行。在单处理机系统中,只有一个进程处于执行状态;再多处理机系统中,则有多个进程处于执行状态。

    3.阻塞状态

    正在执行的进程由于发生某事件而暂时无法继续执行时,便放弃处理机而处于暂停状态,亦即程序的执行受到阻塞,把这种暂停状态称为阻塞状态,有时也称为等待状态或封锁状态。

    三种进程之间的转换图:
    这里写图片描述

    线程: 可以理解为进程的多条执行线索,每条线索又对应着各自独立的生命周期。线程是进程的一个实体,是CPU调度和分派的基本单位,它是比进程更小的能独立运行的基本单位。一个线程可以创建和撤销另一个线程,同一个进程中的多个线程之间可以并发执行。
    这里在详细讲解一下Java中的进程和线程的概念。

    线程状态转换

    线程的状态使用一个枚举类型来描述的。这个枚举一共有6个值: NEW(新建)、RUNNABLE(运行)、BLOCKED(锁池)、TIMED_WAITING(定时等待)、WAITING(等待)、TERMINATED(终止、结束)。
    Thread->State
    在这里插入图片描述
    在这里插入图片描述
    在这里插入图片描述

    //观察线程状态
    public class TestThreadState {
    
        public static void main(String[] args) throws InterruptedException {
            Thread thread = new Thread(()->{
                for (int i = 0; i <5 ; i++) {
                    try {
                        Thread.sleep(1000);
                        System.out.println("current state:"+ Thread.currentThread().getName()+":"+Thread.currentThread().getState());
    
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                System.out.println("over");
            });
    
            //观察状态
            Thread.State state = thread.getState();
            System.out.println(thread.getName()+":1"+ state); //new
    
    
            //观察启动后
            thread.start();  //启动线程
            state = thread.getState();
            System.out.println(thread.getName()+":2"+state); //RUNNABLE
    
            while (state != Thread.State.TERMINATED){
    
                Thread.sleep(500);
                state = thread.getState(); //更新线程状态
                System.out.println(thread.getName()+":3"+ state); //输出状态 结束后TERMINATED
            }
        }
    }
    

    在这里插入图片描述
    注意:

    调用obj.wait()的线程需要先获取obj的monitor,wait()会释放obj的monitor并进入等待态。所以wait()/notify()都要与synchronized联用。

    RUNABLE细分ready->拿到CPU时间片->RUNNING
    在这里插入图片描述

    阻塞与等待的区别

    阻塞:当一个线程试图获取对象锁(非java.util.concurrent库中的锁,即synchronized),而该锁被其他线程持有,则该线程进入阻塞状态。它的特点是使用简单,由JVM调度器来决定唤醒自己,而不需要由另一个线程来显式唤醒自己,不响应中断。
    等待:当一个线程等待另一个线程通知调度器一个条件时,该线程进入等待状态。它的特点是需要等待另一个线程显式地唤醒自己,实现灵活,语义更丰富,可响应中断。例如调用:Object.wait()、Thread.join()以及等待Lock或Condition。

    需要强调的是虽然synchronized和JUC里的Lock都实现锁的功能,但线程进入的状态是不一样的。synchronized会让线程进入阻塞态,而JUC里的Lock是用LockSupport.park()/unpark()来实现阻塞/唤醒的,会让线程进入等待态。但话又说回来,虽然等锁时进入的状态不一样,但被唤醒后又都进入runnable态,从行为效果来看又是一样的。

    参考:
    https://www.cnblogs.com/GooPolaris/p/8079490.html
    https://www.cnblogs.com/waterystone/p/4920007.html (Thread详解)

    展开全文
  • 进程/线程切换究竟需要多少开销?

    千次阅读 2020-11-05 09:00:00
    进程是我们开发同学非常熟悉的概念,我们可能也听说过进程上下文切换开销。那么今天让我们来思考一个问题,究竟一次进程上下文切换会吃掉多少CPU时间呢?线程据说比进程轻量,它的上下文切换会比进...

    进程是我们开发同学非常熟悉的概念,我们可能也听说过进程上下文切换开销。那么今天让我们来思考一个问题,究竟一次进程上下文切换会吃掉多少CPU时间呢?线程据说比进程轻量,它的上下文切换会比进程切换节约很多CPU时间吗?

    01

    进程以及进程切换

    进程是操作系统的伟大发明之一,对应用程序屏蔽了CPU调度、内存管理等硬件细节,而抽象出一个进程的概念,让应用程序专心于实现自己的业务逻辑既可,而且在有限的CPU上可以“同时”进行许多个任务。

    在进程A切换到进程B的过程中,先保存A进程的上下文,以便于等A恢复运行的时候,能够知道A进程的下一条指令是啥。然后将要运行的B进程的上下文恢复到寄存器中。这个过程被称为上下文切换。

    上下文切换开销在进程不多、切换不频繁的应用场景下问题不大。但是现在Linux操作系统被用到了高并发的网络程序后端服务器。在单机支持成千上万个用户请求的时候,这个开销就得拿出来说道说道了。

    因为用户进程在请求Redis、Mysql数据等网络IO阻塞掉的时候,或者在进程时间片到了,都会引发上下文切换。

    02

    一次简单的进程切换开销测试

    废话不多说,我们先用个实验测试一下,到底一次上下文切换需要多长的CPU时间!

    实验方法是创建两个进程并在它们之间传送一个令牌。其中一个进程在读取令牌时就会引起阻塞。另一个进程发送令牌后等待其返回时也处于阻塞状态。如此往返传送一定的次数,然后统计他们的平均单次切换时间开销。编译、运行

    # gcc main.c -o main
    # ./main./main
    Before Context Switch Time1565352257 s, 774767 us
    After Context SWitch Time1565352257 s, 842852 us
    
    

    每次执行的时间会有差异,多次运行后平均每次上下文切换耗时3.5us左右。当然了这个数字因机器而异,而且建议在实机上测试。

    前文我们测试系统调用的时候,最低值是200ns。可见,上下文切换开销要比系统调用的开销要大。系统调用只是在进程内将用户态切换到内核态,然后再切回来,而上下文切换可是直接从进程A切换到了进程B。显然这个上下文切换需要完成的工作量更大。

    03

    进程切换开销分析

    那么上下文切换的时候,CPU的开销都具体有哪些呢?开销分成两种,一种是直接开销、一种是间接开销。

    直接开销就是在切换时,cpu必须做的事情,包括:

    1、切换页表全局目录

    2、切换内核态堆栈

    3、切换硬件上下文(进程恢复前,必须装入寄存器的数据统称为硬件上下文)

    • ip(instruction pointer):指向当前执行指令的下一条指令

    • bp(base pointer): 用于存放执行中的函数对应的栈帧的栈底地址

    • sp(stack poinger): 用于存放执行中的函数对应的栈帧的栈顶地址

    • cr3:页目录基址寄存器,保存页目录表的物理地址

    • ......

    4、刷新TLB

    5、系统调度器的代码执行

    间接开销主要指的是虽然切换到一个新进程后,由于各种缓存并不热,速度运行会慢一些。

    如果进程始终都在一个CPU上调度还好一些,如果跨CPU的话,之前热起来的TLB、L1、L2、L3因为运行的进程已经变了,所以以局部性原理cache起来的代码、数据也都没有用了,导致新进程穿透到内存的IO会变多。

    其实我们上面的实验并没有很好地测量到这种情况,所以实际的上下文切换开销可能比3.5us要大。

    想了解更详细操作过程的同学请参考《深入理解Linux内核》中的第三章和第九章。

    04

    更专业的工具-lmbench

    lmbench用于评价系统综合性能的多平台开源benchmark,能够测试包括文档读写、内存操作、进程创建销毁开销、网络等性能。使用方法简单,但就是跑有点慢,感兴趣的同学可以自己试一试。

    这个工具的优势是是进行了多组实验,每组2个进程、8个、16个。每个进程使用的数据大小也在变,充分模拟cache miss造成的影响。我用他测了一下结果如下(下图需要横向滚动查看完全):

    -------------------------------------------------------------------------
    Host                 OS  2p/0K 2p/16K 2p/64K 8p/16K 8p/64K 16p/16K 16p/64K
                             ctxsw  ctxsw  ctxsw ctxsw  ctxsw   ctxsw   ctxsw
    --------- ------------- ------ ------ ------ ------ ------ ------- -------
    bjzw_46_7 Linux 2.6.32- 2.7800 2.7800 2.7000 4.3800 4.0400 4.75000 5.48000
    

    lmbench显示的进程上下文切换耗时从2.7us到5.48us之间。

    05

    线程上下文切换耗时

    前面我们测试了进程上下文切换的开销,我们再继续在Linux测试一下线程。看看究竟比进程能不能快一些,快的话能快多少。

    在Linux下其实本并没有线程,只是为了迎合开发者口味,搞了个轻量级进程出来就叫做了线程。轻量级进程和进程一样,都有自己独立的task_struct进程描述符,也都有自己独立的pid。从操作系统视角看,调度上和进程没有什么区别,都是在等待队列的双向链表里选择一个task_struct切到运行态而已。只不过轻量级进程和普通进程的区别是可以共享同一内存地址空间、代码段、全局变量、同一打开文件集合而已。

    同一进程下的线程之所有getpid()看到的pid是一样的,其实task_struct里还有一个tgid字段。对于多线程程序来说,getpid()系统调用获取的实际上是这个tgid,因此隶属同一进程的多线程看起来PID相同。

    我们用一个实验来进行另外一个测试。其原理和进程测试差不多,创建了20个线程,在线程之间通过管道来传递信号。接到信号就唤醒,然后再传递信号给下一个线程,自己睡眠。这个实验里单独考虑了给管道传递信号的额外开销,并在第一步就统计了出来。

    # gcc -lpthread main.c -o main
    0.508250
    4.363495
    
    

    每次实验结果会有一些差异,上面的结果是取了多次的结果之后然后平均的,大约每次线程切换开销大约是3.8us左右。从上下文切换的耗时上来看,Linux线程(轻量级进程)其实和进程差别不太大。

    06

    Linux相关命令

    既然我们知道了上下文切换比较的消耗CPU时间,那么我们通过什么工具可以查看一下Linux里究竟在发生多少切换呢?如果上下文切换已经影响到了系统整体性能,我们有没有办法把有问题的进程揪出来,并把它优化掉呢?(下图需要横向滚动查看完全)

    # vmstat 1
    procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu-----
     r  b   swpd   free   buff  cache   si   so    bi    bo   in   cs us sy id wa st
     2  0      0 595504   5724 190884    0    0   295   297    0    0 14  6 75  0  4
     5  0      0 593016   5732 193288    0    0     0    92 19889 29104 20  6 67  0  7
     3  0      0 591292   5732 195476    0    0     0     0 20151 28487 20  6 66  0  8
     4  0      0 589296   5732 196800    0    0   116   384 19326 27693 20  7 67  0  7
     4  0      0 586956   5740 199496    0    0   216    24 18321 24018 22  8 62  0  8
    
    

    或者是

    # sar -w 1
    proc/s
         Total number of tasks created per second.
    cswch/s
         Total number of context switches per second.
    11:19:20 AM    proc/s   cswch/s
    11:19:21 AM    110.28  23468.22
    11:19:22 AM    128.85  33910.58
    11:19:23 AM     47.52  40733.66
    11:19:24 AM     35.85  30972.64
    11:19:25 AM     47.62  24951.43
    11:19:26 AM     47.52  42950.50
    ......
    
    

    上图的环境是一台生产环境机器,配置是8核8G的KVM虚机,环境是在nginx+fpm的,fpm数量为1000,平均每秒处理的用户接口请求大约100左右。其中cs列表示的就是在1s内系统发生的上下文切换次数,大约1s切换次数都达到4W次了。粗略估算一下,每核大约每秒需要切换5K次,则1s内需要花将近20ms在上下文切换上。要知道这是虚机,本身在虚拟化上还会有一些额外开销,而且还要真正消耗CPU在用户接口逻辑处理、系统调用内核逻辑处理、以及网络连接的处理以及软中断,所以20ms的开销实际上不低了。

    那么进一步,我们看下到底是哪些进程导致了频繁的上下文切换?(下图可能需要横向滚动查看完全)

    # pidstat -w 1
    11:07:56 AM       PID   cswch/s nvcswch/s  Command
    11:07:56 AM     32316      4.00      0.00  php-fpm
    11:07:56 AM     32508    160.00     34.00  php-fpm
    11:07:56 AM     32726    131.00      8.00  php-fpm
    ......
    
    

    由于fpm是同步阻塞的模式,每当请求Redis、Memcache、Mysql的时候就会阻塞导致cswch/s自愿上下文切换,而只有时间片到了之后才会触发nvcswch/s非自愿切换。可见fpm进程大部分的切换都是自愿的、非自愿的比较少。

    如果想查看具体某个进程的上下文切换总情况,可以在/proc接口下直接看,不过这个是总值。

    grep ctxt /proc/32583/status
    voluntary_ctxt_switches:        573066
    nonvoluntary_ctxt_switches:     89260
    
    

    07

    结论

    上下文切换具体做哪些事情我们没有必要记,只需要记住一个结论既可,测得作者开发机上下文切换的开销大约是2.7-5.48us左右,你自己的机器可以用我提供的代码或工具进行一番测试。

    lmbench相对更准确一些,因为考虑了切换后Cache miss导致的额外开销。

    漫画:兄弟,今晚又得熬个通宵了!

    从月薪三千到月薪三千

    大牛是怎么练成的?又是怎么赚钱的?

    漫画:量子计算为什么这么牛?

    架构师劝退指南

    Java每次遇难,总会有大神拯救

    程序员的宿命

    芯片战争70年,真正的王者即将现身

    干掉软件开发的最大怪兽:狼人

    宇宙第一IDE到底是谁?

    HTTP Server :一个差生的逆袭

    如何降低程序员的工资?

    程序员,你得选准跑路的时间!

    两年,我学会了所有的编程语言!

    Javascript: 一个屌丝的逆袭

    我是一个线程

    TCP/IP之大明邮差

    一个故事讲完Https

    CPU 阿甘

    展开全文
  • 进程线程的区别(超详细)

    万次阅读 多人点赞 2019-10-03 21:57:46
    进程线程 进程 一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程线程 进程中的一个执行任务(控制单元),负责...

    进程和线程

    进程

    一个在内存中运行的应用程序。每个进程都有自己独立的一块内存空间,一个进程可以有多个线程,比如在Windows系统中,一个运行的xx.exe就是一个进程。

    任务管理器

    线程

    进程中的一个执行任务(控制单元),负责当前进程中程序的执行。一个进程至少有一个线程,一个进程可以运行多个线程,多个线程可共享数据。

    与进程不同的是同类的多个线程共享进程的方法区资源,但每个线程有自己的程序计数器虚拟机栈本地方法栈,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程。

    Java 程序天生就是多线程程序,我们可以通过 JMX 来看一下一个普通的 Java 程序有哪些线程,代码如下。

    public class MultiThread {
    	public static void main(String[] args) {
    		// 获取 Java 线程管理 MXBean
    		ThreadMXBean threadMXBean = ManagementFactory.getThreadMXBean();
    		// 不需要获取同步的 monitor 和 synchronizer 信息,仅获取线程和线程堆栈信息
    		ThreadInfo[] threadInfos = threadMXBean.dumpAllThreads(false, false);
    		// 遍历线程信息,仅打印线程 ID 和线程名称信息
    		for (ThreadInfo threadInfo : threadInfos) {
    			System.out.println("[" + threadInfo.getThreadId() + "] " + threadInfo.getThreadName());
    		}
    	}
    }
    

    上述程序输出如下(输出内容可能不同,不用太纠结下面每个线程的作用,只用知道 main 线程执行 main 方法即可):

    [6] Monitor Ctrl-Break //监听线程转储或“线程堆栈跟踪”的线程
    [5] Attach Listener //负责接收到外部的命令,而对该命令进行执行的并且把结果返回给发送者
    [4] Signal Dispatcher // 分发处理给 JVM 信号的线程
    [3] Finalizer //在垃圾收集前,调用对象 finalize 方法的线程
    [2] Reference Handler //用于处理引用对象本身(软引用、弱引用、虚引用)的垃圾回收的线程
    [1] main //main 线程,程序入口
    

    从上面的输出内容可以看出:一个 Java 程序的运行是 main 线程和多个其他线程同时运行

    进程与线程的区别总结

    线程具有许多传统进程所具有的特征,故又称为轻型进程(Light—Weight Process)或进程元;而把传统的进程称为重型进程(Heavy—Weight Process),它相当于只有一个线程的任务。在引入了线程的操作系统中,通常一个进程都有若干个线程,至少包含一个线程。

    根本区别:进程是操作系统资源分配的基本单位,而线程是处理器任务调度和执行的基本单位

    资源开销:每个进程都有独立的代码和数据空间(程序上下文),程序之间的切换会有较大的开销;线程可以看做轻量级的进程,同一类线程共享代码和数据空间,每个线程都有自己独立的运行栈和程序计数器(PC),线程之间切换的开销小。

    包含关系:如果一个进程内有多个线程,则执行过程不是一条线的,而是多条线(线程)共同完成的;线程是进程的一部分,所以线程也被称为轻权进程或者轻量级进程。

    内存分配:同一进程的线程共享本进程的地址空间和资源,而进程之间的地址空间和资源是相互独立的

    影响关系:一个进程崩溃后,在保护模式下不会对其他进程产生影响,但是一个线程崩溃整个进程都死掉。所以多进程要比多线程健壮。

    执行过程:每个独立的进程有程序运行的入口、顺序执行序列和程序出口。但是线程不能独立执行,必须依存在应用程序中,由应用程序提供多个线程执行控制,两者均可并发执行

    从 JVM 角度说进程和线程之间的关系(重要)

    图解进程和线程的关系

    下图是 Java 内存区域,通过下图我们从 JVM 的角度来说一下线程和进程之间的关系。

    在这里插入图片描述

    从上图可以看出:一个进程中可以有多个线程,多个线程共享进程的方法区 (JDK1.8 之后的元空间)资源,但是每个线程有自己的程序计数器虚拟机栈本地方法栈

    程序计数器为什么是私有的?

    程序计数器主要有下面两个作用:

    1. 字节码解释器通过改变程序计数器来依次读取指令,从而实现代码的流程控制,如:顺序执行、选择、循环、异常处理。
    2. 在多线程的情况下,程序计数器用于记录当前线程执行的位置,从而当线程被切换回来的时候能够知道该线程上次运行到哪儿了。

    需要注意的是,如果执行的是 native 方法,那么程序计数器记录的是 undefined 地址,只有执行的是 Java 代码时程序计数器记录的才是下一条指令的地址。

    所以,程序计数器私有主要是为了线程切换后能恢复到正确的执行位置

    虚拟机栈和本地方法栈为什么是私有的?

    • 虚拟机栈:每个 Java 方法在执行的同时会创建一个栈帧用于存储局部变量表、操作数栈、常量池引用等信息。从方法调用直至执行完成的过程,就对应着一个栈帧在 Java 虚拟机栈中入栈和出栈的过程。
    • 本地方法栈:和虚拟机栈所发挥的作用非常相似,区别是: 虚拟机栈为虚拟机执行 Java 方法 (也就是字节码)服务,而本地方法栈则为虚拟机使用到的 Native 方法服务。 在 HotSpot 虚拟机中和 Java 虚拟机栈合二为一。

    所以,为了保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。

    一句话简单了解堆和方法区

    堆和方法区是所有线程共享的资源,其中堆是进程中最大的一块内存,主要用于存放新创建的对象 (所有对象都在这里分配内存),方法区主要用于存放已被加载的类信息、常量、静态变量、即时编译器编译后的代码等数据。

    多进程和多线程区别

    多进程:操作系统中同时运行的多个程序

    多线程:在同一个进程中同时运行的多个任务

    举个例子,多线程下载软件,可以同时运行多个线程,但是通过程序运行的结果发现,每一次结果都不一致。 因为多线程存在一个特性:随机性。造成的原因:CPU在瞬间不断切换去处理各个线程而导致的,可以理解成多个线程在抢CPU资源。

    多线程提高CPU使用率

    多线程

    多线程并不能提高运行速度,但可以提高运行效率,让CPU的使用率更高。但是如果多线程有安全问题或出现频繁的上下文切换时,运算速度可能反而更低。

    Java中的多线程

    Java程序的进程里有几个线程:主线程,垃圾回收线程(后台线程)等

    在 Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。

    Java支持多线程,当Java程序执行main方法的时候,就是在执行一个名字叫做main的线程,可以在main方法执行时,开启多个线程A,B,C,多个线程 main,A,B,C同时执行,相互抢夺CPU,Thread类是java.lang包下的一个常用类,每一个Thread类的对象,就代表一个处于某种状态的线程

    展开全文
  • Java线程详解(一)Java线程入门

    千次阅读 多人点赞 2019-11-27 20:18:20
    最近听很多面试的小伙伴说,网上往往是一篇一篇的Java线程的文章,除了书籍没有什么学习多线程的一系列文章。但是仅仅凭借一两篇文章很难对多线程有系统的学习,而且面试的时候多线程这方面的知识往往也是考察的...

    最近听很多面试的小伙伴说,网上往往是一篇一篇的Java多线程的文章,除了书籍没有什么学习多线程的一系列文章。但是仅仅凭借一两篇文章很难对多线程有系统的学习,而且面试的时候多线程这方面的知识往往也是考察的重点,所以考虑之下决定写一系列关于Java多线程的文章。文章参考了高老师的《Java多线程编程核心技术》。力争使用最短的篇幅把Java多线程的知识作以系统的讲述。

    本节思维导图:

    思维导图源文件+思维导图软件关注微信公众号:“Java团长”回复关键字:“Java多线程”即可免费领取。

     

    一 进程和多线程简介

    1.1 相关概念

    何为线程?

    线程与进程相似,但线程是一个比进程更小的执行单位。一个进程在其执行的过程中可以产生多个线程。与进程不同的是同类的多个线程共享同一块内存空间和一组系统资源,所以系统在产生一个线程,或是在各个线程之间作切换工作时,负担要比进程小得多,也正因为如此,线程也被称为轻量级进程

    何为进程?

    进程是程序的一次执行过程,是系统运行程序的基本单位,因此进程是动态的。系统运行一个程序即是一个进程从创建,运行到消亡的过程。简单来说,一个进程就是一个执行中的程序,它在计算机中一个指令接着一个指令地执行着,同时,每个进程还占有某些系统资源如CPU时间,内存空间,文件,文件,输入输出设备的使用权等等。换句话说,当程序在执行时,将会被操作系统载入内存中。

    线程和进程有何不同?

    线程是进程划分成的更小的运行单位。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。从另一角度来说,进程属于操作系统的范畴,主要是同一段时间内,可以同时执行一个以上的程序,而线程则是在同一程序内几乎同时执行一个以上的程序段。

    1.2 多线程

    何为多线程?

    多线程就是几乎同时执行多个线程(一个处理器在某一个时间点上永远都只能是一个线程!即使这个处理器是多核的,除非有多个处理器才能实现多个线程同时运行。)。几乎同时是因为实际上多线程程序中的多个线程实际上是一个线程执行一会然后其他的线程再执行,并不是很多书籍所谓的同时执行。

    为什么多线程是必要的?

    1. 使用线程可以把占据长时间的程序中的任务放到后台去处理
    2. 用户界面可以更加吸引人,这样比如用户点击了一个按钮去触发某些事件的处理,可以弹出一个进度条来显示处理的进度
    3. 程序的运行速度可能加快

    二 使用多线程

    2.1继承Thread类

    MyThread.java

    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		super.run();
    		System.out.println("MyThread");
    	}
    }

    Run.java

    public class Run {
    
    	public static void main(String[] args) {
    		MyThread mythread = new MyThread();
    		mythread.start();
    		System.out.println("运行结束");
    	}
    
    }

    运行结果:

    结果

    从上面的运行结果可以看出:线程是一个子任务,CPU以不确定的方式,或者说是以随机的时间来调用线程中的run方法

     

    2.2实现Runnable接口

    推荐实现Runnable接口方式开发多线程,因为Java单继承但是可以实现多个接口

    MyRunnable.java

    public class MyRunnable implements Runnable {
    	@Override
    	public void run() {
    		System.out.println("MyRunnable");
    	}
    }

    Run.java

    public class Run {
    
    	public static void main(String[] args) {
    		Runnable runnable=new MyRunnable();
    		Thread thread=new Thread(runnable);
    		thread.start();
    		System.out.println("运行结束!");
    	}
    
    }

    运行结果:

    运行结果

     

    三 实例变量和线程安全

    定义线程类中的实例变量针对其他线程可以有共享和不共享之分

    3.1 不共享数据的情况

    MyThread.java

    public class MyThread extends Thread {
    
    	private int count = 5;
    
    	public MyThread(String name) {
    		super();
    		this.setName(name);
    	}
    
    	@Override
    	public void run() {
    		super.run();
    		while (count > 0) {
    			count--;
    			System.out.println("由 " + MyThread.currentThread().getName()
    					+ " 计算,count=" + count);
    		}
    	}
    }

    Run.java

    public class Run {
    	public static void main(String[] args) {
    		MyThread a = new MyThread("A");
    		MyThread b = new MyThread("B");
    		MyThread c = new MyThread("C");
    		a.start();
    		b.start();
    		c.start();
    	}
    }

    运行结果:

    运行结果

    可以看出每个线程都有一个属于自己的实例变量count,它们之间互不影响。我们再来看看另一种情况。

     

    3.2 共享数据的情况

    MyThread.java

    public class MyThread extends Thread {
    
    	private int count = 5;
    
    	@Override
    	 public void run() {
    		super.run();
    		count--;
    		System.out.println("由 " + MyThread.currentThread().getName() + " 计算,count=" + count);
    	}
    }

    Run.java

    public class Run {
    	public static void main(String[] args) {
    		
    		MyThread mythread=new MyThread();
            //下列线程都是通过mythread对象创建的
    		Thread a=new Thread(mythread,"A");
    		Thread b=new Thread(mythread,"B");
    		Thread c=new Thread(mythread,"C");
    		Thread d=new Thread(mythread,"D");
    		Thread e=new Thread(mythread,"E");
    		a.start();
    		b.start();
    		c.start();
    		d.start();
    		e.start();
    	}
    }

    运行结果:

    运行结果

    可以看出这里已经出现了错误,我们想要的是依次递减的结果。为什么呢??

    因为在大多数jvm中,count--的操作分为如下下三步:

    1. 取得原有count值
    2. 计算i -1
    3. 对i进行赋值

    所以多个线程同时访问时出现问题就是难以避免的了。

    那么有没有什么解决办法呢?

    答案是:当然有,而且很简单。

    在run方法前加上synchronized关键字即可得到正确答案

    加上关键字后的运行结果:

    加上关键字后的运行结果

    四 一些常用方法

    4.1 currentThread()

    返回对当前正在执行的线程对象的引用。

    4.2 getId()

    返回此线程的标识符

    4.3 getName()

    返回此线程的名称

    4.4 getPriority()

    返回此线程的优先级

    4.5 isAlive()

    测试这个线程是否还处于活动状态。

    什么是活动状态呢?

    活动状态就是线程已经启动且尚未终止。线程处于正在运行或准备运行的状态。

    4.6 sleep(long millis)

    使当前正在执行的线程以指定的毫秒数“休眠”(暂时停止执行),具体取决于系统定时器和调度程序的精度和准确性。

    4.7 interrupt()

    中断这个线程。

    4.8 interrupted() 和isInterrupted()

    interrupted():测试当前线程是否已经是中断状态,执行后具有将状态标志清除为false的功能

    isInterrupted(): 测试线程Thread对相关是否已经是中断状态,但部清楚状态标志

    4.9 setName(String name)

    将此线程的名称更改为等于参数 name 。

    4.10 isDaemon()

    测试这个线程是否是守护线程。

    4.11 setDaemon(boolean on)

    将此线程标记为 daemon线程或用户线程。

    4.12 join()

    在很多情况下,主线程生成并起动了子线程,如果子线程里要进行大量的耗时的运算,主线程往往将于子线程之前结束,但是如果主线程处理完其他的事务后,需要用到子线程的处理结果,也就是主线程需要等待子线程执行完成之后再结束,这个时候就要用到join()方法了

    join()的作用是:“等待该线程终止”,这里需要理解的就是该线程是指的主线程等待子线程的终止。也就是在子线程调用了join()方法后面的代码,只有等到子线程结束了才能执行

    4.13 yield()

    yield()方法的作用是放弃当前的CPU资源,将它让给其他的任务去占用CPU时间。注意:放弃的时间不确定,可能一会就会重新获得CPU时间片。

    4.14 setPriority(int newPriority)

    更改此线程的优先级

    五 如何停止一个线程呢?

    stop(),suspend(),resume()(仅用于与suspend()一起使用)这些方法已被弃用,所以我这里不予讲解。

     

    5.1 使用interrupt()方法

    我们上面提到了interrupt()方法,先来试一下interrupt()方法能不能停止线程 MyThread.java

    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		super.run();
    		for (int i = 0; i < 5000000; i++) {
    			System.out.println("i=" + (i + 1));
    		}
    	}
    }

    Run.java

    public class Run {
    
    	public static void main(String[] args) {
    		try {
    			MyThread thread = new MyThread();
    			thread.start();
    			Thread.sleep(2000);
    			thread.interrupt();
    		} catch (InterruptedException e) {
    			System.out.println("main catch");
    			e.printStackTrace();
    		}
    	}
    
    }

    运行上诉代码你会发现,线程并不会终止

    针对上面代码的一个改进:

    interrupted()方法判断线程是否停止,如果是停止状态则break

    MyThread.java

    public class MyThread extends Thread {
    	@Override
    	public void run() {
    		super.run();
    		for (int i = 0; i < 500000; i++) {
    			if (this.interrupted()) {
    				System.out.println("已经是停止状态了!我要退出了!");
    				break;
    			}
    			System.out.println("i=" + (i + 1));
    		}
    		System.out.println("看到这句话说明线程并未终止------");
    	}
    }

    Run.java

    public class Run {
    
    	public static void main(String[] args) {
    		try {
    			MyThread thread = new MyThread();
    			thread.start();
    			Thread.sleep(2000);
    			thread.interrupt();
    		} catch (InterruptedException e) {
    			System.out.println("main catch");
    			e.printStackTrace();
    		}
    		System.out.println("end!");
    	}
    
    }

    运行结果:

    运行结果

    for循环虽然停止执行了,但是for循环下面的语句还是会执行,说明线程并未被停止。

     

    5.2 使用return停止线程

    MyThread.java

    public class MyThread extends Thread {
    
    	@Override
    	public void run() {
    			while (true) {
    				if (this.isInterrupted()) {
    					System.out.println("ֹͣ停止了!");
    					return;
    				}
    				System.out.println("timer=" + System.currentTimeMillis());
    			}
    	}
    
    }

    Run.java

    public class Run {
    
    	public static void main(String[] args) throws InterruptedException {
    		MyThread t=new MyThread();
    		t.start();
    		Thread.sleep(2000);
    		t.interrupt();
    	}
    
    }

    运行结果:

    运行结果

    当然还有其他停止线程的方法,后面再做介绍。

    六 线程的优先级

    每个线程都具有各自的优先级,线程的优先级可以在程序中表明该线程的重要性,如果有很多线程处于就绪状态,系统会根据优先级来决定首先使哪个线程进入运行状态。但这个并不意味着低 优先级的线程得不到运行,而只是它运行的几率比较小,如垃圾回收机制线程的优先级就比较低。所以很多垃圾得不到及时的回收处理。

    线程优先级具有继承特性比如A线程启动B线程,则B线程的优先级和A是一样的。

    线程优先级具有随机性也就是说线程优先级高的不一定每一次都先执行完。

    Thread类中包含的成员变量代表了线程的某些优先级。如Thread.MIN_PRIORITY(常数1)Thread.NORM_PRIORITY(常数5), Thread.MAX_PRIORITY(常数10)。其中每个线程的优先级都在Thread.MIN_PRIORITY(常数1)Thread.MAX_PRIORITY(常数10) 之间,在默认情况下优先级都是Thread.NORM_PRIORITY(常数5)

    学过操作系统这门课程的话,我们可以发现多线程优先级或多或少借鉴了操作系统对进程的管理。

    线程优先级具有继承特性测试代码:

    MyThread1.java:

    public class MyThread1 extends Thread {
    	@Override
    	public void run() {
    		System.out.println("MyThread1 run priority=" + this.getPriority());
    		MyThread2 thread2 = new MyThread2();
    		thread2.start();
    	}
    }

    MyThread2.java:

    public class MyThread2 extends Thread {
    	@Override
    	public void run() {
    		System.out.println("MyThread2 run priority=" + this.getPriority());
    	}
    }

    Run.java:

    public class Run {
    	public static void main(String[] args) {
    		System.out.println("main thread begin priority="
    				+ Thread.currentThread().getPriority());
    		Thread.currentThread().setPriority(6);
    		System.out.println("main thread end   priority="
    				+ Thread.currentThread().getPriority());
    		MyThread1 thread1 = new MyThread1();
    		thread1.start();
    	}
    }

    运行结果:

    运行结果

     

    七 Java多线程分类

    7.1 多线程分类

    用户线程:运行在前台,执行具体的任务,如程序的主线程、连接网络的子线程等都是用户线程

    守护线程:运行在后台,为其他前台线程服务.也可以说守护线程是JVM中非守护线程的 “佣人”

    特点:一旦所有用户线程都结束运行,守护线程会随JVM一起结束工作

    应用:数据库连接池中的检测线程,JVM虚拟机启动后的检测线程

    最常见的守护线程:垃圾回收线程

    7.2 如何设置守护线程?

    可以通过调用Thead类的setDaemon(true)方法设置当前的线程为守护线程

    注意事项:

    1.  setDaemon(true)必须在start()方法前执行,否则会抛出IllegalThreadStateException异常
    2. 在守护线程中产生的新线程也是守护线程
    3. 不是所有的任务都可以分配给守护线程来执行,比如读写操作或者计算逻辑

    MyThread.java:

    public class MyThread extends Thread {
    	private int i = 0;
    
    	@Override
    	public void run() {
    		try {
    			while (true) {
    				i++;
    				System.out.println("i=" + (i));
    				Thread.sleep(100);
    			}
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }

    Run.java:

    public class Run {
    	public static void main(String[] args) {
    		try {
    			MyThread thread = new MyThread();
    			thread.setDaemon(true);
    			thread.start();
    			Thread.sleep(5000);
    			System.out.println("我离开thread对象也不再打印了,也就是停止了!");
    		} catch (InterruptedException e) {
    			// TODO Auto-generated catch block
    			e.printStackTrace();
    		}
    	}
    }

    运行结果:

    守护线程

     

    如果你觉得博主的文章不错,欢迎转发点赞。

    参考:

    展开全文
  • java--进程线程

    千次阅读 多人点赞 2018-11-25 22:55:49
    进程线程的概述 在学习线程之前要先知道什么是进程进程就是正在运行的程序,它是系统资源调度的独立单位,并且一个进程可以执行多个任务,而线程就是程序执行的任务,它是程序使用CPU的基本单位,因此也可以说...
  • 进程切换与线程切换的区别?

    万次阅读 多人点赞 2019-07-25 17:00:04
    注意这个题目问的是进程切换与线程切换的区别,不是进程与线程的区别。当然这里的线程指的是同一个进程中的线程。 这个问题能很好的考察面试者对进程和线程的理解深度,有比较高的区分度。 要想正确回答这个问题,...
  • linux线程切换进程切换的方法

    千次阅读 2019-06-08 02:35:00
    进程切换和线程切换在效率上略有不同,相比之下进程切换耗费资源较大,效率要差一些,原因可以参考下面这篇文章 原文链接:https://www.jb51.net/article/102004.htm 进程切换分两步: 1.切换页目录以使用新的地址...
  • 首先要说的是线程状态,了解了线程状态以及状态切换的过程基本上就了解了多线程线程的状态1、新建状态(New):新创建了一个线程对象。 2、就绪状态(Runnable):线程对象创建后,其他线程调用了该对象的start()...
  • 查看进程线程方法 windows 任务管理器可以查看进程和线程数,也可以用来杀死进程 tasklist 查看进程 taskkill 杀死进程 linux ps -fe 查看所有进程 ps -fT -p <PID> 查看某个进程(PID)的所有线程 kill 杀死...
  • 虽然并不是真正意义上的“同一时间点”,而是多个任务或进程共享一个CPU,并交由操作系统来完成多任务间对CPU的运行切换,以使得每个任务都有机会获得一定的时间片运行。 再后来发展到多线程技术,使得在一个程序...
  • 介绍了多线程的概念、优点,以及线程上下文切换时的性能损耗问题
  • Java面试常客——进程线程

    千次阅读 2020-08-10 15:43:21
    线程进程中的一个执行单元,负责当前进程中程序的执行,一个进程中至少有一个线程。一个进程中是可以有多个线程的,这个应用程序也可以称之为多线程程序。 线程调度: 1.分时调度 所有线程轮流使用CPU的使用权,...
  • Java线程面试题

    千次阅读 2019-10-30 13:58:47
    在典型的Java面试中, 面试官会从线程的基本概念问起, 如:为什么你需要使用线程, 如何创建线程,用什么方式创建线程比较好(比如:继承thread类还是调用Runnable接口),然后逐渐问到并发问题像在Java并发编程的...
  • Java线程生命周期与状态切换

    千次阅读 2020-08-05 10:54:55
    前提最近有点懒散,没什么比较有深度的产出。刚好想重新研读一下JUC线程池的源码实现,在此之前先深入了解一下Java中的线程实现,包括线程的生命周期、状态切换以及线程的上下文切换等等。编写...
  • Java线程进程区别

    万次阅读 多人点赞 2018-08-06 21:07:56
    什么是进程,什么是线程? 进程:是并发执行的程序在执行过程中分配和管理资源的基本单位,是一个动态...进程线程的区别 1、地址空间:同一进程的线程共享本进程的地址空间,而进程之间则是独立的地址空间。 2、...
  • Java线程

    千次阅读 多人点赞 2019-05-04 22:28:49
    就会引出线程的概念,而线程进程之间又是息息相关的。 进程:操作系统中一个程序的执行周期称为一个进程线程:一个程序同时执行多个任务。通常,每一个任务就称为一个线程。 多线程:一个进程运行时产生了多...
  • java中的进程线程区别

    千次阅读 2019-03-12 20:19:10
    线程:是进程的一个执行单元,是进程内科调度实体。比进程更小的独立运行的基本单位。线程也被称为轻量级进程。 一个程序由一个或多个进程组成,一个进程由一个或多个线程组成。 【进程 线程的区别】 1.地址空间:...
  • 通俗易懂的 Java 线程进程区别

    千次阅读 2021-01-18 17:28:00
    Java 中,当我们启动 main 函数时其实就是启动了一个 JVM 的进程,而 main 函数所在的线程就是这个进程中的一个线程,也称主线程。 补充:什么是程序 程序是含有指令和数据的文件,被存储在磁盘或其他的数据存储...
  • java进程线程通信方法以及进程与线程的区别
  • 进程线程上下文切换的开销

    千次阅读 2020-09-10 11:21:07
    进程线程上下文切换的开销 虚拟内存与地址空间映射关系 虚拟内存是操作系统为每个进程提供的一种抽象,每个进程都有属于自己的、私有的、地址连续的虚拟内存,当然我们知道最终进程的数据及代码必然要放到物理...
  • 说到线程,很多人最直观的感受就是多线程。本章不讨论高并发、多线程之类的。返璞归真,咱们来讨论讨论线程这个东西到底是什么东西,着眼于线程本身,我们怎么玩? 为了了解线程,这里我不得不搬出来线程的概念了:...
  • Java面试题大全(2020版)

    万次阅读 多人点赞 2019-11-26 11:59:06
    发现网上很多Java面试题都没有答案,所以花了很长时间搜集整理出来了这套Java面试题大全,希望对大家有帮助哈~ 本套Java面试题大全,全的不能再全,哈哈~ 一、Java 基础 1. JDK 和 JRE 有什么区别? JDK:Java ...
  • java并发编程-进程线程调度基础

    万次阅读 2020-02-20 14:38:54
    操作系统一般是按照一定策略,定期给每个活动的进程执行其内部程序的机会,并且每次只执行一小段时间,然后操作系统利用中断强行退出执行,将当前程序信息压栈,然后开始执行下一个进程的一小段程序。通过这样不断...
  • JDK源码中很多Native方法,特别是多线程、NIO部分,很多功能需要操作系统功能支持,作为Java程序员,如果要理解和掌握多线程和NIO等原理,就需要对操作系统的原理有所了解。 CPU 上下文切换 多任务操作系统中,多于...
  • java 进程线程的区别与联系

    千次阅读 2016-12-27 21:12:26
     进程:是程序的一次动态执行,它对应着从代码加载,执行至执行完毕的一个完整的过程,是一个动态的实体,它有自己的生命  周期。它因创建而产生,因调度而运行,因等待资源或事件而被处于等待状态,因完成任务...
  • 一、进程线程的区别 1、进程可以理解成程序的一次执行(即动态的),所以一个程序可以对应一个或多个进程(程序的多次执行),而一个进程往往包含一个或多个线程 2、每个进程有独立的地址空间,包含资源。而线程...
  • Java线程的上下文切换

    千次阅读 2017-05-13 18:26:18
    对于上下文切换不同的操作系统模式也不尽相同,这里...时间片是CPU分配给各个线程的时间,因为时间片非常短,所以CPU通过不停地切换线程执行,让我们感觉多个线程是同时执行的,时间片一般是几十毫秒(ms) CPU通过
  • 关于Java中的进程线程的理解

    千次阅读 2017-09-04 14:16:20
    关于Java中的程序,进程线程的详解... 程序:一段静态的代码,一组指令的有序集合,它本身没有任何运行的含义,它只是一个静态的实体,是应用软件执行的蓝本。 进程:是程序的一次动态执行,它对应着从代码加载,...
  • 多任务操作系统可以通过周期性地将CPU从一个进程切换到另一个进程,来实现同时运行多个(进程)程序,尽管使得每个线程看起来在其执行过程都是歇歇停停。进程被操作系统互相隔开,因此不会彼此干涉,这使得用进程...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 108,646
精华内容 43,458
关键字:

java进程线程切换

java 订阅