java程序员从笨鸟到菜鸟之(四十一)线程初涉

1 进程和线程的区别和联系(重点理解掌握)

要区别:每个进程都需要操作系统为其分配独立的内存地址空间(独立的代码和数据空间);而同一进程中的所有线程同一块地址空间中工作,共享同一块内存系统资源,比如共享一个对象或共享一个已经打开的文件(共享的代码和数据空间),随后补个图

要区别:进程间的切换开销较大,线程切换开销较小

联系:一个进程至少包含一条线程

进程:正在运行的应用程序,有多条执行路径(线程)

线程:是依赖于进程存在,线程相当于进程中的某个任务(执行路径),是执行代码的基本单位

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

线程的状态装换图

与其他阻塞状态有关的Thread中的方法
(1)Thread类的静态方法sleep(long millions)
参数:没有返回值,接受long类型的时间毫秒数
功能:让 当前线程(Thread.currentThread)沉睡指定的毫秒数---专业点就做线程挂起
特点:处于正在 运行的线程,执行此方法时会让 线程挂起(阻塞状态,放弃CPU的执行权),如果没有外部中断, 时间毫秒数执行完毕(timeout),进入 绪状态准备抢占CPU重新进入运行状态
使用方式:当前线程中调用此方法。直接sleep()( 推荐使用)或者this.sleep()或者Thread.curentThread.sleep()
注意:此方法会抛出一个 InterruptedException的异常---编译时的异常(一般try-catch处理),不能让程序严格指定挂起时间(有微小的误差)
出现异常原因:线程正处于挂起状态,因为 某种原因( interrupt()方法)被打断了。举例:本应该挂起5秒,但在挂起2秒后,由于某种原因挂起状态被打断
(2)Thread类的非静态方法join()
功能:当前运行的线程中调用另一个线程的join()方法,当前线程进入阻塞状态,另一个线程处于运行状态
理解:有点类似鸠占鹊巢中的孵蛋,在别人的鸟巢孵蛋,把别的鸟蛋弄到一边,自己的先孵,自己的孵出来了,别的鸟蛋才有被孵出的可能
举例:在主线程中调用了自定义线程对象的join()方法,主线程必须等到自定义线程运行结束(run()方法结束),才可能恢复运行
两种 重载形式
无参----另一个线程运行完,才有可能恢复
有参----指定当前运行线程挂起的指定时间毫秒数,timeout后当前线程恢复
(3) 与其他阻塞状态有关的原因(非Thread)
       当前线程发出了I/O请求
解释:当前线程执行类似Systemout.println()或者System.in.read()方法后,会发出一个I/O请求,该线程放弃CPU,进入阻塞状态,直到I/O处理完毕,该线程才会恢复运行(进入就绪状态,准备抢占CPU)

线程的调度

java虚拟机采用的是抢占式调度模型

抢占式调度:让运行池(就绪状态池)中优先级比较高的线程先抢占CPU的执行权(只是抢到的概率比较大,不一定抢得到);优先级相同的话就随机选择一个线程来抢占CPU

说明:处于运行状态的线程会一直运行,知道它不得不放弃CPU

线程放弃CPU的原因

(1)java虚拟机让当前运行线程暂时放弃CPU(并发性---随机性---某个线程在下一时刻抢到了),转到就绪状态,其它线程或得运行机会(其它线程去抢CPU)

原因1的举例:Thread类的静态方法yield()

特点:当前运行的线程放到可运行池中(处于就绪状态),并使另一个线程运行(处于就绪状态,且与当前线程同级或者更高优先级线程),一点点的CPU片段;如果没有相同优先级的或更到的线程,此方法什么都不做

通俗:线程执行了yield()方法后,就转到就绪状态

理解:此方法比较势力,攀附权贵,一般在测试用(认为提高程序的并发性,帮助寻找潜在的错误)

(2)当前线程因为某些原因进入阻塞状态

(3)线程运行结束(自然结束)

特点:线程的调度不是跨平台的,依赖于JVM和操作系统

说明:有的操作系统没有遇到阻塞不会放弃CPU;某些操作系统即使没有阻塞,也会在运行一段时间之后放弃CPU的执行权,给其它线程机会

Thread类中关于优先级的方法---非静态
(1)Thread类的setPriority(int)
                 设置优先级,在start()方法之前
(2)Thread类的getPriority(int)
                 读取优先级,在start()方法之后
补充:Thread有3个与优先级有关的静态常量对应优先级是:1、5、10
说明:默认的优先级是5
线程调度中,如果 明确一个线程给另外一个线程的运行机会,可以采取的方式?
(1)调整优先级
(2)运行状态的线程调用Thread.sleep()方法-----阻塞
(3)运行状态的线程调用Thread.yield()方法------就绪
(4)运行状态的线程调用另一个线程的join方法--阻塞

后台线程(守护线程)

意义:为其他线程提供服务的线程
典型的例子:java 虚拟机的垃圾回收线程是典型守护线程,负责回收其它线程不再使用的内存
后台线程的特点:
(1)后台线程与前台线程相伴,只有当 所有的前台线程结束生命周期后, 后台线程 才会结束生命周期( 前提是后台线程此时还没有运行完)
Thread中与守护线程有关的方法
(1)Thread类的 setDaemon( true)---非静态
功能:把一个线程设置为后台线程
(2)Thread类的 isDaemon()方法
功能:判断一个线程是否是后台线程
使用后台线程时, 注意
(1)后台线程 不一定在前台线程的 后面结束生命周期(取决于程序的具体实现)
(2)java虚拟机能够保证的是:当所有的前台线程运行结束,假如后台线程还在运行,java虚拟机会终止后台线程
(3)在线程 启动之前(调用start()方法之前),才能把它 设置为后台线程。如果之后设置出现IllegalThreadStateException异常
(4)前台线程中创建的线程默认还是前台线程,后台一样

定时器

位于java.util.Timer,特点:定时执行特定的任务
:具体Date日期、延时、延时后重复
关于任务:涉及 TimerTask类(见名知意),是一个 抽象类,实现了Runnable接口;因此就需要重写run()方法,run()方法就是定时器需要完成的任务,同时run()方法也是与线程有关的方法(开启了一个线程)
简单介绍一下Timer类的几个方法
(1) 有参构造
Timer(boolean isDaemon)
说明:是否允许把与Timer 有关的线程(其实就是任务中的线程,再具体点run())设置为后台线程
(2)无参构造略
(3) Timer类的定时开启一个任务(线程)的 schedule()方法重载形式
           1)schedule(TimerTask task,Date date)
          说明:在具体的日期执行该任务,涉及到 日期的解析
           2)schedule(TimerTask task,long delay)
          说明:仅仅在 延时时间毫秒数后,执行一次该任务
           3)schedule(TimerTak task,long delay,long period)
          说明: 延时后,周期性的执行任务
开发步骤:
(1)创建一个定时器对象timer
(2)创建一个定时器对象需要完成的任务TimerTask对象(常用匿名内部类,需要重写run()方法)task
(3)定时器对象开启一个任务---schedule(TimerTask task,long delay)方法,将task作为参数传递进去,JVM开始执行任务

注意一点:Timer类本身不是不是一个线程类,但在它的实现中(定时),是利用线程来执行任务的(理解为包装或隐藏了线程)
实例3(随后补充)
说明:截止到目前我们并没有引入任何的关于同步机制的问题,下一节我们会以买票、生产者和消费者、银行账户模型来理解同步机制

实例2 买票案例---层层递进---说明问题---同步锁对象

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值