zoukankan      html  css  js  c++  java
  • 线程创建方式

    摘要:

    1. 通过继承Thread类来创建并启动多线程的方式

    2. 通过实现Runnable接口来创建并启动线程的方式

    3. 通过实现Callable接口来创建并启动线程的方式

    4. 总结Java中创建线程的方式,比较各自优势和区别

    一、继承Thread类创建线程类

    1.1 继承Thread类创建线程步骤

    Java使用Thread类代表线程,所有的线程对象都必须是Thread类或其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流即一段顺序执行的代码。Java使用线程执行体来代表这段程序流。Java中通过继承Thread类来创建并启动多线程的步骤如下:

    01. 定义Thread类的子类,并重写该类的run()方法,该run()方法的方法体就代表了线程需要完成的任务

         因此把run()方法称为线程执行体

    02. 创建Thread子类的实例,即创建了线程对象

    03. 调用线程对象的start()方法来启动该线程

    1.2 继承Thread类创建线程示例

    下面程序示范了通过继承Thread类来创建并启动多线程:

    1. // 通过继承Thread类来创建线程类
      
      public class MyThreadTest extends Thread {
      
          private int i;
      
          // 重写run方法,run方法的方法体就是线程执行体
      
          public void run() {
      
              for (; i < 100; i++) {
      
                  // 当线程类继承Thread类时,直接使用this即可获取当前线程
      
                  // Thread对象的getName()返回当前该线程的名字
      
                  // 因此可以直接调用getName()方法返回当前线程的名
      
                  System.out.println(getName() + "" + i);
      
              }
      
          }
      
          public static void main(String[] args) {
      
              for (int i = 0; i < 100; i++) {
      
                  // 调用Thread的currentThread方法获取当前线程
      
                  System.out.println(Thread.currentThread().getName() + "" + i);
      
                  if (i == 20) {
      
                      // 创建、并启动第一条线程
      
                      new MyThreadTest().start();
      
                      // 创建、并启动第二条线程
      
                      new MyThreadTest().start();
      
                  }
      
              }
      
          }
      
      }

    运行部分结果:

    虽然上面程序只显式地创建并启动了2个线程,但实际上程序有3个线程,即程序显式创建的2个子线程和1个主线程。前面已经提到,当Java程序开始运行后,程序至少会创建一个主线程,主线程的线程执行体不是由run()方法确定的,而是由main()方法确定的,main()方法的方法体代表主线程的线程执行体。

    该程序无论被执行多少次输出的记录数是一定的,一共是300条记录。主线程会执行for循环打印100条记录,两个子线程分别打印100条记录,一共300条记录。因为i变量是MyThreadTest的实例属性,而不是局部变量,但因为程序每次创建线程对象时都需要创建一个MyThreadTest对象,所以Thread-0和Thread-1不能共享该实例属性,所以每个线程都将执行100次循环。

    二、实现Runnable接口创建线程类

    2.1 实现Runnable接口创建线程步骤

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

    01. 定义Runnable接口的实现类,并重写该接口的run()方法,该run()方法的方法体同样是该线程的线程执行体

    02. 创建Runnable实现类的实例,并以此实例作为Thread的target来创建Thread对象,该Thread对象才是真正的线程对象

    03. 调用线程对象的start()方法来启动线程

    需要注意的是:Runnable对象仅仅作为Thread对象的target,Runnable实现类里包含的run()方法仅作为线程执行体。而实际的线程对象依然是Thread实例,只是该Thread线程负责执行其target的run()方法。

    2.2实现Runnable接口创建线程示例

    下面程序示范了通过实现Runnable接口创建线程步骤:

    1. public class MyRunnableTest implements Runnable {
      
          private int i;
      
          void print(){
      
               System.out.println(Thread.currentThread().getName() + "" + i);
      
          }
      
          // run方法同样是线程执行体
      
          public void run() {
      
              for (; i < 100; i++) {
      
                  // 当线程类实现Runnable接口时,
      
                  // 如果想获取当前线程,只能用Thread.currentThread()方法。
      
                  print();
      
              }
      
          }
      
          public static void main(String[] args) {
      
              for (int i = 0; i < 100; i++) {
      
                  System.out.println(Thread.currentThread().getName() + "" + i);
      
                  if (i == 20) {
      
                      MyRunnableTest st = new MyRunnableTest();
      
                      // 通过new Thread(target , name)方法创建新线程
      
                      new Thread(st, "新线程-1").start();
      
                      new Thread(st, "新线程-2").start();
      
                  }
      
              }
      
          }
      
      }

    运行部分结果:

    从该运行结果中我们可以看出,控制台上输出的内容是乱序的,而且每次结果不尽相同。这是因为:

    01. 在这种方式下,程序所创建的Runnable对象只是线程的target,而多个线程可以共享同一个target。

    02. 所以多个线程可以共享同一个线程类即线程的target类的实例属性。

    03. 往控制台窗口print()输出的过程并不是多线程安全的,在一个线程输出过程中另一个线程也可以输出。

    为能够保证顺序输出,我们可以对打印方法设置Synchronized,让每次只能有一个进程能够访问打印,代码如下:

    1. public class MyRunnableTest implements Runnable {
      
          private int i;
      
          synchronized void print(){
      
               System.out.println(Thread.currentThread().getName() + "" + i);
      
          }
      
          // run方法同样是线程执行体
      
          public void run() {
      
              for (; i < 100; i++) {
      
                  // 当线程类实现Runnable接口时,
      
                  // 如果想获取当前线程,只能用Thread.currentThread()方法。
      
                  print();
      
              }
      
          }
      
          public static void main(String[] args) {
      
              for (int i = 0; i < 100; i++) {
      
                  System.out.println(Thread.currentThread().getName() + "" + i);
      
                  if (i == 20) {
      
                      MyRunnableTest st = new MyRunnableTest();
      
                      // 通过new Thread(target , name)方法创建新线程
      
                      new Thread(st, "新线程-1").start();
      
                      new Thread(st, "新线程-2").start();
      
                  }
      
              }
      
          }
      
      }

    运行结果:

    该运行结果,更加明显的证明了,在这种方式下,多个线程共享了tartget类实例的属性

    三、使用Callable和Future创建线程

    3.1 Callable和Future接口概述

    3.1.1 callable接口概述

    也许受此启发,从Java 5开始,Java提供了Callable接口,该接口怎么看都像是Runnable接口的增强版,Callable接口提供了一个call()方法可以作为线程执行体,但call()方法比run()方法功能更强大。

    01. call()方法可以有返回值

    02. call()方法可以声明抛出异常

    因此我们完全可以提供一个Callable对象作为Thread的target,而该线程的线程执行体就是该Callable对象的call()方法。问题是:Callable接口是Java 5新增的接口,而且它不是Runnable接口的子接口,所以Callable对象不能直接作为Thread的target。而且call()方法还有一 个返回值-----call()方法并不是直接调用,它是作为线程执行体被调用的。那么如何获取call()方法的返回值呢?

    3.1.2 Future接口概述

    Java 5提供了Future接口来代表Callable接口里call()方法的返回值,并为Future接口提供了一个FutureTask实现类,该实现类实现了Future接口和Runnable接口可以作为Thread类的target。在Future接口里定义了如下几个公共方法来控制它关联的Callable任务:

    1. boolcan cancel(boolean maylnterruptltRunning):试图取消该Future里关联的Callable任务

    2. V get():返回Callable任务里call()方法的返回值。调用该方法将导致程序阻塞,必须等到子线程结束后才会得到返回值

    3. V get(long timeout,TimeUnit unit):返回Callable任务里call()方法的返回值。

        该方法让程序最多阻塞timeout和unit指定的时间,如果经过指定时间后Callable任务依然没有返回值,

        将会抛出TimeoutExccption异常

    4. boolean isCancelled():如果在Callable任务正常完成前被取消,则返回true

    5. boolean isDone():妇果Callable任务已完成,则返回true

    注意:Callable接口有泛型限制,Callable接口里的泛型形参类型与call()方法返回值类型相同。

    3.1.3 创建并启动有返回值的线程的步骤

    01. 创建Callable接口的实现类,并实现call()方法,该cal()方法将作为线程执行体,且该call()方法有返回值

    02. 创建Callable实现类的实例,使用FutureTask类来包装Callable对象

          该FutureTask对象封装了该Callable对象的call()方法的返回值

    03. 使用FutureTask对象作为Thread对象的target创建并启动新线程

    04. 调用FutureTask对象的get()方法来获得子线程执行结束后的返回值

    3.2使用Callable和Future创建线程示例

    下面程序示范了通过实现Callable接口创建线程步骤:

    1. public class MyCallableTest implements Callable<Integer>{
      
          // 实现call方法,作为线程执行体
      
          public Integer call(){
      
              int i = 0;
      
              for ( ; i < 100 ; i++ ){
      
                  System.out.println(Thread.currentThread().getName()+ "	" + i);
      
              }
      
              // call()方法可以有返回值
      
              return i;
      
          }
      
          public static void main(String[] args) {
      
              // 创建Callable对象
      
              MyCallableTest myCallableTest = new MyCallableTest();
      
              // 使用FutureTask来包装Callable对象
      
              FutureTask<Integer> task = new FutureTask<Integer>(myCallableTest);
      
              for (int i = 0 ; i < 100 ; i++){
      
                  System.out.println(Thread.currentThread().getName()+ " 	" + i);
      
                  if (i == 20){
      
                      // 实质还是以Callable对象来创建、并启动线程
      
                      new Thread(task , "callable").start();
      
                  }
      
              }
      
              try{
      
                  // 获取线程返回值
      
                  System.out.println("callable返回值:" + task.get());
      
              }
      
              catch (Exception ex){
      
                  ex.printStackTrace();
      
              }
      
          }
      
      }

    运行上面程序,将看到主线程和call()方法所代表的线程交替执行的情形,程序最后还会输出call()方法的返回值:

    上面程序中创建Callable实现类与创建Runnable实现类并没有太大的差别,只是Callable的call()方法允许声明抛出异常, 而且允许带返回值。当主线程中当循环变量i等于20时,程序启动以FutureTask对象为target的线程。程序最后调用FutureTask对象 的get()方法来返回call()方法的返回值——该方法将导致主线程被阻塞,直到call()方法结束并返回为止。

    四、总结

    通过以下三种途径可以实现多线程:

    01. 继承Thread类

    02. 实现Runnable接口

    03. 实现Callable接口

    不过实现Runnable接口与实现Callable接口的方式基本相同,只是Callable接口里定义的方法有返回值,可以声明抛出异常而已。 因此可以将实现Runnable接口和实现Callable接口归为一种方式。这种方式与继承Thread方式之间的主要差别如下。

    (1) 采用实现Runnable、Callable接口的方式创建多线程

    线程类只是实现了Runnable接口或Callable接口,还可以继承其他类。

    在这种方式下,多个线程可以共享同一个target对象,所以非常适合多个相同线程来处理同一份资源的情况,从而可以将CPU、代码和数据分开,形成清晰的模型,较好地体现了面向对象的思想。

    劣势:编程稍稍复杂,如果需要访问当前线程,则必须使用Thread.currentThread()方法。

    (2) 采用继承Thread类的方式创建多线程

    劣势:因为线程类已经继承了Thread类,所以不能再继承其他父类

  • 相关阅读:
    [转] 百万级数据查询优化
    vs生成失败不报错
    C# 编译器错误对应代码详细信息
    使用XSL 样式表无法查看XML 输入。请更正错误然后单击刷新按钮
    vs中release模式调试和错误:CA0503:无法显示额外的代码分析警告或错误
    sp_executesql的用法
    一个for循环根据条件可以递增或递减
    Lc.exe已退出 代码为1
    将所有输出窗口文本重定向到即时窗口
    两步解决《内部服务器错误:500》
  • 原文地址:https://www.cnblogs.com/zmy-520131499/p/11190764.html
Copyright © 2011-2022 走看看