Java中Runnable和Thread的区别 常用的是implements Runable,而不是 extends Thread(Java单继承的限制)
一:Java实现多线程的方式有两种:
- 通过继承Thread类构造线程。Java定义了一个直接从根类Object中派生的Thread类,所有从这个类派生的子类或者间接子类,均为线程;
- 实现一个Runable接口;
Thread类和Runable接口之间的关系
Java API Thread类的文档中有:
创建新执行线程有两种方法。
一种方法是将类声明为 Thread 的子类。该子类应重写 Thread 类的 run 方法。接下来可以分配并启动该子类的实例。例如,计算大于某一规定值的质数的线程可以写成:
--------------------------------------------------------------------------------
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
--------------------------------------------------------------------------------
然后,下列代码会创建并启动一个线程:
class PrimeThread extends Thread {
long minPrime;
PrimeThread(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
--------------------------------------------------------------------------------
然后,下列代码会创建并启动一个线程:
PrimeThread p = new PrimeThread(143);
p.start();
----------------------------------------------------------------------------------
创建线程的
另一种方法是声明实现 Runnable 接口的类。该类然后实现 run 方法。然后可以分配该类的实例,在创建 Thread 时作为一个参数来传递并启动。采用这种风格的同一个例子如下所示:
--------------------------------------------------------------------------------
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
--------------------------------------------------------------------------------
然后,下列代码会创建并启动一个线程:
--------------------------------------------------------------------------------
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
--------------------------------------------------------------------------------
class PrimeRun implements Runnable {
long minPrime;
PrimeRun(long minPrime) {
this.minPrime = minPrime;
}
public void run() {
// compute primes larger than minPrime
. . .
}
}
--------------------------------------------------------------------------------
然后,下列代码会创建并启动一个线程:
--------------------------------------------------------------------------------
PrimeRun p = new PrimeRun(143);
new Thread(p).start();
二、继承Thread类构造线程
通过继承Thread类构造线程的步骤是:
- 创建一个类扩展(extends)Thread类;
- 用要在这个线程中执行的代码覆盖Thread类的run()方法;
- 用关键字new创建所定义的线程类的一个对象;
- 调用该对象的start()方法启动线程。
package ThreadTest;
public class ThreadTest {
public static void main(String[] argStrings)
{
System.out.println("MainThread begin");
CountingThread thread1 = new CountingThread();
thread1.start();
CountingThread thread2 = new CountingThread();
thread2.start();
CountingThread thread3 = new CountingThread();
thread3.start();
System.out.println("MainThread end");
}
}
class CountingThread extends Thread{
public void run(){
System.out.println();
System.out.println("SubThread " + this + " begin"); //this显示本对象信息,[Thread-0,5,main]依次是线程名,优先级,该线程的父线程名字
for(int i=0; i<8; i++)
System.out.println(this.getName()+".i" + (i+1)+"\t");
System.out.println();
System.out.println("SubThread " + this+ " end");
}
}
上面的例子说明的问题:
- 创建独立运行的线程很容易,java负责处理了大部分细节问题。独立运行的线程程序员不需要考虑线程之间的同步等问题;
- 无法准确知道线程在什么时候开始执行,这是由操作系统决定。并不是严格按照程序中代码顺序;
- 线程间的执行时相互独立的;
- 线程独立于启动它的线程(或程序)
三、实现Runnable接口来构造线程
有时一个类已经扩展了JFrame类或者Applet,由于单继承性,这样的类就不能再继承Thread,Runnable接口的出现克服了java只能单继承的限制。在java API中Runnable接口只包含了一个抽象的方法,定义如下:
public interface Runnable{使用Runnable接口构造线程的方法是: 要在一个扩展了Runnable接口的类中实现接口中的抽象方法run(), 并且在这个类中定义一个Thread类型的域。 当调用Thread的构造方法实例化一个线程对象时,要将定义的这个类的自身的对象(this)作为参数,因为Thread类的构造方法要求将一个Runable接口类型的对象作为参数,这个就将这个接口的run()方法与所声明的Thread对象绑定在一起,也就可以用这个对象的start和sleep方法来控制这个线程。
public abstract void run();
}
常使用的编程框架是:
package RunnableTest;上面的start方法和Stop方法并不是自动被调用的,它们提供了一种很方便的手段启动和终止线程
public class ClassName extends SuperClass implements Runnable {
private Thread threadName = null;
//该类的域和方法
//一般包含如下start方法用于启动线程,或者将方法类的代码放在类的构造方法中
public void start()
{
if(threadName == null)
{
threadName = new Thread(this);
threadName.start();
}
}
public void stop()
{
threadName = null;
}
@Override
public void run()
{
Thread currentThread = Thread.currentThread();
while(threadName == currentThread)
{
//有适当的代码调用threadName.sleep(sleeptime)或threadName.yield()
// TODO Auto-generated method stub
}
}
}
一个例子:
package RunnableTest;
import java.util.Date;
public class ShowSeconds implements Runnable {
private Thread clocker = null;
private Date now = new Date();
public ShowSeconds()
{
clocker = new Thread(this);
clocker.start();
}
@Override
public void run()
{
while(true)
{
now = new Date();
System.out.println(now);
try
{
clocker.sleep(1000);
} catch (InterruptedException e)
{
System.err.println("Thread error:" + e);
}
}
}
public static void main(String[] argStrings)
{
ShowSeconds time = new ShowSeconds();
}
}
clocker线程通过Thread类构造方法得到的对象进行初始化<new Thread(this)>, 并将ShowSeconds类当前对象(this)作为参数。 该参数将clocker线程的run()方法与ShowSeconds类实现runnable接口的run()方法绑定在一起,因而当线程启动后,ShowSeconds类的run()方法就开始执行。
上面的程序是一个无限循环的程序,不是很好。使用多线程的时候,要保证所有已经实例化和启动的线程在必要的时候能够停止,否则这些线程可能在消费系统资源而没有在做有用的事情,还是采用之前的编程框架比较合适。
四、线程的暂停和恢复
java中提供了几种方法可以暂时停止一个线程的执行,在适当的时候再恢复其执行
- sleep方法
它是Thread类中的方法,它指定线程休眠一段时间。但是线程休眠到指定时间之后,不会立刻进入执行状态,而只是可以参与调度执行。这是因为当前线程正在运行时不会立刻放弃处理机。除非休眠的线程优先级特高能抢占,或者当前线程主动退出。线程run()方法的主循环中应该包含一个带有时间参数的sleep调用,这样就能保证循环体以固定的时间间隔周期性的执行。
- yield方法
也是Thread类中的方法,作用是暂时终止正在执行的线程对象,给与它同优先级的线程执行机会。但是要是没有同优先级的线程在等待时,当前线程执行yield方法将没有任何作用,当前线程继续执行。而sleep方法时当前线程必须暂停方法时间参数长度的时间,给同优先级或者低优先级的其他线程执行机会。 这样看来,yield方法不存在浪费CPU时间的情况,而sleep方法存在浪费情况(要是没有线程在等待的话,调用了该方法的当前线程也必须暂停一段时间)。
- wait和notify方法
都是根类object类的方法,wait方法使线程进入等待状态,直到被另外的一个线程唤醒,notify方法就是把线程状态的变化通知便唤醒另外一个等待线程;
上面的方法都是Thread类或者其父类的方法,所以需要时可以在一个线程的对象中*的调用它们。
五、线程的终止
之前Thread类提供stop方法可以随时终止线程的运行,但是该方法已经过时。使得线程终止可以采用以下步骤:
- 编写一个类扩展Thread类;
- 增加一个布尔变量running到这个Thread类中,并初始化为false;
- 覆盖start()方法,首先将running变量设置为true,然后调用super.start()方法;
- 提供一个pubic方法halt(),它将running变量置为false;
- 在run方法中使用类似于下例的while循环:
public void run(){ while(running)
{/*线程需要执行的代码*/}
}
如果一个线程的halt方法被调用,就会将running变量设置为假,因而引起run()方法终止执行,从而结束该线程。