您的位置:首页 > 编程语言 > Java开发

Java基础知识每日总结(16)---Java多线程编程

2020-07-05 18:26 288 查看

多线程编程

1.线程基础
线程是Java语言的关键技术之一,在Swing中也扮演重要的角色。
①线程与进程
由于在操作系统上创建进程的开销非常大,因此提出了线程的概念,它相当于“轻量级”的进程。一个操作系统可能会包括多个进程,而1个进程可能会包括多个线程。
每个进程都拥有属于自己的一套变量,而在同一进程中创建的线程共享这些变量。因此,对于变量的修改通常需要使用同步。
注:在使用Java语言编写多线程程序时,运行结果与操作系统密切相关,即使在同一台机器上,多次运行同一个程序,结果也可能不同。
②创建无返回值线程
通常有两种方式用于创建无返回值线程:实现Runnable接口和继承Thread类。

实现Runnable接口:
Runnable接口位于java.lang包中,这个接口非常简单,仅定义了一个run()方法,通常将希望在新线程中运行的代码写在这个方法中:

public abstract void run();

实现Runnable接口创建新线程的步骤:

  • 新建一个类,该类实现了Runnable接口并重写了run()方法。run()方法中包含要在新线程中运行的代码
  • 使用刚刚编写的类创建Runnable接口类型的对象
  • 使用Thread类的构造方法创建Thread类的对象
  • 调用Thread类的start()方法来运行新线程

注:不要使用Thread类的run()方法,它不会创建新的线程。

继承Thread类:
Thread类位于java.lang包,它包含了很多与线程密切相关的方法,该类还实现了Runnable接口。使用Thread类创建新线程时,也需要重写run()方法。

建议使用实现Runnable接口来创建新线程,因为:

  • Java仅支持单继承,一旦一个类继承了其他类,则不能继承Thread类
  • 创建大量的Thread类对象,开销较大
  • Java SE 5.0版中新增的很多简化线程开发的类是使用Runnable接口的

③线程生命周期
线程是具有生命周期的,生命周期是由不同的状态组成。对于不同的状态,能够执行的操作不同。在Thread类的内部,定义了一个枚举State,其中包含了线程的全部状态:

其关系图:

各个数字的含义:

  • 1:线程由NEW状态变成RUNNABLE状态,这可以通过调用start()方法实现
  • 2: 线程在RUNNABL状态和BLOCKED状态之间进行转换,可以通过得到和等待内置锁实现
  • 3:线程在RUNNABL状态和TIMED_WAITING状态之间进行转换,可以通过调用sleep()等方法实现
  • 4:线程在RUNNALBE状态和WAITING状态之间进行转换,可以通过调用wait()等方法实现
  • 5:线程在由RUNNABLE状态变成TERMINATED状态,可能是run()方法运行结束或出现异常

注:当使用new操作符创建Thread对象时,线程处于NEW状态。

④线程各属性

  • 线程优先级属性:Java中所有线程都具有优先级属性,默认的优先级与创建该线程的线程优先级相同。使用Thread类的getPriority()方法获得线程优先级,setPriority()方法设置线程优先级
  • 线程名称属性:每个线程在创建时为其指定名称。如果未指定,则由系统进行指定。对于主线程,名称是main。对于其他线程,名称默认样式是“Thread-数字”。使用Thread类的getName()方法获得线程名称,setName()方法设置线程名称。
    注:不同的线程可以有相同的名称,不能使用线程名称来区分线程。
  • 线程ID属性:每个线程在创建时会被分配一个ID属性,使用Thread类的getId()方法获得线程ID。Thread类并没有提供修改ID的方法。
    注:不同的线程不能有相同的ID,系统是使用ID值来区分线程。
    -守护线程:守护线程的作用是为其他线程提供服务,如果其他线程运行完毕,即虚拟机中只剩下守护线程时,虚拟机会退出。
    注:不要让守护线程去访问文件等资源,它们可能在任何时候终止而资源却没有释放。

2.线程控制
线程创建以后需要进行启动、休眠、停止等控制。
①线程的休眠
Thread类的sleep()方法可以让线程进入休眠状态。在使用该方法时,需要指明线程休眠的时间,在时间到达后,线程会自动唤醒,该方法有两种形式:

  • 精度到毫秒
    方法声明:public static void sleep(long millis)throws InterruptedException
    参数说明:millis是以毫秒为单位的休眠时间
  • 精度到纳秒
    方法声明:public static void sleep(long millis,int nanos)throws InterruptedException
    参数说明:millis是以毫秒为单位的休眠时间。nanos是以纳秒为单位的休眠时间

注:这两个方法都受系统计时器和调度程序精度和准确性的影响,因此时间可能并不十分精确。这两个方法都会抛出InterruptedException,需要对其进行捕获或抛出。
1秒 = 103毫秒 = 106微秒 = 109纳秒

②线程的插队
通常情况下,线程调度器是根据线程优先级来分配资源。对于高优先级的线程,其获得资源的概率也很高。如果想控制线程的执行顺序,例如让某个线程执行到一半时让另一个线程优先运行完毕,再运行前一个线程,可以使用Thread类提供的join()方法,该方法有3种形式:

  • 无时间参数
    方法声明:public final void join()throws InterruptedException
  • 精度到毫秒
    方法声明:public final void join(long millis)throws InterruptedException
    参数说明:millis是以毫秒为单位的休眠时间
  • 精度到纳秒
    方法声明:public final void join(long millis,int nanos)throwsInterruptedException
    参数说明:millis是以毫秒为单位的休眠时间。nanos是以纳秒为单位的休眠时间

③线程的停止
线程通常是在run()方法运行完毕后停止运行,此外还可以使用布尔值来让线程提前停止。虽然Thread类也提供了stop()方法来停止线程,但是由于该方法固有的不安全性,已经不推荐使用。
④线程同步
在编写多线程程序时,会遇到两个或两个以上线程同时修改一个共享数据的情形。此时,如果不使用同步,则会发生错误。
Java中提供了很多种实现同步的方式,所谓同步方法就是使用synchronized关键字修饰的方法。如果某个对象调用了同步方法,其他对象在该方法运行完毕前是不能再进入这个方法。这样则可以避免因为同时修改数据而引发的问题。在使用synchronized关键字修饰run()方法后,在一个线程运行完毕前,其他的线程并没有进入该方法。
虽然同步方法很简单,但是同步的开销很大。因此应该尽量减少同步的代码。此时可以考虑使用同步块来减负。
可以使用volatile关键字来修饰成员变量。它的用处在于告诉虚拟机该成员变量可能已经被修改,需要进行确认才能使用,这样则避免了同步的需求。

3.线程应用
使用多线程编程的原因之一是提高程序的响应性能,在Swing中尤为重要。对于用户而言,更希望程序能够时刻响应自己的各种操作。
Swing不是线程安全的。如果希望在多个线程中操作同一控件,很可能让程序崩溃。
①EventQueue与线程分配
EventQueue是一个与平台无关的类,它是Swing的一个重要组成部分,负责AWTEvent类及其子类的分发。首先将所有事件放置在一个队列中,然后使用dispatchEvent()方法来依次执行各个事件。
Swing中的事件如果耗时过长,就会让程序暂时死掉。就是因为使用dispatchEvent()方法来依次执行各个事件。为了将Swing程序在事件分配线程中运行,需要使用invokeLater()方法:

方法声明:public static void invokeLater(Runnable runnable) 参数说明:runnable是Runnable对象,其run方法应该在EventQueue上同步执行

②SwingWorker类的使用
对于Swing中的耗时任务,可以使用Java SE 6.0版中提供的SwingWorker类来实现。它是Swing中专门用于执行耗时任务的抽象类。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: