java创建线程implement runnable 和 extends thread 比较

时间:2021-08-24 14:10:44

我们都知道java实现线程有两种方法

一。继承Thread类

二。实现Runnable接口


看到很多说法他们之间有一个区别是:实现Runnable接口才能实现资源共享。继承thread的方式不行

并且附有类似以下的实例:

//Implement Runnable 的方式来实现线程
class ImplementsRunnable implements Runnable {

private int counter = 0;

public void run() {
counter
++;
System.out.println("ImplementsRunnable : Counter : " + counter);
}
}

//通过继承 Thread 类的方式
class ExtendsThread extends Thread {

private int counter = 0;

public void run() {
counter
++;
System.out.println("ExtendsThread : Counter : " + counter);
}
}

public class ThreadVsRunnable {

public static void main(String args[]) throws Exception {
// 多线程共享同一个变量(rc)
ImplementsRunnable rc = new ImplementsRunnable();
Thread t1 = new Thread(rc);
t1
.start();
Thread.sleep(1000); //启动下一个线程前,等待一秒钟
Thread t2 = new Thread(rc);
t2
.start();
Thread.sleep(1000); //启动下一个线程前,等待一秒钟
Thread t3 = new Thread(rc);
t3
.start();

// 通过extend Thread只能为每一个线程创建新的对象
ExtendsThread tc1 = new ExtendsThread();
tc1
.start();
Thread.sleep(1000);
ExtendsThread tc2 = new ExtendsThread();
tc2
.start();
Thread.sleep(1000);
ExtendsThread tc3 = new ExtendsThread();
tc3
.start();
}
}

输出结果如下:

ImplementsRunnable : Counter : 1
ImplementsRunnable : Counter : 2
ImplementsRunnable : Counter : 3
ExtendsThread : Counter : 1
ExtendsThread : Counter : 1
ExtendsThread : Counter : 1

他们通过这个例子得出:实现Runnable接口才能实现资源共享。继承thread的方式不行 这个结论

其实我们仔细看一下这个例子,就会发现这个结论是错的:


1.首先我们看一下通过 Implements Runnable 方式创建线程时,发生了什么

截取上面例子中的两行代码:

ImplementsRunnable rc = new ImplementsRunnable();
Thread t1 = new Thread(rc);
rc对象是一个Runnable类,把它作为参数通过new Thread(Runnable arg)初始化了一个Thread。

我们进入Thread类的源码看一下(经过简化,要查看完整方法,参看java源码):

当new Thread的时候,会调用Thread的init()方法,初始化这个线程

/**
* 初始化一个Thread
* .......
* @参数 target :这个target参数的run()方法将会被最后调用(the object whose run() method gets called)
* ........
*/
private void init(...,Runnable target,....) {
......
this.target = target;
......
}

我们看到这个函数的关键是用传递给他的参数初始化这个Thread,注释中明确说道调用Thread的start()方法时,最终会调用的是这个target参数的run方法,当这个Thread调用start()方法后,最终会调用到如下run()方法:

    public void run() {
if (target != null) {
target.run();
}
}

我们可以看到正如前面init()方法的注释所描述的一样,最终会调用的是线程init()时传递给这个Thread的target参数的run方法。

所以正如一开始的例子中的代码那样,用同一个Runnable target初始化多个线程,那么这多个线程都会调用同一个target的run()方

法。至始至终这个ImplementsRunnable对象就只有一个。


在这里要加一段话 虽然这样实现了资源的共享,但是并没有实现资源的同步,如下例子,我加入一个条件,如果counter小于10才能继续+1(比如多个窗口同时卖火车票的场景):

class ImplementsRunnable implements Runnable {
private int counter = 0;
public void run() {
while(counter<10){
counter++;
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
}
System.out.println(Thread.currentThread().getName()+":"+ counter);
}
}
}

public static void main(String args[]) throws Exception {
// Multiple threads share the same object.
ImplementsRunnable rc = new ImplementsRunnable();
Thread t1 = new Thread(rc);
t1.start();
Thread.sleep(1000); // Waiting for 1 second before starting next thread
Thread t2 = new Thread(rc);
t2.start();
Thread.sleep(1000); // Waiting for 1 second before starting next thread
Thread t3 = new Thread(rc);
t3.start();
}
运行结果如下,出现了同步错误:

Thread-5:2
Thread-6:3
Thread-5:4
Thread-5:6
Thread-6:7
Thread-7:8
Thread-5:9
Thread-6:10
Thread-7:9
Thread-5:10
如果我们要实现同步的话,就需要加入synchronized方式了(当然还可以通过原子操作的方式通过CAS无锁实现)

修改后的代码如下:

class ImplementsRunnable implements Runnable {
private int counter = 0;
public void run() {
while(counter<10){
synchronized(this){
if(counter<10){
counter++;
System.out.println(Thread.currentThread().getName()+":"+ counter);
}
}
}
}
}
运行结果如下,达到了我们的目的:

Thread-5:1
Thread-6:2
Thread-7:3
Thread-7:4
Thread-6:5
Thread-6:6
Thread-6:7
Thread-6:8
Thread-6:9
Thread-5:10

那我们在看看通过extend Thread 的方式。它并没有一个共同的Runnable对象来初始化3个Thread对象。每一个Thread都拥有自己一个独立的Runnable对象。

当然不能实现资源的共享,因为现在每个Thread都有一个自己counter对象了。


那么问题来了:难道用extends Thread的方式就不能实现共享吗,答案是肯定可以实现:

方法有很多。我自己试着写了一个:

package test;
public class TestThread {

public static void main(String args[]) throws Exception {
ExtendsThread tc1 = new ExtendsThread();
tc1.start();
ExtendsThread tc2 = new ExtendsThread();
tc2.start();
ExtendsThread tc3 = new ExtendsThread();
tc3.start();
}
}

class ExtendsThread extends Thread {
public void run() {
while(Resource.getCounter()<10){
Resource.count();
}
}

}
多加入了一个类Resource.java
package test;public class Resource {private static int counter = 0;public static int getCounter(){return counter;}public static synchronized void count(){if(counter<10){counter++;try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}System.out.println(Thread.currentThread().getName()+":"+counter);}}} 

运行结果如下:

Thread-5:1
Thread-5:2
Thread-6:3
Thread-5:4
Thread-7:5
Thread-6:6
Thread-7:7
Thread-6:8
Thread-7:9
Thread-6:10

可以看到 在这段代码中 我counter设置成了static,相应的用了class级别的锁。

不过相比implement runnable的方式还是麻烦了很多。


所以从上面两个比较我们可以得出

实现Runnable接口相比继承Thread类有如下好处:

  ->避免单点继承的局限,一个类可以继承多个接口。

  ->适合于资源的共享