我们都知道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();rc对象是一个Runnable类,把它作为参数通过new Thread(Runnable arg)初始化了一个Thread。
Thread t1 = new Thread(rc);
我们进入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如果我们要实现同步的话,就需要加入synchronized方式了(当然还可以通过原子操作的方式通过CAS无锁实现)
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
修改后的代码如下:
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;多加入了一个类Resource.java
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();
}
}
}
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类有如下好处:
->避免单点继承的局限,一个类可以继承多个接口。
->适合于资源的共享