【JAVA并发第一篇】Java的进程与线程
1、进程与线程
1.1、进程
进程可以看作是程序的执行过程。一个程序的运行需要CPU时间、内存空间、文件以及I/O等资源。操作系统就是以进程为单位来分配这些资源的,所以说进程是分配资源的基本单位。
(1)、进程是动态的,程序是静态的
程序是静态的,它本身作为一种软件资源可以长期保存在磁盘(常说的硬盘)中。比如QQ,QQ作为一个程序,其本身保存在计算机的磁盘上。此时,它并没有得到CPU、内存、I/O等资源。因此当前的QQ程序只是一个静态的程序并不能给我们实现视频、语音等功能。
但当QQ程序开始执行时,操作系统就会将QQ程序从磁盘中装入内存,同时也会在操作系统中创建属于QQ的进程,这些新创建的QQ进程会得到操作系统分配的CPU、内存、I/O等资源,得到这些资源后,创建出来的QQ进程就可以实现视频和语音的功能。而当我们点击退出QQ后,这些进程就会立刻消亡,分配得到的资源也会被释放。
由此可以看出,QQ进程是QQ程序的执行过程,它是动态的,有一定的生命周期,会动态的产生和消亡。进程是资源分配的单位。
(2)、程序与进程并不是一 一对应的关系
虽然进程可以看作是程序的执行过程,但并非一个程序对应一个进程,即二者并不是一 一对应的关系。程序与进程的关系可能有以下几种:
①一个程序产生一个进程:比如Win10的记事本程序(notepad.exe),每打开一个txt文本文件,就只会启动一个记事本进程。
②一个程序产生多个进程:比如浏览器启动时,一般都会产生多个进程,这些进程相互配合,互相影响,共同实现浏览器的功能。
③一个程序可以被多个进程共用:比如一个记事本程序在执行时,就只会产生一个进程。但当我们再用记事本程序打开一个文件时,此时就会再次在操作系统中创建一个新的进程,这个新的进程同样也会调用记事本程序。即在此刻,计算机磁盘中只有一个记事本程序,但是操作系统中却有两个记事本进程在共用这个程序,且这两个进程互不影响。
④一个进程又可能要用到多个程序:比如,用C语言写了一个helloword.c的程序。此时,输入命令gcc helloword.c
。那么操作系统会创建一个进程,它调用c编译程序,对helloword.c文件进行编译。这个进程在执行编译的过程中,除了调用c编译程序和我们编写的helloword程序外,还会用到c预处理程序、连接程序、结果输出程序等。
1.2、线程
线程从属于进程,只能在进程的内部活动,多个线程共享进程所拥有的的资源。如果把进程看作是完成许多功能的任务的集合,那么线程就是集合中的一个任务元素,负责具体的功能。虽然CPU、内存、I/O等资源分配给了进程,但实际上真正利用这些资源并在CPU上执行的却是线程,即真正完成程序功能的是线程。
因为进程作为这些资源的拥有者,它的负载很重,在进程的创建、切换、删除过程中的时间和空间开销都很大。所以目前主流的操作系统都只将进程作为资源的拥有者,而把CPU调度和运行的属性赋予了线程。
比如打开浏览器程序,会产生相应的进程,浏览器进程中包含有许多线程,如HTTP请求线程,I/O线程,渲染线程,事件响应线程等。浏览器进程拥有着内存和I/O资源等,但当我们在浏览器中输入文字时,真正使用I/O资源接收我们输入的文字,并在CPU处理文字的却是浏览器进程中的I/O线程。即真正完成浏览器文字输入功能的是线程。
现代很多操作系统支持让一个进程包含多个线程,从而提高程序的并行程度和资源的利用率。
1.3、线程与进程的关系
①一个进程可以有多个线程,但至少要有一个线程,并且一个线程只能在一个进程的地址空间内活动。
②资源分配给进程,而一个进程内的所有线程共享该进程的所有资源。
③CPU分配给的是线程,即真正在CPU上运行的是线程。
④进程间通信较为复杂,同一台计算机的进程通信称为 IPC(Inter-process communication)。
而不同计算机之间的进程通信,则需要通过网络,并遵守共同的协议,例如 HTTP等。
⑤线程通信相对简单,因为它们共享进程内的内存,一个例子是多个线程可以访问同一个共享变量。
⑥线程更轻量,线程上下文切换成本一般上要比进程上下文切换低。
2、Java中的进程与线程
2.1、JVM进程
我们知道Java语言是需要运行在JVM上的。实际上,JVM也是一个软件程序,这就意味着它执行起来也会在操作系统中创建进程,即JVM进程,通常又叫JVM实例。而我们所写的main方法,实际上就是JVM进程中主线程的所在。
从操作系统的角度来看,我们常说的Java程序,应该包括JVM和我们编写的Java代码。
当我们写完Java代码,并编译成class文件后,使用Java命令执行main方法;或者直接在IDE启动main方法时,JVM程序就会执行,操作系统会将其从磁盘中装入内存,并创建一个JVM进程,随后启动主线程,主线程会去调用某个类的 main 方法,因此这个主线程就是我们写的main方法所在。
实际上,JVM本身就是一个多线程应用,即使我们在代码中并没有手动的创建线程,JVM进程也并不是只有一个主线程,而是也会有其他线程。这些线程完成着JVM的功能,如GC线程负责回收JVM使用过程中的垃圾对象。JVM进程启动完成后,必然会有的线程如下:
线程 | 作用 |
---|---|
main | 主线程,执行我们指定的启动类的main方法 |
Reference Handler | 处理引用的线程 |
Finalizer | 调用对象的finalize方法的线程 |
Signal Dispatcher | 分发处理发送给JVM信号的线程 |
Attach Listener | 负责接收外部的命令的线程 |
至此,我们知道了,启动一个Java程序,本质上就是启动JVM程序,并在操作系统中创建一个JVM进程。这个JVM进程会由操作系统分配许多资源,如内存、I/O等。JVM进程中包含有许多线程,这些线程共享JVM进程分配到的资源,同时这些线程也是CPU核心上执行的实体,它们完成着JVM所具有的功能。
那么如果我们启动两个Java程序,会生成多少个JVM进程呢?
我们编写两个Java程序,其有代码如下:
processTest01程序
public class processTest01 {
public static void main(String[] args) throws InterruptedException {
System.out.println("我是测试01");
byte[] a = new byte[1024*1024*50]; //在堆中占50MB
processTest02 test02 = new processTest02();
Thread.sleep(1000*60*30); //休眠三十分钟
}
}
processTest02程序
public class processTest02 {
public static void main(String[] args) throws InterruptedException {
System.out.println("我是测试02");
byte[] a = new byte[1024*1024*900]; //在堆中占900MB
Thread.sleep(1000*60*30); //休眠三十分钟
}
}
我们将编译这两个程序,并分别用Java命令启动它们。看看两个Java程序会在操作系统中创建了多少个进程。
打开JDK自带的jvisualvm.exe
,这是JDK提供的查看Java进程和线程相关信息的工具程序,在自己电脑上的JDK目录下(Win10):Java\jdk1.8.0_131\bin
。如图所示:
可以继续使用jvisualvm
,查看进程中线程的相关情况:
pid(进程号)为2146的进程:
pid(进程号)为4196的进程:
可以看出,启动多少个Java程序,就会创建多少个JVM进程,也称之为JVM实例。而每一个JVM实例都是独立的,它们互不影响。这也是前面所说的一个程序可以被多个进程共用的情况。
一个JVM进程就是一个JVM的实例。JVM的实例在执行Java程序的过程中会把它管理的内存划分为不同区域,称之为运行时数据区,如下所示:
2.2、Java的线程
我们知道,线程从属于进程,是CPU调度执行的单位,各个线程共享进程内的资源。目前主流的操作系统都支持了线程。在实现了线程的操作系统中,一个进程中必然有至少一个操作系统的线程,这种属于操作系统的线程被称为内核线程(kernel-Level Thread,KLT)。
而各个应用程序实现多线程的方式主要有三种:
①内核线程1:1实现
内核线程即操作系统本身的线程,1:1意味着程序中的线程与操作系统中的内核线程是直接对应的。这种线程的创建是由操作系统来完成的,同时也是由操作系统来负责调度的。这种内核线程的切换需要硬件支持,切换所需的时间也较长,但其优点是一个线程阻塞了,其他线程也可以执行,则进程就能继续工作。但一般来说,程序中的线程不会直接使用内核线程,而是使用它提供的高级接口,称之为轻量级进程(Light Weight Process,LWP)。虽然名称变了,但其本质上还是操作系统的内核线程。每个轻量级进程,都由一个内核线程支持,所以他们都可以独立调度,由操作系统的调度器(Scheduler)负责调度。总的来说就是,程序中的线程是操作系统的内核线程。
可以看出,使用内核线程1:1实现的程序可以同时在多个CPU核心上跑。也就是说,程序执行产生的一个进程中的多个线程在同一时刻可能会在不同的CPU核心上运行。这对于一个程序来说,大大加快了运行效率。
②用户线程1:N实现
用户线程指的是由用户程序自主实现,不需要操作系统来实现的线程,一个线程不是内核线程,就可以认为是用户线程。用户线程虽然不需要操作系统来实现,但在实现了线程的操作系统中,一个进程中必然要有一个内核线程来支持运行。1:N中的1就是一个内核线程的意思。而N指的是用户程序自主实现的多用户线程,操作系统无法得知这些用户线程的存在,因为这些用户线程都是在用户程序内部建立、切换和销毁的。由于用户线程不需要操作系统的帮助,所以对于用户线程的操作可以非常快,消耗低,且不需要硬件的支持。同时,用户线程的的数量不受操作系统的限制。在没有实现多线程的操作系统中也可以实现多线程程序。但由于用户线程需要映射为内核线程才能执行,所以如果一个线程阻塞,那么所有的线程都将阻塞,进程也无法继续工作。线程的调度也是由用户程序自主实现。总的来说,用户线程就是用户的程序自主实现的线程,多个用户线程对应着一个操作系统的内核线程。
可以看出,用户线程1:N实现的程序,一个进程中的多用户线程在同一时刻只能在一个CPU核心上运行,因为只有一个内核线程支持着这个进程。从操作系统角度来看,这就是一个单线程的进程。当然,如果一个实现了用户线程的程序执行产生了多个进程,那么实际上这个程序也可能在多个CPU核心上跑。目前很少有程序实现这种用户线程了。
③混合N:M实现
混合实现即用户线程和内核线程一起使用的实现方式。在这种混合实现下,即存在用户线程,又存在轻量级进程(内核线程)。用户线程还是由用户程序自主实现,这样用户线程的创建、切换、销毁依然快速且消耗低。而一个用户线程的集合(包含一个或多个用户线程)又与一个内核线程映射。多个用户线程的集合,就是N:M实现中的N,而M自然指的是多个内核线程。这样的情况下,也可以继续使用操作系统的调度功能,而且由于一个内核线程支持着一个用户线程的集合,所以一个用户线程阻塞,并不会阻塞其他用户线程,进程也能继续工佐。总的来说,混合实现就是一个用户线程的集合对应着一个内核线程,一个进程中会存在多个用户线程集合,则会有多个内核线程来支持运行。
可以看出,由于用户线程集合映射到了一个内核线程上,而一个进程又有多个用户线程集合。所以使用混合实现多线程的程序,进程中也可能存在多个用户线程在不同CPU核心上执行的情况。
Java线程的实现
介绍完程序实现多线程的三种方式,那么Java是如何实现多线程的呢?
首先,Java虚拟机规范并未规定要如何实现多线程,所以Java的线程都是由虚拟机来具体实现,不同的虚拟机实现线程的方式可能都不相同。不过,在JDK1.2之前,早期的虚拟机都采用的是用户线程1:N的实现方式。而在JDK1.3之后,大部分的虚拟机都采用了内核线程1:1实现的方式,包括我们最常用的HostSpot虚拟机。
这就意味着,我们在平常的开发中,不论是JVM程序自己创建的线程,还是我们手动编码创建的线程,实际上都是直接1:1映射到了操作系统的内核线程。 这一内核线程由操作系统来创建,且虚拟机不会去干涉线程调度。Java的线程何时交给CPU核心去执行,交给哪个CPU核心,线程有多少CPU核心的执行时间,线程何时冻结、唤醒等等,都交给操作系统去完成,也都是操作系统全权决定(不过Java虚拟机也可以设置线程优先级来给操作系统的线程调度提供建议)。
3、多线程与并行、并发
两个或两个以上的线程在同一时刻发生就称为并行,如两个线程在同一时刻在两个不同的CPU核心上执行,则可以说这两个线程是并行执行。
两个或两个以上的线程在同一时间段内发生则称为并发,比如两个线程在一个极短的时间段上分别在同一个CPU核心上执行,则可以说这两个线程是并发执行。
并行与并发的关键就在于是否为同一时刻执行,并行是在同一时刻执行,而并发则是在极短的时间内执行。
在一个CPU核心中,线程实际是并发执行的,操作系统中有一个组件叫做任务调度器,将cpu核心的时间片(windows下时间片最小约为 15 毫秒)分给不同的程序使用,只是由于cpu核心在线程间(时间片很短)的切换非常快,给人的感觉是同时运行的,但实际上一个CPU核心同一时刻只能支持一个线程运行。即如果是在单个CPU核心的操作系统中,Java程序(包含JVM)本身虽然是多线程的,但实际上,同一时刻只能有一个Java线程在执行。
但目前的计算机已经很少有单个核心的CPU了,目前即使是个人使用的计算机都是多个核心的CPU了,每个核心都可以独立调度运行线程,这就意味着线程之间可以并行执行。
可以看出,在多核心的CPU下,线程之间是可以并行执行的。但即使是拥有多个CPU核心的计算机,CPU核心的数量始终是有限的,而一个操作系统中的线程数远远多于CPU核心数,所以线程之间大部分情况下是属于并发状态的。即线程之间是在极短时间下交替在CPU核心上执行的。
需要注意的是,在单个CPU核心下,多线程其实是没有太大意义的,因为始终只能有一个线程在CPU核心上执行,而线程间的切换是需要耗费时间和资源的。但多核CPU可以同时执行线程,如果在多核CPU中还是使用单线程,无疑是对CPU的巨大浪费。并发的最主要的目的就是最大限度利用CPU资源。
但并发并不是线程特有的,进程之间也可以并发。有些语言实现并发就是使用进程来进行并发,如PHP。不过Java的并发依然是依赖于多线程,即多线程是Java实现并发的一种方式。
【JAVA并发第一篇】Java的进程与线程的更多相关文章
- 从.Net到Java学习第一篇——开篇
以前我常说,公司用什么技术我就学什么.可是对于java,我曾经一度以为“学java是不可能的,这辈子不可能学java的.”结果,一遇到公司转java,我就不得不跑路了,于是乎,回头一看N家公司交过社保 ...
- 从源码学习Java并发的锁是怎么维护内部线程队列的
从源码学习Java并发的锁是怎么维护内部线程队列的 在上一篇文章中,凯哥对同步组件基础框架- AbstractQueuedSynchronizer(AQS)做了大概的介绍.我们知道AQS能够通过内置的 ...
- Java并发编程(二)如何保证线程同时/交替执行
第一篇文章中,我用如何保证线程顺序执行的例子作为Java并发系列的开胃菜.本篇我们依然不会有源码分析,而是用另外两个多线程的例子来引出Java.util.concurrent中的几个并发工具的用法. ...
- Java并发编程:Java的四种线程池的使用,以及自定义线程工厂
目录 引言 四种线程池 newCachedThreadPool:可缓存的线程池 newFixedThreadPool:定长线程池 newSingleThreadExecutor:单线程线程池 newS ...
- Java多线程原理+基础知识(超级超级详细)+(并发与并行)+(进程与线程)1
Java多线程 我们先来了解两个概念!!!! 1.什么是并发与并行 2.什么是进程与线程 1.什么是并发与并行 1.1并行:两个事情在同一时刻发生 1.2并发:两个事情在同一时间段内发生 并发与并行的 ...
- 8成以上的java线程状态图都画错了,看看这个-图解java并发第二篇
本文作为图解java并发编程的第二篇,前一篇访问地址如下所示: 图解进程线程.互斥锁与信号量-看完还不懂你来打我 图形说明 在开始想写这篇文章之前,我去网上搜索了很多关于线程状态转换的图,我惊讶的发现 ...
- java核心知识点学习----并发和并行的区别,进程和线程的区别,如何创建线程和线程的四种状态,什么是线程计时器
多线程并发就像是内功,框架都像是外功,内功不足,外功也难得精要. 1.进程和线程的区别 一个程序至少有一个进程,一个进程至少有一个线程. 用工厂来比喻就是,一个工厂可以生产不同种类的产品,操作系统就是 ...
- Java并发编程:如何创建进程?
转载自:http://www.cnblogs.com/dolphin0520/p/3913517.html 在前面一篇文章中已经讲述了在进程和线程的由来,今天就来讲一下在Java中如何创建线程,让线程 ...
- Java并发容器篇
作者:汤圆 个人博客:javalover.cc 前言 断断续续一个多月,也写了十几篇原创文章,感觉真的很不一样: 不能说技术有很大的进步,但是想法确实跟以前有所不同: 还没开始的时候,想着要学的东西太 ...
随机推荐
- 基于gin的golang web开发:使用数据库事务
在前文介绍访问数据库时介绍了github.com/jmoiron/sqlx包,本文基于这个包使用数据库事务. defer 在使用数据库事务之前,首先需要了解go语言的defer关键字.defer是go ...
- create event[]
一.前言自 MySQL5.1.6起,增加了一个非常有特色的功能–事件调度器(Event Scheduler),可以用做定时执行某些特定任务(例如:删除记录.对数据进行汇总等等),来取代原先只能由操作系 ...
- haproxy 思考
通过代理服务器在两个TCP接连之间转发数据是一个常见的需求,然后通常部署的时候涉及到(虚拟)服务器.真实服务器.防护设备.涉及到多个ip地址相关联,改动一个IP就需要修改配置. 比如反向服务器部署的时 ...
- 聊一聊Token
阔别了一阵,再次提笔,有些感慨. 聊聊Token吧,以前工作中总是遇到. 首先明确什么是token? 一些关键标签:服务端签发的一个字符串,客户端的请求令牌,用户第一次使用用户名密码登录后生成,在to ...
- 限流10万QPS、跨域、过滤器、令牌桶算法-网关Gateway内容都在这儿
一.微服务网关Spring Cloud Gateway 1.1 导引 文中内容包含:微服务网关限流10万QPS.跨域.过滤器.令牌桶算法. 在构建微服务系统中,必不可少的技术就是网关了,从早期的Zuu ...
- Spring扩展之二:ApplicationListener
1.介绍 用于监听应用程序事件的接口. 子接口:GenericApplicationListener,SmartApplicationListener. 通过ApplicationEvent类和App ...
- C#高级编程之泛型二(泛型类型约束、类型、反射与泛型)
泛型类型约束 简言之:对泛型类型进行约束,细化,限定. MSDN的定义:泛型定义中的 where 子句指定对用作泛型类型.方法.委托或本地函数中类型参数的参数类型的约束,意思就是可以有泛型类.泛型方法 ...
- 深入理解r2dbc-mysql
目录 简介 r2dbc-mysql的maven依赖 创建connectionFactory 使用MySqlConnectionFactory创建connection 执行statement 执行事务 ...
- 超级无敌详细使用ubuntu搭建hadoop完全分布式集群
一.软件准备 安装VMware 下载ubuntu镜像(阿里源ubuntu下载地址)选择自己适合的版本,以下我使用的是18.04-server版就是没有桌面的.安装桌面版如果自己电脑配置不行的话启动集群 ...
- 安恒DASCTF 四月战 WP
web1 打开提就是源码审计 考点:反序列化POP链.反序列化字符串逃逸 show_source("index.php"); function write($data) { ret ...