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. DotNetty网络通信框架学习之初识Netty

    p{ text-align:center; } blockquote > p > span{ text-align:center; font-size: 18px; color: #ff0 ...

  2. Windows 安装配置MongoDB

    1.安装 下载安装包 直接下一步. 2.添加环境变量 3.bin同级目录下新建data文件夹,data文件夹下新建db和log文件夹 4.输入命令启动mongodb服务 mongod --dbpath ...

  3. MySQL ERROR 1698 (28000): Access denied for user 'root'@'localhost'

    今天在安装MySQL的过程中竟然没有让我输入密码,登录的时候也不需要密码就能进入,这让我很困惑. 进了数据库就设置密码,用了各种方式都不行. 虽然我这数据库没啥东西但也不能没有密码就裸奔啊,有点丢人是 ...

  4. CSS3@media媒体查询

    CSS3@media媒体查询 定义 media媒体查询, 当文档宽度变化时, 就可以根据文档宽度的变化来运用样式,不同的宽度应用不同的样式 使用 @media 查询,你可以针对不同的媒体类型定义不同的 ...

  5. 58 字体反爬攻略 python3

    1.下载安装包 pip install fontTools 2.下载查看工具FontCreator 百度后一路傻瓜式安装即可 3.反爬虫机制 网页上看见的 后台源代码里面的 从上面可以看出,生这个字变 ...

  6. MyPython

    目录 Python,那些不可不知的事儿 Python简介 Python环境搭建 从Hello World开始 Python中的数据类型 函数 模块 面向对象 More Python,那些不可不知的事儿 ...

  7. axure原型设计

    在上一个学期的学习中,我们已经初步学习了axure的使用方法,它可以为负责定义需求设计,功能和界面的人员能快速设计出所需产品. 引入:在我们想为软件设计原型的时候,纸质原型很难表达交互的界面,与此同时 ...

  8. 移动端开发注意事项——meta、rem以及弹性盒

    移动端开发注意事项——meta.rem以及弹性盒 随着人们对移动端的依赖程度的增强,前端开发对移动端的需求也越来越强烈.那么,在移动端开发中,有哪些事项是需要注意的呢? meta标签 在常规的pc端开 ...

  9. 利用策略模式实现了同一接口的多个Servicel实现类,如何同时注入Controller

    解决方法如上图,通过给实现类起别名,并在controller中,通过@Qualifier注解获取不同的实现类,之前没有这样写,会出现这样的情况: 通过@autowired注解注入dao层时为空,会报空 ...

  10. win10 开机自启指定软件

    开机自启 %programdata%\Microsoft\Windows\Start Menu\Programs\StartUp