原文地址: http://www.tianshouzhi.com/api/tutorials/mutithread/62
1 概述
在Java中,创建线程运行时代码
有三种方式。
第一种:继承Thread
类,覆写其run
方法,这种方式我们在之间的案例中已经见过。
第二种:实现Runnable
接口,实现run
方法,Thread类也实现了Runable接口。
第三种:实现Callable
接口,实现其call
方法,这种方式是在JDK1.5中的java并发包中引入的,因为本教程会有一个章节单独讲解Java并发包,所以在这里只是提一下有这种方式。
在讲解具体的代码如何做之前,我们先来讲解一些概念上的问题。
1.1 创建线程与创建线程运行时代码的区别
一个很常见的错误是,将创建线程运行时代码
和创建线程
混为一谈。在这里先给出一个结论:
创建线程的方式只有一种,就是创建Thread对象的实例,创建线程运行时代码就是以上提到的三种方式。
对于Runnable和Callable接口,如果我们实现它们,主要就是为了实现接口中定义的方法,以便线程执行时回调,而实现的方法中的具体内容,就是我们所说的线程运行时代码
。
对于Thread,其本身是一个线程对象,不过由于其也实现了Runable接口,因此其本身是将创建线程对象
和线程运行时代码合
为一体了。
千万不要以为是我在大惊小怪,因为这种概念上的误解实在太常见了,引用百度中一段搜索内容作为说明:
可以看到,当我搜索"创建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();
- myTread.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();
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和操作系统一起决定了线程的执行顺序,他和线程的启动顺序并非一定是一致的。