Java-多线程(一)

线程概述

几乎所有的操作系统都支持进程的概念,所有运行中的任务通常对应一个进程。当一个程序进入内存运行时,即变成一个进程。进程是处于运行中的程序,并且具有一定的独立功能。进程也可以理解成程序的一次执行过程,是系统运行程序的基本单元。大部分操作系统都支持多进程并发运行,首先要注意并发和并行是两个不同的概念:

  • 并发性:在同一时刻只有一条指令在执行,但多个进程指令被快速轮换执行,使得同一段时间段上来看,有多个进程同时执行的效果
  • 并行性:指在同一时刻,多个指令同时执行

由于CPU不断地在进程之间轮换执行,且执行速度相对人的感觉来说,非常快,使得用户感觉多个进程在同时执行。
而多线程扩展了多进程的概念,使得同一进程可以同时并发处理多个任务。

  • 线程是进程的执行单元,负责当前进程中程序的执行,一个进程可以拥有多个线程,一个线程必有一个父进程。
  • 线程可以拥有自己的堆栈、自己的程序计数器和自己的局部变量,但不拥有系统资源,它与父进程的其他线程共享该进程所拥有的的全部资源。

多线程编程具有如下的优点:

  • 进出之间不能共享内存,但线程之间共享内存非常容易
  • 系统创建进程时需要为该进程重新分配系统资源,但创建线程则代价小得多,因此使用多线程来实现多任务并发比多进程的效率高

线程的创建和启动

Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。
当Java程序开始运行后,程序至少会创建一个主线程,即由main()方法确定的,由main()方法的方法体代表主线程的线程执行体

继承Thread类创建线程类

通过继承Thread类来创建并启动多线程的步骤如下:

  1. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务,因此把run()方法称为线程执行体
  2. 创建Thread子类的实例,即创建了线程对象
  3. 调用线程对象的start()方法来启动该线程
  • 多次启动一个线程是非法的,特别是线程已经结束执行后,不能再重新启动
  • Java程序属于抢占式调度,哪个线程的优先级高,哪个线程优先执行

【Thread类】
构造方法:

  • public Thread() - 分配一个新的线程对象
  • public Thread(String name) - 分配一个指定名字的新的线程对象
  • public Thread(Runnable target) - 分配一个带有指定目标新的线程对象
  • public Thread(Runnable target,String name) - 分配一个带有指定目标新的线程对象并指定名字

常用方法:

  • public String getName() - 获取当前线程名称
  • public String setName(String name) - 设置线程名称
  • public void start() - 导致此线程开始执行
  • public void run() - 此线程要执行的任务在此处定义代码
  • public static void sleep(long millis) - 使当前正在执行的线程以指定的毫秒数暂停
  • public static Thread currentThread() - 返回对当前正在执行的线程对象的引用。
实现Runnable接口创建线程类

实现Runnable接口来创建并启动多线程的方法如下:

  1. 定义Runnable接口的实现类,并重写该接口的run()方法,该run方法的方法体同样是该线程的线程执行体
  2. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象
  3. 调用线程对象的start()方法来启动该线程
  • Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体,设计的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法
使用Callable和Future创建线程

通过是按Runnable接口创建多线程时,Thread类的作用就是把run()方法包装成线程执行体,但是Java不能直接将任意方法都包装成线程执行体
从Java 5开始,Java提供了Callble接口,可以说是Runnable接口的增强版,Callable接口提供了一个call()方法作为线程执行体,比run()方法功能更加强大

  • call()方法可以有返回值
  • call()方法可以声明抛出异常

因此可提供一个Callable对象作为Thread的target,而该线程的线程执行体就是该Callable对象的call()方法。
但是,Callable接口不是Runnable的子接口,不能直接作为Thread的target。而且call()方法还有一个返回值——call()方法不是直接调用,是作为线程执行体被调用,Java 5提供了Future接口来代表Callable接口里的call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口,并实现了Runnable接口——可以作为Thread类的targer。
创建并启动返回值的线程步骤如下:

  1. 创建Callable接口的实现类,并实现call方法,该call()方法将作为线程执行体,且该call()方法有返回值,再创建Callable实现类的实例
  2. 使用FutureTask类来包装Callable对象,该FutureTask对象封装了Callable对象的call()方法的返回值
  3. 使用FutureTask对象作为Thread对象的target创建并启动新线程
  4. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值
创建线程的三种方式的对比

实现Runnable接口以实现Callable接口的方式基本相同,可以归为一种方式,采用实现Runbable、Callable接口的方式有如下优点:

  • 线程类只实现了接口,还可以继承其他类,可以避免java中单继承的局限性
  • 多个线程可以共享同一个target对象,适合多个相同线程来处理同一份资源
匿名内部类方式实现线程的创建

使用线程的内匿名内部类方式,可以方便的实现每个线程执行不同线程任务操作。

  • 使用匿名内部类的方式实现Runnable接口,重新Runnable接口中的run()方法

线程状态

当线程被创建并启动以后,它既不是一启动就进入了执行状态,也不是一直处于执行状态,在线程的生命周期中,会出现以下几种状态:

  • New - 初始状态,新建状态,线程被构建,还没有调用start()方法
    • 如果直接调用线程对象的run()方法,系统把线程对象当成一个普通对象,而run()方法也是一个普通方法,不是线程执行体,程序只有一个主线程运行
    • 启动线程使用的是start()方法
  • Runnable、Running - 这里不同的资料有不同的说法,以《JAVA并发编程的艺术》书为例,Java将就绪和运行两种状态统称为“运行中”,线程在调用start()方法后,会进入Runnable(就绪)状态。线程得到处理器资源后,会进入Ruuning(运行)状态,反过来可以用yield()方法,后面会介绍。
  • Blocked - 阻塞状态,线程失去了处理器资源,或线程在等待某个通知,用锁的概念,可以理解成当一个线程视图获得一个对象锁,而该对象锁被其他线程持有,进入阻塞状态。
    • 在如下情况下,线程为进入阻塞状态
      • 线程调用了一个阻塞式IO方法,在该方法返回之前,该线程被阻塞
      • 线程视图获得一个同步监视器锁,但该同步监视器锁正被其他简称所持有。
      • 线程在等待某个通知(notify)
      • 程序调用了线程的suspend()方法将线程挂起(使用resume()方法恢复),此时易造成死锁
    • 【注意】被阻塞线程的阻塞解除后,进入就绪状态,而不是直接进入运行状态。
  • Waiting - 无限等待,一个线程在等待另一个线程执行一个(唤醒)动作时,进入Waiting状态。进入这个状态后是不能自动唤醒的,需要notify或notifyAll方法才能够唤醒
  • Timed_Waiting - 计时等待,跟Waiting状态类似,设置了超时参数,这一状态将一直保持到超时期满或者接受到唤醒通知。
  • Terminated - 终止状态,run()方法正常退出而死亡,或者直接调用stop()方法——该方法容易导致死锁,不推荐使用
    在这里插入图片描述

控制线程

Java的线程支持提供了一些便捷的工具方法,通过这些工具方法可以很好的控制线程的执行

join线程

Thread提供了让一个线程等待另一个线程完成的方法——join()方法

  • join()方法:当在某个程序执行流中调用其他线程的join()方法时,调用线程将被阻塞,直到被join()方法加入的join线程执行完为止
  • join()方法通常有使用 线程的程序调用,以将大问题划分为许多小问题,每个小问题分配一个线程,当所有的小问题都得到处理后,再调用主线程来操作
后台线程

Daemon Thread——守护线程,或称为“精灵线程”,是一种在后台运行的后台线程,为其他线程提供服务,JVM的垃圾回收线程就是典型的后台线程。

  • 后台线程有个特征:如果所有的前台线程都死亡,后台线程会自动死亡。
  • 调用Thread对象的setDaemon(true)方法可将指定线程设置成后台线程
    • 主线程默认是前台线程
线程睡眠:sleep

如果需要让当前正在执行的线程暂停一段时间,并进入阻塞状态,可以通过调用Thread类的静态方法sleep()来实现

  • 当当前线程调用sleep()方法进入阻塞状态后,在其睡眠时间段内,该线程不会获得执行的机会,即使系统中没有其他可执行的线程,处于sleep()中的线程也不会执行,因此sleep()方法常用来暂停程序的执行
线程让步:yield

yield()方法是一个和sleep()方法有点相似的办法,它也可以让当前正在执行的线程暂停,但它不会阻塞该线程,它只是将该线程转入就绪状态

  • yield()只是让当前线程暂停一下,让系统的线程调度器重新执行一次,完全可能的情况是:当某个线程调用了yield()方法暂停之后,线程调度器又将其调度出来重新执行。
  • 2
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值