2.1 创建并运行Java运行时代码的三种方式

2016-05-29 20:42:45 6,185 1

1 概述

在Java中,创建线程运行时代码有三种方式。

第一种:继承Thread类,覆写其run方法,这种方式我们在之间的案例中已经见过。

第二种:实现Runnable接口,实现run方法,Thread类也实现了Runable接口。

第三种:实现Callable接口,实现其call方法,这种方式是在JDK1.5中的java并发包中引入的,因为本教程会有一个章节单独讲解Java并发包,所以在这里只是提一下有这种方式。

在讲解具体的代码如何做之前,我们先来讲解一些概念上的问题。

1.1 创建线程与创建线程运行时代码的区别

一个很常见的错误是,将创建线程运行时代码创建线程混为一谈。在这里先给出一个结论:

创建线程的方式只有一种,就是创建Thread对象的实例,创建线程运行时代码就是以上提到的三种方式。

对于Runnable和Callable接口,如果我们实现它们,主要就是为了实现接口中定义的方法,以便线程执行时回调,而实现的方法中的具体内容,就是我们所说的线程运行时代码

对于Thread,其本身是一个线程对象,不过由于其也实现了Runable接口,因此其本身是将创建线程对象线程运行时代码合为一体了。

千万不要以为是我在大惊小怪,因为这种概念上的误解实在太常见了,引用百度中一段搜索内容作为说明:

QQ截图20160529200951.png

可以看到,当我搜索"创建java线程的方式"的时候,排在前几位的都是2种或者3种(2种的情况不包含实现Callable这种方式,因为这种方式是后来引入的),这个问题就严重了,很多人都会因此而受到误解。

不过现在你已经完全了解二者的区别了,这值得庆祝一下:

2 创建线程运行时代码

现在我们知道,要编写一个并发应用,我们需要创建线程对象,同时还要创建线程运行时代码。因为Callable的方式暂时不进行讲解。因此我们主要关注的是:

  • 创建Thread子类的一个实例并重写run方法

  • 创建类的时候实现Runnable接口

2.1 创建Thread的子类,覆写run方法

创建Thread子类的一个实例并重写run方法,run方法会在调用start()方法之后被执行。这种方式是将创建线程对象和创建线程运行时代码合并为一个过程。例子如下:

public class MyThread extends Thread {
   public void run(){
     System.out.println("MyThread running");
   }
}

可以用如下方式创建并运行上述Thread子类

MyThread myThread = new MyThread();
myThread.start();

一旦线程启动后start方法就会立即返回,而不会等待到run方法执行完毕才返回。就好像run方法是在另外一个cpu上执行一样。当run方法执行后,将会打印出字符串MyThread running。

你也可以如下创建一个Thread的匿名子类:

Thread thread = new Thread(){
   public void run(){
     System.out.println("Thread Running");
   }
};
thread.start();

当新的线程的run方法执行以后,计算机将会打印出字符串”Thread Running”。

2.2 实现Runnable接口

第二种编写线程执行代码的方式是新建一个实现了java.lang.Runnable接口的类的实例,实例中的方法可以被线程调用。下面给出例子:

public class MyRunnable implements Runnable {
   public void run(){
    System.out.println("MyRunnable running");
   }
}

为了使线程能够执行run()方法,需要在Thread类的构造函数中传入 MyRunnable的实例对象。示例如下:

Thread thread = new Thread(new MyRunnable());
thread.start();

当线程运行时,它将会调用实现了Runnable接口的run方法。上例中将会打印出”MyRunnable running”。

同样,也可以创建一个实现了Runnable接口的匿名类,如下所示:

Runnable myRunnable = new Runnable(){
   public void run(){
     System.out.println("Runnable running");
   }
}
Thread thread = new Thread(myRunnable);
thread.start();

2.3 实现Callable接口

      Callable接口与Runnable接口类似,其定义了一个call方法,不同的是,其可以返回运行的结果

public interface Callable<V> {
 
    V call() throws Exception;
}

      泛型参数V就是返回值的类型。Callable接口需要与线程池结合使用,因此后文介绍到线程池的时候再进行讲解。

3、创建子类还是实现Runnable接口?

对于这两种方式哪种好并没有一个确定的答案,它们都能满足要求。就我个人意见,我更倾向于实现Runnable接口这种方法。因为Java中有一个线程池的概念。所谓线程池,可以理解为有一堆线程对象已经创建好了,那么其缺的就是线程运行时代码。所以我们只需要提供了运行时代码就好了,因此实现Runable接口可能是更好的一种方式。

4、常见错误:调用run()方法而非start()方法

创建并运行一个线程所犯的常见错误是调用线程的run()方法而非start()方法,如下所示:

Thread newThread = new Thread(MyRunnable());
newThread.run();  //should be start();

起初你并不会感觉到有什么不妥,因为run()方法的确如你所愿的被调用了。但是,事实上,run()方法并非是由刚创建的新线程所执行的,而是被 创建新线程的当前线程所执行了。也就是被执行上面两行代码的线程所执行的。想要让创建的新线程执行run()方法,必须调用新线程的start方法。

线程名

当创建一个线程的时候,可以给线程起一个名字。它有助于我们区分不同的线程。例如:如果有多个线程写入System.out,我们就能够通过线程名容易的找出是哪个线程正在输出。例子如下:

MyRunnable runnable = new MyRunnable();
Thread thread = new Thread(runnable, "New Thread");
thread.start();
System.out.println(thread.getName());

需要注意的是,因为MyRunnable并非Thread的子类,所以MyRunnable类并没有getName()方法。可以通过以下方式得到当前线程的引用:

Thread.currentThread();

因此,通过如下代码可以得到当前线程的名字:

String threadName = Thread.currentThread().getName();

线程代码举例:

这里是一个小小的例子。首先输出执行main()方法线程名字。这个线程JVM分配的。然后开启10个线程,命名为1~10。每个线程输出自己的名字后就退出。

public class ThreadExample {
  public static void main(String[] args){
     System.out.println(Thread.currentThread().getName());
      for(int i=0; i<10; i++){
         new Thread("" + i){
            public void run(){
             System.out.println("Thread: " + getName() + "running");
            }
         }.start();
      }
  }
}

需要注意的是,尽管启动线程的顺序是有序的,但是执行的顺序并非是有序的。也就是说,1号线程并不一定是第一个将自己名字输出到控制台的线程。这是因为线程是并行执行而非顺序的。Jvm和操作系统一起决定了线程的执行顺序,他和线程的启动顺序并非一定是一致的。