1. 什么是进程?

对其概念需要自行goole,简单理解就是:进程是计算机系统进行资源分配和调度的基本单位,是正在运行程序的实体;每一个进程都有它自己的内存空间和系统资源;进程是线程的容器。如:打开IDEA写代码是一个进程,打开有道词典也是一个独立的进程。

如果我们在用IDEA写代码的同时打开有道词典那就是多进程,多进程具有独立性,动态性,并发性,异步性。鉴于多数人混淆并行和并发,在此简单介绍:

  • 并发:多个CPU实例同时执行一段代码或处理逻辑,具有物理意义上的同时发生。
  • 并行:计算机通过算法调度获得CPU时间片继而执行属于自己的执行计划,CPU的高效切换在转瞬间完成,让用户感觉像是同时发生,实际上只是逻辑上的同时发生。

那么IDEA和有道词典是同时进行的吗?取决于CPU的个数,单个CPU在某个时间点上只能做一件事情,而多核(多个CPU)可以同时进行。多进程的意义在于,提高了CPU使用率。值得一提的是,Java是不能够通过调用系统资源来开启一个进程的,例如在windows系统中,Java通过调用C语言底层代码来开启进程。

2. 什么是线程?

线程:是进程中的单个顺序控制流,计算机最小的执行单元,一条执行路径。一个进程如果只有一条执行路径,成为单线程程序;如果有多条执行路径,则成为多线程程序;多线程共享该进程的全部资源。如:打开QQ后,好友聊天属于一条线程,浏览QQ空间又属于一条线程。

假如我们的计算机只有一个CPU,那么CPU在某一个时刻只能执行一条指令,线程只有得到CPU时间片才能拥有使用权,才可以执行指令,那么Java是如何对线程进行调用的呢?

线程调用的两种模型:

  1. 分时调度模型 : 所有的线程轮流获得CPU的使用权,平均分配每个线程占用CPU
  2. 抢占式调度模型:优先让优先级高的线程使用CPU,如果优先级相同,那么会从中随机选取一个,优先级高的线程获取的CPU时间片相对多一些。
  3. Java使用的是抢占式调度模型。
  4. 可利用API设置和获取线程优先级。 

    public final int getPriority()

    public final void setPriority(int newPriority)

现在大致了解进程和线程之间的关系后,再来看Java程序运行原理。

Java命令会启动Java虚拟机,启动JVM,等于启动了一个进程。该进程会自动启动一个"主线程",然后主线程去调用某个类的main方法,所有main方法运行在主线程中,在此之前的所有程序都是单线程的。Java虚拟机的启动是多线程的,因为JVM启动至少启动了垃圾回收线程和主线程。

3. 多线程的意义

进程具有独立性,多进程之间是没有共享资源的,但是多线程可以共享内存资源,而且十分简单。系统创建进程是需要为该进程重新分配系统资源,浪费了大量资源,但创建线程的代价要小很多,因此多线程实现多任务的并发要比多进程的效率高。

总结起来:

  1. 共享内存资源
  2. 并发效率高
  3. 多线程的作用不是提高执行速度,而是提高应用程序的使用率

而多线程的实际应用包括:

    • 浏览器必须能同时下载多张图片
    • 一台服务器必须能同时响应多个用户请求
    • JVM本身就在后台提高了一个超级线程进行垃圾回收 

4. Java多线程实现

一)继承Thread类,复写run()方法

 /**
* @author supiaol
* @date 2019/3/7
* @time 9:26
*/
public class MyThread extends Thread { //多线程运行的代码块
public void run() {
System.out.println("Thread is running");
} public static void main(String[] args) { MyThread myThread1 = new MyThread();
MyThread myThread2 = new MyThread(); //运行多线程
myThread1.start();
myThread2.start(); }
}

Thread类本质上是实现Runable接口的一个实例。Thread 类中有一些关键属性,如:name属性代表线程的名称,可以通过Thread类的构造器中参数来指定线程名称;priority属性代表线程优先级,上文提高优先级高的线程抢占CPU时间可能性越大,默认优先级为5,最小值为1,最大值为10;daemon属性表示线程是否是守护线程,target属性代表要执行的任务。

下面是Thread类中常用的api:

1. run()方法   新建线程(新建状态)

需要明确的是run()方法不是用来运行线程的,也不需要用户调用,当线程获得CPU执行时间,会进入run()方法执行代码块。

2. start()方法    启动线程(就绪状态)

线程启动的方法,调用start()方法后,系统会开启一个新的线程用来执行用户定义的任务,在此过程中,为线程分配系统资源。需要注意的是,调用start()方法后,并不会立即执行定义的任务,而是赋予线程可以抢占CPU时间片的资格,只有得到CPU时间片才能执行计划任务。

3. sleep()方法   睡眠线程(堵塞状态)

线程睡眠,必须指定睡眠时间,在适当的位置调用sleep(),让该线程睡眠,也就是交出CPU,让CPU来执行其它任务。特别需要关注的是,sleep()方法不会释放锁或者监视器,也就是说如果当前线程持有某个对象的锁,那么即使调用sleep()方法,其他线程也无法访问该对象,关于该方法和锁的关系会在后续详细说明和演示。

4.yield()方法    礼让线程(就绪状态)

调用yield()方法同样可以让该线程交出CPU时间片,失去执行权,类似于sleep()方法,同样不会释放锁对象或者监视器,而区别之处在于,yield()不能控制具体交出CPU的时间,而且交出的CPU时间片只能允许相同优先级的线程获取,该进程返回到就绪状态而不是堵塞状态。

5.join()方法      线程加入(堵塞状态)

join方法有三个重载版本:

join()
join(long millis) //参数为毫秒
join(long millis,int nanoseconds) //第一参数为毫秒,第二个参数为纳秒

它们的区别在于指定的参数,假如我们在main()所属的主线程中调用另外一个从线程thread.join()方法,则main()方法失去执行权,只有等到thread线程执行完毕或者等待一定的时间后重新获得执行权。如何调用无参join()方法,需要等待thread线程执行完毕,调用指定时间的带参join()方法,则等到指定时间过后获取执行权。

通过查看源码发现,join实际上调用了wait()方法实现主线程等待,至于wait()方法,后面学习线程安全时候着重讲述,在此先做了解。

 public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0; if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//空参join 需要等待从线程执行完毕
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
//带参join,等待指定时间后重新获得执行权
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}

6. interrupt()  线程中断(堵塞状态)

顾名思义,interrupt即中断的意思。调用interrupt()方法能够使处于堵塞状态的线程抛出异常,其实质上就是用来中断处于堵塞状态的线程,通常配合isInterrupted()方法来停止正常运行的线程。

7. stop()方法 线程停止(线程中断)

stop方法是一个已经被废弃的方法,自身不安全。因为调用stop方法会直接终止run方法的调用,并且抛出ThreadDeath异常,如果该线程调用stop方法之前持有某个对象锁,之后会完全释放锁对象,导致对象状态不一致。

8.destory() 方法  已被废弃,不会用到。

(二)  实现Runnable接口,重写run()方法。

 /**
* @author supiaol
* @date 2019/3/7
* @time 14:49
*/
public class MyThread extends OtherClass implements Runnable {
@Override
public void run() {
System.out.println(Thread.currentThread().getName() + "is running");
} public static void main(String[] args) {
MyThread myThread = new MyThread();
Thread thread1 = new Thread(myThread);
Thread thread2 = new Thread(myThread);
thread1.setName("线程1:");
thread2.setName("线程2:");
thread1.start();
thread2.start();
}
}

实现Runnable接口实现多现成的好处就在于弥补Java单继承的缺陷。更适合多个相同程序的代码去处理一个资源的情况,这样线程同程序的代码,数据有效分离,较好的体现了面向对象的设计思想。区别于继承Thread类启动线程,实现Runnable接口启动线程时,需要将实现Runnable接口的实例作为target目标传入Thread实例,然后调用start()方法启动线程。

如果需要对线程设置名称,可以通过线程对象调用setName方法进行设置,也可以通过Thread的构造方法设置,而getName()方法可以获取线程名称,也可以通过Thread.currentThread().getName()方法获取当前线程的名称。

(三) 基于线程池实现多线程,用到不多,在此不多介绍

5. 线程的生命周期

  1. 新建:创建线程对象,从new一个线程对象到调用start()方法之间都是新建状态
  2. 就绪:调用start()方法后,线程对象已经启动,但是还没有获取到CPU的执行权
  3. 运行:获取到CPU时间片,开始执行run()方法中的代码
  4. 堵塞:失去执行权,回到就绪状态。
  5. 结束:代码运行完毕,或者main方法执行完毕,线程消亡

以上就是一个线程完整的生命周期,一个线程最基本的生命周期包括:新建,就绪,运行,结束。

从零开始学习Java多线程(一)的更多相关文章

  1. 从零开始学习Java多线程(三)

    本文主要对Java多线程同步与通信以及相关锁的介绍. 1 .Java多线程安全问题 Java多线程安全问题是实现并发最大的问题,可以说多线程开发其实就是围绕多线程安全问题开发,涉及之深,不是简简单单一 ...

  2. 从零开始学习Java多线程(二)

    前面已经简单介绍进程和线程,为后续学习做铺垫.本文讨论多线程传参,Java多线程异常处理机制. 1. 多线程的参数传递 在传统开发过程中,我们习惯在调用函数时,将所需的参数传入其中,通过函数内部逻辑处 ...

  3. 从零开始学习java一般需要多长时间?

    从零开始学习java一般需要多长时间? 其实学java一般要多久?因人而异,例如一个零基础的小白自学java,每天学习8个小时来算,而且在有学习资料的基础上,每天学习,从零到找到工作,起码要半年起步, ...

  4. 从火箭发场景来学习Java多线程并发闭锁对象

    从火箭发场景来学习Java多线程并发闭锁对象 倒计时器场景 在我们开发过程中,有时候会使用到倒计时计数器.最简单的是:int size = 5; 执行后,size—这种方式来实现.但是在多线程并发的情 ...

  5. 从零开始学习JAVA(入门基础)

    目录 博主从零开始学习JAVA(入门基础) 1.搭建JAVA开发环境 卸载JDK(未安装的请忽略) 安装JDK 2.编程语言中,何为编译型与解释型 编译型 解释型 3.第一个JAVA应用程序 4.JA ...

  6. 我也学习JAVA多线程-join

    在工作中,挺少遇到join关键字,但很多多线程资料和面试过程中,初中级开发工程师总会遇到join. 今天一起学习下join. join的作用:等待指定的时间(当为0时,一直等待),直到这个线程执行结束 ...

  7. 从零开始学习java(一)java基础语法

    从公司裸辞一个月,原本工作是做VB的,现在想从事java:在找工作的时候总是要什么项目经验,多少有些不爽,所有语言都有共 通性,我就不信java有这么难?给自己点时间来学习.坚持一个月自学,看看自己的 ...

  8. Java 多线程学习笔记:生产者消费者问题

    前言:最近在学习Java多线程,看到ImportNew网上有网友翻译的一篇文章<阻塞队列实现生产者消费者模式>.在文中,使用的是Java的concurrent包中的阻塞队列来实现.在看完后 ...

  9. java多线程系列(一)

    java多线程技能 前言:本系列将从零开始讲解java多线程相关的技术,内容参考于<java多线程核心技术>与<java并发编程实战>等相关资料,希望站在巨人的肩膀上,再通过我 ...

随机推荐

  1. set nocount on/off的作用,可配合存储过程使用

    当set nocount 为NO的时候,不返回计数(受Transact-SQL语句影响行数) 当set nocount 为OFF时,返回计数(默认返回) 当 SET NOCOUNT 为 ON 时,将不 ...

  2. CentOS7──xxx is not in the sudoers file

    提示"xxx is not in the sudoers file. This incident will be reported.其中 ”XXX“是你的用户名,也就是你的用户名没有权限使用 ...

  3. PHP 不同类型之间的松散和严格比较

    原始数据类型 在比较之前先简单介绍一下PHP的9种原始数据类型,包括 四种标量类型: boolean(布尔型) integer(整型) float(浮点型,也称作 double) string(字符串 ...

  4. CentOS-7.3 设置静态 ip

    1. VMnet8 必须设置固定 ip,否则会发生:虚拟机可以访问主机和外网,但是主机 windows 却访问不了虚拟机 2. 虚拟网络编辑器设置网关 IP(G): 必须与 VMnet8 的 ip 在 ...

  5. PostgreSql之在group by查询下拼接列字符串

    首先创建group_concat聚集函数: CREATE AGGREGATE group_concat(anyelement) ( sfunc = array_append, -- 每行的操作函数,将 ...

  6. 随手记一 2018/04/23 Ajax基础了解

    1.什么是ajax? 主要目的是用来实现客户端和服务器之间的异步通信,实现页面的局部刷新 2.同步和异步! 同步:当多个线程同时向一个数据发送请求时,必须是A先执行完毕才可以给B,会出现阻塞的情况,但 ...

  7. SQL语句——exists和in区别

    表结构及数据 user表 order表 in select * from table A where id in (xxxxxxxxxxx):满足条件的数据会被查出来: 先查询子查询的表,然后将内表. ...

  8. CentOS 离线安装 MYSQL+APACHE+PHP

    一.MYSQL安装 下载MYSQL安装包:MySQL-client-XXX.rpm   MySQL-server-XXX.rpm   MySQL-devel-XXX.rpm 如有冲突,要先删除原来的M ...

  9. eclipse中出现An internal error occurred during: "Initializing Java Tooling"

    关于这个问题我查了一下,就是删除.projct文件夹下的文件. 自己试了一下,这个可以及解决问题可是会出现新的问题. 1.SVN关联没了,这样做你的svn信息都没了,项目还要重新导一遍 2.出现了新的 ...

  10. MongoDB --- 01. 安装, 第三方客户端

    一.安装 1.选择默认安装 ①完成后进入目录查看 "C:\Program Files\MongoDB\Server\3.4\bin" ② 配置环境变量 ③ 在 c盘创建目录(数据库 ...