Java程序开发学习之多线程

(学习参考书:Java2实用教程第5版)

一、进程与线程

程序时一段静态的代码,是应用软件执行的蓝本。进程是程序的一次动态执行过程,对应了从代码加载、执行至执行完毕的完整过程。现代操作系统可以同时管理计算机的多个进程。
线程不是进程,但其行为很像进程。线程是比进程更小的单位,一个进程在执行过程中,可以产生多个线程,每个线程也有它自身的产生、存在和消亡的过程。通俗的讲,线程是进程中的“小进程”。

二、Java中的线程

(1)Java的多线程机制
Java语言的一大特点就是内置对多线程的支持。多线程是指一个应用程序中同时存在几个执行体,按几条不同的执行线索共同工作的情况。
每个Java应用程序都有一个缺省的主线程。当JVM加载代码发现main方法后,就会启动一个线程,该线程就是主线程(main线程)。如果在主线程之外创建了其他线程,JVM就要在主线程和其他线程之间轮流切换,所有线程结束之后,应用程序才会结束。

(2)线程的状态和生命周期
Java语言使用Thread类及其子类的对象来表示线程,新建的线程在它的一个完整的生命周期要经历如下四种状态:

  • 新建:当一个Thread类及其子类的对象被声明创建后,新生的线程处于新建状态。此时它已有相应的内存和其他资源。
  • 运行:线程创建之后就具备了运行的条件,一旦轮到它想用CPU资源时,即JVM切换到该线程时,此线程就可以脱离创建它的主线程独立开始自己的生命周期了。
    线程创建后仅仅是占有了内存资源,在JVM的管理的线程中还没有这个线程,此线程必须调用start()方法通知JVM。当JVM切换到线程时,如果该线程是Thread的子类创建的,该类的run()方法就立刻执行,该方法规定了该线程的具体使命。所以程序必须在子类中重写父类的run()方法。在线程没有结束run()之前,不要让线程再调用start()方法。否则发生IllegalThreadStateException异常
  • 中断:有四种中断原因
    (1)JVM将CPU资源从当线程切换给其他线程,使本线程让出CPU的使用权处于中断状态。
    (2)线程使用CPU资源期间,执行力sleep(int millsecond)方法,使当前线程进入休眠状态。经过参数(休眠的毫秒数)之后,该线程重新进入到线程队列中排队等待CPU资源。
    (3)线程使用CPU资源期间,执行力wait()方法,使得当前线程进入等待状态。等待状态的线程不会主动进入线程队列中排队等待CPU资源,必须由其他线程调用notify()方法通知它,使得它重新排队。以便从中断处继续运行。
    (4)线程使用CPU资源期间,执行某个操作进入阻塞状态,比如执行读/写。进入阻塞状态的线程不能进入排队对列,只有当阻塞原因消除后,线程才能重新排队。
  • 死亡:
    处于死亡状态的线程不具有继续运行的能力。线程死亡原因有二:一为正常运行的线程完成了它的全部工作结束了run()方法。另一个原因是线程被提前强制性终止。所谓的死亡状态就是线程释放了实体(内存)。

(3)线程调度与优先级
处于就绪状态的线程首先进入就绪队列排队等候CPU资源,同一时刻队列中的线程有多个。Java虚拟机中的线程调度器负责管理线程,调度器将线程的优先级划分为10级(1~10),分别使用Thread类的类常量表示。如果没有明确设置线程的优先级别,每个线程的优先级都为常数5。
线程的优先级可以用setPriority(int grade)方法是调整,如果参数不在1~10范围内,便会产生IllegalThreadStateException异常。getPriority方法返回线程的优先级。
JVM的线程调度器的任务是使高优先级的线程能是始终运行,一旦时间片有空闲,则是同等优先级的线程以轮流的方式顺序使用时间片。高优先级的线程们执行至死亡状态,低优先级的线程才开始交替进行。

三、Thread类和线程的创建

(1)使用Thread类或其子类
使用Thread子类创建线程的优势是:可以在子类中增加新的成员变量,使线程具有某种属性,也可以在子类中新增方法,使线程具有某种功能。

直接使用Thread类创建线程对象:构造方法为Thread(Runnable target)。参数为一个Runnable类型的接口。因此在创建线程对象时必须向构造方法的参数传递一个实现Runnable接口类的实例。该实例对象被称作所创建线程的目标对象。当线程调用start方法后,一旦轮到它享用CPU资源,目标对象就自动调用接口中的run方法(接口回调)。线程绑定于Runnable接口,当线程被调度并转入运行状态时,所执行的就是run方法所规定的操作。

(2)目标对象与线程的关系
从对象和对象之间的关系角度上看,目标对象和线程的关系有以下两种情景:
目标对象和线程完全解耦
实现Runnable接口的类创建的目标对象不包含对main方法创建的线程对象的引用(完全解耦)。这种情况下,目标对象经常需要通过获得线程的名字(因为无法获得线程对象的引用):

String name = Thread.currentThread.getName();

以便确定是哪个线程正在占用CPU资源。
目标对象组合线程(弱耦合)
目标对象可以组合线程,即将线程作为自己的成员(弱耦合)。当创建目标对象的类组合线程对象时,目标对象可以通过获得线程对象的引用:

Thread.currentThread;

来确定是哪个线程正在占用CPU的资源。

四、线程的常用方法

start() 线程调用该方法将启动线程使之从新建状态进入就绪队列排队,一旦轮到它享用CPU资源时,就可以脱离创建它的线程独立开始自己的生命周期。

run() Thread类和Runnable接口中的run()方法的功能和作用相同,都用来定义线程对象被调度后所执行的操作。在线程没有结束run方法之前,不赞成让线程在调用start方法。

sleep(int millsecond) 线程的调度执行是按照优先级的顺序进行的,高线程未死亡时,低线程没有机会调用。当高级别线程需要低级别线程配合时,就需要它调用sleep方法暂时放弃CPU资源。参数为休眠的毫秒数,如果在休眠中被打断,抛出InterruptedException异常。因此必须在try-catch语句中调用sleep方法。

isAlive() 线程处于新建状态时,调用isAlive()方法返回false。当一个线程调用start方法并占有CPU资源运行run方法的期间,该方法返回truecurrentThread() 是Thread的类方法,返回当前正在使用CPU资源的线程。

interrupt() 用于唤醒休眠的线程,一个占有CPU资源的线程可以让休眠的线程调用该方法唤醒自己。抛出InterruptedException异常,从而结束休眠,继续排队。

五、线程同步

线程同步就是若干个线程都需要使用一个synchronized(同步)修饰的方法,即程序中的若干个线程都需要使用一个方法,这个方法用synchronized进行修饰。在使用多线程解决问题时,可能要把某些修改数据的方法用关键字synchronized来修饰,即使用同步机制。
线程同步机制:当一个线程A使用synchronized方法时,其他线程想使用这个方法就必须等待,直到线程A使用完该synchronized方法。

六、协调同步的线程

当一个线程使用的同步方法中用到某个变量,而此变量又需要其他线程修改后才能符合本线程的需要,那么可以在同步方法中使用wait()方法。该方法可以中断线程的执行,允许其他线程使用同步方法。
其他线程如果在使用同步方法时不需要等待,那么在它使用完的同时,应当用notifyAll()方法通知所有由于使用这个同步方法而处于等待状态的线程结束等待,曾中断的线程就会从刚才的中断处继续执行这个同步方法,并遵循“先中断先继续”的原则。

wait()、notify()、notifyAll()方法都是Object类的final方法,被所有类继承且不允许重写,不可以在非同步方法使用这三个方法

七、线程联合

一个线程A在占有CPU资源期间,可以让其他线程调用join()和本线程联合:

B.join();

则称A在运行期间联合了B。如果线程A在占有CPU资源期间一旦联合了B,A线程会立刻中断执行,一直等到联合的B执行完毕,A重新进入队列排队。如果A准备联合的B线程已经束,那么B.join()不会产生任何效果。

八、GUI线程

当Java程序包含图形用户界面时,JVM会自动启动更多的线程,其中两个重要的是:AWT-EventQuecue和AWT-Windows。前者负责处理GUI事件,前者负责将窗体和组件绘制到桌面。
程序中发生GUI界面事件时,JVM就会将CPU资源切换给AWT-EventQuecue线程处理该事件。

九、计时器线程

java.swing包中有一个Timer类,即计时器。当某些操作需要周期性执行时,就可以使用计时器。使用构造方法Timer(int a,Object b)创建一个计时器:其中参数a的单位是毫秒,确定计时器每隔参数指定时间“震铃”一次;参数b是计时器的监视器。计时器发生的事件为ActionEvent事件,即每隔指定事件,监视器方法执行一次。计时器的监视器必须是组件类,否则无法启动。常用方法有:

start() 启动计时器,即启动线程
stop() 停止计时器,即挂起线程
restart() 重新启动计时器,即恢复线程
setReapeats(boolean b) 如果参数为false,计时器只震铃一次
setInitialDelay(int delay) 设置首次震铃的延时,不设置就默认为构造方法的参数a

十、守护线程

线程默认为非守护线程,非守护线程也称作用户线程(user),一个线程调用void setDaemon(boolean on)方法可以将自己设置成一个守护线程(Daemon)如:

thread.setDaemon(true);

当程序中的所有用户线程都运行结束后,即使守护线程还没有运行完成,守护线程也会立刻结束运行。一个线程必须在运行前就设置好是否为守护线程。守护线程可以用作一些不是很严格,立即结束也没有什么不良后果的工作。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值