【Java多线程学习】Java多线程的两种实现方式:继承Thread类 & 实现Runable接口

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
              . . .
         }

     } 
--------------------------------------------------------------------------------
然后,下列代码会创建并启动一个线程:

     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();


二、继承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{
	public abstract void run();	
}
使用Runnable接口构造线程的方法是: 要在一个扩展了Runnable接口的类中实现接口中的抽象方法run(), 并且在这个类中定义一个Thread类型的域当调用Thread的构造方法实例化一个线程对象时,要将定义的这个类的自身的对象(this)作为参数,因为Thread类的构造方法要求将一个Runable接口类型的对象作为参数,这个就将这个接口的run()方法与所声明的Thread对象绑定在一起,也就可以用这个对象的start和sleep方法来控制这个线程。
常使用的编程框架是:
package RunnableTest;

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
		}
		
	}

}
          上面的start方法和Stop方法并不是自动被调用的,它们提供了一种很方便的手段启动和终止线程

一个例子:
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类构造方法得到的对象进行初始化, 并将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()方法终止执行,从而结束该线程。

你可能感兴趣的:(知识学习)