Java多线程的实现方式
在 Java 的 JDK 开发包中,已经自带了对多线程技术的支持,可以方便地进行多线程编程。实现多线程编程的方式主要有两种:一种是继承 Thread 类,另一种是实现 Runnable 接口。下面详细介绍这两种具体实现方式。
继承 Thread 类
在学习如何实现多线程前,先来看看 Thread 类的结构,如下:
public class Thread implements Runnable
从上面的源代码可以发现,Thread 类实现了 Runnable 接口,它们之间具有多态关系。
其实,使用继承 Thread 类的方式实现多线程,最大的局限就是不支持多继承,因为 Java 语言的特点就是单根继承,所以为了支持多继承,完全可以实现 Runnable 接口的方式,一边实现一边继承。但用这两种方式创建的线程在工作时的性质是一样的,没有本质的区别。
Thread 类有如下两个常用构造方法:
-
public Thread(String threadName)
-
public Thread()
继承 Thread 类实现线程的语法格式如下:
public class NewThreadName extends Thread { //NewThreadName 类继承自 Thread 类 public void run() { //线程的执行代码在这里 } }
线程实现的业务代码需要放到 run() 方法中。当一个类继承 Thread 类后,就可以在该类中覆盖 run() 方法,将实现线程功能的代码写入 run() 方法中,然后同时调用 Thread 类的 start() 方法执行线程,也就是调用 run() 方法。
Thread 对象需要一个任务来执行,任务是指线程在启动时执行的工作,该工作的功能代码被写在 run() 方法中。当执行一个线程程序时,就会自动产生一个线程,主方法正是在这个线程上运行的。当不再启动其他线程时,该程序就为单线程程序。主方法线程启动由 Java 虚拟机负责,开发人员负责启动自己的线程。
如下代码演示了如何启动一个线程:
new NewThreadName().start(); //NewThreadName 为继承自 Thread 的子类
注意:如果 start() 方法调用一个已经启动的线程,系统将会抛出 IllegalThreadStateException 异常。
例 1
编写一个 Java 程序演示线程的基本使用方法。这里创建的自定义线程类为 MyThread,此类继承自 Thread,并在重写的 run() 中输出一行字符串。
MyThread 类代码如下:
public class MyThread extends Thread { @Override public void run() { super.run(); System.out.println("这是线程类 MyThread"); } }
接下来编写启动 MyThread 线程的主方法,代码如下:
public static void main(String[] args) { MyThread mythread=new MyThread(); //创建一个线程类 mythread.start(); //开启线程 System.out.println("运行结束!"); //在主线程中输出一个字符串 }
运行上面的程序将看到如下所示的运行效果。
运行结束! 这是线程类 MyThread
从上面的运行结果来看,MyThread 类中 run() 方法执行的时间要比主线程晚。这也说明在使用多线程技术时,代码的运行结果与代码执行顺序或调用顺序是无关的。同时也验证了线程是一个子任务,CPU 以不确定的方式,或者说以随机的时间来调用线程中的 run() 方法,所以就会出现先打印“运行结束!”,后输出“这是线程类MyThread”这样的结果了。
例 2
上面介绍了线程的调用具有随机性,为了更好地理解这种随机性这里编写了一个案例进行演示。
(1) 首先创建自定义的线程类 MyThread01,代码如下:
package ch14; public class MyThread01 extends Thread { @Override public void run() { try { for(int i=0;i<10;i++) { int time=(int)(Math.random()*1000); Thread.sleep(time); System.out.println("当前线程名称="+Thread.currentThread().getName()); } } catch(InterruptedException e) { e.printStackTrace(); } } }
(2) 接下来编写主线程代码,在这里除了启动上面的 MyThread01 线程外,还实现了 MyThread01 线程相同的功能。主线程的代码如下:
package ch14; public class Test02 { public static void main(String[] args) { try { MyThread01 thread=new MyThread01(); thread.setName("myThread"); thread.start(); for (int i=0;i<10;i++) { int time=(int)(Math.random()*1000); Thread.sleep(time); System.out.println("主线程名称="+Thread.currentThread().getName()); } } catch(InterruptedException e) { e.printStackTrace(); } } }
在上述代码中,为了展现出线程具有随机特性,所以使用随机数的形式来使线程得到挂起的效果,从而表现出 CPU 执行哪个线程具有不确定性。
MyThread01 类中的 start() 方法通知“线程规划器”此线程已经准备就绪,等待调用线程对象的 run() 方法。这个过程其实就是让系统安排一个时间来调用 Thread 中的 run() 方法,也就是使线程得到运行,启动线程,具有异步执行的效果。
如果调用代码 thread.run() 就不是异步执行了,而是同步,那么此线程对象并不交给“线程规划器”来进行处理,而是由 main 主线程来调用 run() 方法,也就是必须等 run() 方法中的代码执行完后才可以执行后面的代码。
这种采用随机数延时调用线程的方法又称为异步调用,程序运行的效果如下所示。
当前线程名称=myThread 主线程名称=main 当前线程名称=myThread 当前线程名称=myThread 当前线程名称=myThread 主线程名称=main 当前线程名称=myThread 当前线程名称=myThread 主线程名称=main 当前线程名称=myThread 主线程名称=main 当前线程名称=myThread 当前线程名称=myThread 当前线程名称=myThread 主线程名称=main 主线程名称=main 主线程名称=main 主线程名称=main 主线程名称=main 主线程名称=main
例 3
除了异步调用之外,同步执行线程 start() 方法的顺序不代表线程启动的顺序。下面创建一个案例演示同步线程的调用。
(1) 首先创建自定义的线程类 MyThread02,代码如下:
package ch14; public class MyThread02 extends Thread { private int i; public MyThread02(int i) { super(); this.i=i; } @Override public void run() { System.out.println("当前数字:"+i); } }
(2) 接下来编写主线程代码,在这里创建 10 个线程类 MyThread02,并按顺序依次调用它们的 start() 方法。主线程的代码如下:
package ch14; public class Test03 { public static void main(String[] args) { MyThread02 t11=new MyThread02(1); MyThread02 t12=new MyThread02(2); MyThread02 t13=new MyThread02(3); MyThread02 t14=new MyThread02(4); MyThread02 t15=new MyThread02(5); MyThread02 t16=new MyThread02(6); MyThread02 t17=new MyThread02(7); MyThread02 t18=new MyThread02(8); MyThread02 t19=new MyThread02(9); MyThread02 t110=new MyThread02(10); t11.start(); t12.start(); t13.start(); t14.start(); t15.start(); t16.start(); t17.start(); t18.start(); t19.start(); t110.start(); } }
程序运行后的结果如下所示,从运行结果中可以看到,虽然调用时数字是有序的,但是由于线程执行的随机性,导致输出的数字是无序的,而且每次顺序都不一样。
当前数字:1 当前数字:3 当前数字:5 当前数字:7 当前数字:6 当前数字:2 当前数字:4 当前数字:8 当前数字:10 当前数字:9
实现 Runnable 接口
如果要创建的线程类已经有一个父类,这时就不能再继承 Thread 类,因为 Java 不支持多继承,所以需要实现 Runnable 接口来应对这样的情况。
实现 Runnable 接口的语法格式如下:
public class thread extends Object implements Runnable
提示:从 JDK 的 API 中可以发现,实质上 Thread 类实现了 Runnable 接口,其中的 run() 方法正是对 Runnable 接口中 run() 方法的具体实现。
实现 Runnable 接口的程序会创建一个 Thread 对象,并将 Runnable 对象与 Thread 对象相关联。Thread 类有如下两个与 Runnable 有关的构造方法:
-
public Thread(Runnable r);
-
public Thread(Runnable r,String name);
使用上述两种构造方法之一均可以将 Runnable 对象与 Thread 实例相关联。使用 Runnable 接口启动线程的基本步骤如下。
- 创建一个 Runnable 对象。
- 使用参数带 Runnable 对象的构造方法创建 Thread 实例。
- 调用 start() 方法启动线程。
通过实现 Runnable 接口创建线程时开发人员首先需要编写一个实现 Runnable 接口的类,然后实例化该类的对象,这样就创建了 Runnable 对象。接下来使用相应的构造方法创建 Thread 实例。最后使用该实例调用 Thread 类的 start() 方法启动线程,如图 1 所示。
发表评论