1 进程和线程的区别和联系(重点理解掌握)
主要区别:每个进程都需要操作系统为其分配独立的内存地址空间(独立的代码和数据空间);而同一进程中的所有线程在同一块地址空间中工作,共享同一块内存和系统资源,比如共享一个对象或共享一个已经打开的文件(共享的代码和数据空间),随后补个图
次要区别:进程间的切换开销较大,线程切换开销较小
联系:一个进程至少包含一条线程
进程:正在运行的应用程序,有多条执行路径(线程)
线程:是依赖于进程存在,线程相当于进程中的某个任务(执行路径),是执行代码的基本单位
2 java虚拟机是多线程程序吗?(重点掌握)
是多线程,至少开启两条子线程:
主线程:当前程序在执行代码的时候,会开启main线程
守护线程(后台线程):垃圾回收器会开启一个垃圾回收线程,来确保程序不会内存异常,将不用的变量或者没有更多引用的对象来回收掉!
多线程的意义:
一个进程中开启了多个任务,每个一任务(线程)它们在互相抢占CPU的执行权---并发性---(提高程序的执行效率)
多线程在抢占CPU的执行权特点:具有随机性!----抢占式调度模型
并发:通过CPU调度算法,让用户看上去是同时执行(涉及CPU的一点时间片),从操作系统(CPU的层面)不是真正的同时--也就是后面的调度问题。
并行:是真正的同时;多个CPU实例或者多台计算机同时执行一段处理逻辑
线程的运行过程(有点涉及底层了)
有机会了再补充
线程的创建方式和启动
Thread的简介
代表线程类,最主要的两个方法是:
run():包含线程运行时所执行的代码-----没有抛出异常(所以重写时只能处理,不能抛出),是线程的起点
start():用于启动线,让线程去执行指定的方法---run()---→JVM底层去调用run()方法,不需要用户调用
方式1
用户自定义线程类需要继承Thread类,覆盖该类的run()方法,而实际启动的start()方法还是Thread本身的
开发步骤:
1)自定一个类:MyThread 继承自Thread类
2)在MyThread类中重写Thread类中的run()
3)在主线程中,创建该类的实例对象,并启动自定义线程(start()方法)
补充:匿名内部类的形式
问题1:为什么重写run()?
答:其实是通过JVM调用线程中的run()来进行多个线程抢占CPU执行权,run()方法中是线程启动后执行的方法,是核心
实例1(随后补充)
注意:
1)主线程与自定义线程并发运行----即:不会等待,通过抢占CPU运行各自的线程
2)不要随便覆盖Thread类的start()方法
3)一个线程只能被启动一次,否则出现IllegalThreadStateException---不合法的线程状态异常
体会:多个线程共享同一个对象的实例变量的情况
方式2
脱离继承,通过创建实现Runnable接口的子类或匿名内部类的形式来创建和开启一个线程
Runnable接口说明:只有一个抽象的run()方法
开发步骤:
1)自定义一个实现Runnable接口的子实现类,重写run()方法
2)在测试类(main--主线程中),创建此接口的实现类的实例对象
3)创建一个Thread类的对象,把上述创建的实例对象作为参数进行传递
3)通过start()方法分别启动自定义线程
说明:不管是继承还是实现Runnable接口,本质都是重写run()方法,即:指定线程执行的方法
面试题:多线程的实现方式有几种,分别是什么?
答:笔试(三个方式),第三种方式和线程池有关系(并且和Callable接口有关系);面试:两种方式
实例2(随后补充)
Thread类中常用的方法
(1)线程的名字
获取线程名称
public final String getName():返回该线程的名称----被final修饰不能重写
设置线程名称
public final void setName(String name):改变线程名称,使之与参数 name 相同
说明:在启动线程之前设置名称
通过Thread(String name)的构造方法来给线程命名,如何实现呢?
实现方式:不能直接创建Thread的类对象,必须自定义一个继承Thread的子类,我们可以在创建自定义的线程类对象中通过有参构造传入name,在自定义类的有参构造方法中,调用父类的super(name)属性即可给自定义的线程类命名
通过Thread(Runnable target,String name)的构造方法给线程命名---实现方式略
补充一点:对于线程的名字,默认主线程是main;在程序中创建的线程是"thread-"加上一个递增的整数(从0开始)
(2)得到当前的线程(对象的引用)
1.Thread类的静态方法currentThread()
说明:方法的返回值是执行当前代码的线程的引用
使用:Thread.currentThread()或者this.currentThread()
线程的状态装换图
新建状态---就绪状态---运行状态---阻塞状态---死亡状态
状态装换图是理解线程起承上启下的作用!!!
新建状态:用new语句创建的线程对象处于新建状态
就绪状态:当一个线程对象的创建后,其它线程调用它的start()方法,处于当前状态的线程位于可运行池,等待抢占CPU的使用权
运行状态:处于运行状态的线程抢占到了CPU的执行权,执行该线程的代码
说明:从就绪状态到运行状态涉及到线程的调度---即:在此刻谁抢占到了CPU的执行权
线程的调度:与java虚拟机和操作系统有关。例如:有的操作系统没有遇到阻塞不会放弃CPU;某些操作系统即使没有阻塞,也会在运行一段时间之后放弃CPU的执行权,给其它线程机会
说明1:在并发运行环境,对于只有一个CPU的计算机,任何时刻只有一个线程处于运行状态
说明2:只有处于就绪状态的线程才可能有机会转到运行状态(线程的调度)
阻塞状态:处于运行状态的线程因为某些原因放弃争夺CPU,暂停运行状态,进入阻塞状态
阻塞分3种:位于对象等待池的阻塞状态、位于对象锁池的阻塞状态、其它阻塞状态
说明:处于阻塞状态的线程,只有重新进入就绪状态,才有机会转到运行状态
问题1:从运行状态进入就绪状态的原因?
问题2:如何从阻塞状态进入就绪状态?
死亡状态:线程退出run()方法以后,进入死亡状态,该线程的生命周期结束
关于退出run()方法的具体情形:
(1)线程正常执行完run()方法
(2)线程遇到异常而退出
补充说明:不管线程是正常结束还是异常结束,都不会对其他线程造成影响,线程之间是相互独立的
关于死亡状态与Thread有关的方法
isAlive():非静态方法,用于判断一个线程是否存活;当线程死亡或处于新建状态,此方法返回false,其余状态返回true
线程的状态装换图
线程的调度
java虚拟机采用的是抢占式调度模型
抢占式调度:让运行池(就绪状态池)中优先级比较高的线程先抢占CPU的执行权(只是抢到的概率比较大,不一定抢得到);优先级相同的话就随机选择一个线程来抢占CPU
说明:处于运行状态的线程会一直运行,知道它不得不放弃CPU
线程放弃CPU的原因:
(1)java虚拟机让当前运行线程暂时放弃CPU(并发性---随机性---某个线程在下一时刻抢到了),转到就绪状态,其它线程或得运行机会(其它线程去抢CPU)
原因1的举例:Thread类的静态方法yield()
特点:当前运行的线程放到可运行池中(处于就绪状态),并使另一个线程运行(处于就绪状态,且与当前线程同级或者更高优先级线程),一点点的CPU片段;如果没有相同优先级的或更到的线程,此方法什么都不做
通俗:线程执行了yield()方法后,就转到就绪状态
理解:此方法比较势力,攀附权贵,一般在测试用(认为提高程序的并发性,帮助寻找潜在的错误)
(2)当前线程因为某些原因进入阻塞状态
(3)线程运行结束(自然结束)
特点:线程的调度不是跨平台的,依赖于JVM和操作系统
说明:有的操作系统没有遇到阻塞不会放弃CPU;某些操作系统即使没有阻塞,也会在运行一段时间之后放弃CPU的执行权,给其它线程机会