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. Ubuntu MariaDB PhpMyAdmin

    root@www:~# apt-get -y install phpmyadmin php-mbstring php-gettext # select which one you using (thi ...

  2. C# 弹出确定、取消窗口

    if (MessageBox.Show("确定要退出吗?", "", MessageBoxButtons.OKCancel, MessageBoxIcon.Qu ...

  3. JDBC中常用的接口

    JDBC常用的接口DriverManager 驱动管理器获得数据库链接 Connection 数据库链接接口 Statement 语句接口,用来静态操作SQL语句 PreparedStatement ...

  4. 手机APP应用外网访问本地WEB应用

    手机APP应用外网访问本地WEB应用 本地安装了WEB服务端,手机APP应用只能在局域网内访问本地WEB,怎样使手机APP应用从公网也能访问本地WEB? 本文将介绍具体的实现步骤. 1. 准备工作 1 ...

  5. 【题解】Luogu P5284 [十二省联考2019]字符串问题

    原题传送门 我用sa做的本题 (码量似乎有点大) 先对原串建sa 考虑如何建图: 从大到小枚举长度len 先将height中等于len的两个位置在并查集合并起来,将lst也合并(lst是链表) 再将长 ...

  6. Linux学习方法和心态

    如果单纯是为了架站,那我就可以毕业了. 成就感+兴趣=学习的动力. 不同的环境下,解决问题的办法有很多种,只要行得通,都是好方法. Distribution 安装 熟悉Shell环境 Shell脚本 ...

  7. [Python]基础教程(4)、Python 变量类型

    Python 变量类型 变量存储在内存中的值.这就意味着在创建变量时会在内存中开辟一个空间. 基于变量的数据类型,解释器会分配指定内存,并决定什么数据可以被存储在内存中. 因此,变量可以指定不同的数据 ...

  8. ORACLE中INSERT插入多条数据

    insert ALL into u_role_permission(rid,pid) values (4,12) into u_role_permission(rid,pid) values (3,4 ...

  9. cf C题

    题意:矩阵只包含0,1两种数字,给你一个矩阵A,另一个矩阵B,每一次可以从A中选出一个子矩阵,点击一次使得这个子矩阵的四个角的数字变成与原来相反的数,0变1,1变0.问你可不可以经过有限次的变换把矩阵 ...

  10. MemoryStream请求与接收

    //流请求 static void Main(string[] args) { Console.WriteLine("Hello World!"); //Console.ReadL ...