多线程之(线程创建)

[Toc]

线程创建

作为一名优秀的程序员,怎么能不懂多线程呢?那想搞懂多线程,第一步就是先创建线程跑起来。说到创建线程,方式多种多样。其实最根本的就是三种方式,剩下的无非就是语法变一变的玩意。

创建线程的三种方式

  • 继承Thread
  • 实现Runnable接口
  • 实现Callable接口

第一种,继承Thread.

先说为什么要继承Thread类呢?为什么不直接new 一个Thread对象来创建线程呢?原则上是可以直接new 一个Thread对象来创建一个线程。但是我们要想一下创建一个线程做什么呢?对的,肯定是想来跑我们自己的代码。我们应该知道当线程被开启执行的是run()方法。即使不知道,那就记住。这个run()方法就存在于Thread中。但是呢Thread中的run()方法几乎是没什么东西的,那我们肯定要把自己的代码写到run()方法中,那怎么来写呢?继承父类就可以重写。这样讲,就明白为什么要继承Thread类了吧。
话不多说上码

package cn.zl;
// 第一种方式创建线程,通过继承Thread。
public class ThreadTest extends Thread{
    private int count = 10000;
    @Override
    public void run() {
        while (true){
            System.out.println("count"+count);
            if (--count<0){
                break;
            }
        }
    }
}

既然我们的线程已经创建好,那就跑一把。

package cn.zl;
public class Main {
    public static void main(String[] args) {
      // 创建线程
        ThreadTest threadTest = new ThreadTest();
      //  启动线程
        threadTest.start();
      // 为了我们执行的结果可以交叉显示,所以写了以下代码。
        int i=10000;
        while (i>0){
            System.out.println("Main"+i);
            i--;
        }
    }
}

这时候,有个重点,就是我们线程启动,是通过start()方法,而不是直接调用run()方法。想要搞定这个东西,都需要研究一下JVM。因为start()方法其实是一个本地方法,由C/C++来实现的。但是对于我们Java程序员来讲,我们要记住线程启动时通过调用start()方法。第一种方式已经完成,学习是一个循序渐进的过程,尤其对java程序员来说,先学会怎么用,再去研究原理。

第二种,实现Runnable接口.

还是刚才那个问题,有没有不通过继承Thread的方式来重写run()方法呢?答案肯定是有的。Thread这个类对外提供了传参Runnable的实现类来重写run()方式。于是这就是我们要说的第二种了。通过实现Runnable接口中的run()方法,并把此实现类当做参数传入Thread中。
话不多说,上码

package cn.zl;
// 第二种线程创建的方式,实现Runnable
public class RunnableTest implements Runnable {
    private int i = 10000;
    public void run() {
        while (i > 0) {
            System.out.println("Runnable" + i);
            i--;
        }
    }
}

跑一把

package cn.zl;
public class Main {
    public static void main(String[] args) {
        // 创建一个线程
        Thread thread = new Thread(new RunnableTest());
        // 启动线程
        thread.start();
       // 为了我们执行的结果可以交叉显示,所以写了以下代码。
        int i=10000;
        while (i>0){
            System.out.println("Main"+i);
            i--;
        }
    }
}

这样第二种方式就完成了。稍后讲一下三种的区别。既然存在三种,三种一定有所不同。

第三种,实现Callable接口

这一种,猛一下类似于第二种。这一种,我们不是通过实现run()方法来重写Thread中的run()方法,而是我们通过实现call()方法来重写Thread中的run()方法。
废话不多说,上码

package cn.zl;
import java.util.concurrent.Callable;
// 第三种创建线程的方式,实现Callable方式
public class CallableTest implements Callable {
    private int i = 10000;
    public Integer call() throws Exception {
        while (i>0){
            System.out.println("Callable"+i);
            i--;
        }
        return i;
    }
}

跑一下

package cn.zl;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class Main {
    public static void main(String[] args) {
         // 创建Future对象来装载Callable的实现类对象(CallableTest)。
        FutureTask integerFutureTask = new FutureTask(new CallableTest());
        // 创建线程
        Thread thread1 = new Thread(integerFutureTask);
       // 启动线程
        thread1.start();
        try {
          //  获取线程中的返回值,并输出
            System.out.println(integerFutureTask.get()+"--------------");
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }
        // 为了我们执行的结果可以交叉显示,所以写了以下代码。
        int i=10000;
        while (i>0){
            System.out.println("Main"+i);
            i--;
        }
    }
}

通过以上的代码,我们可以感受到。用实现Callable方式,我们不仅能创建线程,还能获取线程的返回值。而不是像之前一样,线程启动了,到结束都不给我们打个招呼。这种方式,就比较友好。不受控,对程序员来说是一件很可怕的事情。

三种方式的优缺点

  • 采用继承Thread类方式:
    (1)优点:编写简单,如果需要访问当前线程,无需使用Thread.currentThread()方法,直接使用this,即可获得当前线程。当然是在我们自己写的子类中使用this。
    (2)缺点:因为线程类已经继承了Thread类,所有不能再继承其他的父类,这是Java语言的一大特性,单继承多实现。
  • 采用实现Runable接口方式:
    (1)优点:线程只是实现了Runnable接口,还可以继承其他的类。在这种方式下,可以多个线程共享同一目标对象,所有非常适合多个相同的线程处理同一份资源的情况,从而可以将CPU代码和数据分开,形成清晰的模型。
    (2)缺点:编程稍微复杂,如果需要访问当前线程,必须使用Thread.currentThread()方法。
  • Runnable 和Callable的区别:
    (1)Callable 规定的方法是call(),Runable规定的是run();
    (2)Callable 的任务执行后可返回值,而Runable的任务是不能获取返回值的。
    (3)call 方法可以抛出异常,run方法不可以,因为run方法本身没有抛出异常,所以自定义的线程类在重写run的时候也无法抛出异常。
    (4) 运行Callable 任务可以拿到一个Future对象,表示异步计算的接口。它提供了检查计算是否完成的方法,以等待计算的完成,并检索计算的结果。通过Future对象可以了解任务执行情况,可取消任务的执行,还可获取执行结果。

start()和run() 的区别

  • start()方法用来,开启线程,但是线程开启后并没有立即执行,他需要获取CPU的执行权才可以执行。
  • run()方法有JVM 创建完本地操作系统级线程后回调的方法,不可以手动调用(否则就是普通方法)。

消化一下,后续写更深点的东西,学习讲究循序渐进。

你可能感兴趣的:(多线程之(线程创建))