No6.多线程的实现

知识点:

   掌握java中三种多线程的实现方式。

具体内容:

   java实现多线程有两种方式:

      1.继承Thread类;

      2.实现Runnable接口(Callable接口);

方式一:继承Thread类:

   Thread类是一个支持多线程的功能类,只要有一个子类就可以实现多线程的支持。

class MyThread extends Thread{  //这是一个多线程操作类
	
}

   所有程序的起点是main()方法,但是所有的线程也一定要有一个自己的起点,那么这个起点就是run()方法,也就是说在多线程的主题类中都必须覆写Thread类中所提供的run()方法。

public void run(),这个方法没有返回值,那么也就只表示县城一点开始就要一直执行,不能够返回内容。

class MyThread extends Thread{  //这是一个多线程操作类
	private String name; //定义类中的属性
	public MyThread(String name){
		this.name=name;   //定义构造方法
	}

	@Override
	public void run() {   //覆写run()方法,作为线程的主体操作方法。
		for(int x=0;x<200;x++){
			System.out.println(this.name+"-->"+x);
		}
	} 
	
}

本线程的功能是进行循环的输出操作,所有的线程与进程都是一样的,都必须轮流去抢占资源,所有的多线程的执行应该是多个线程彼此交替执行。一下的代码执行的结果可以看到,它是一个线程执行完了之后,再执行的下一个线程,所以一个run()并不能够启动多线程。

       那怎么启动呢?

唯一方法就是Thread类中的start()方法:public void star(),调用此方法执行的方法体是run()定义的。

package cn.mldn.demo1;

class MyThread extends Thread{  //这是一个多线程操作类
	private String name; //定义类中的属性
	public MyThread(String name){
		this.name=name;   //定义构造方法
	}

	@Override
	public void run() {   //覆写run()方法,作为线程的主体操作方法。
		for(int x=0;x<5;x++){
			System.out.print(this.name+"-->"+x);
		}
		System.out.println("\n");
	} 
	
}

public class TestDemo {

	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程A");
		MyThread mt2 = new MyThread("线程B");
		MyThread mt3 = new MyThread("线程C");
		mt1.run();
		mt2.run();
		mt3.run();
	}

}

运行结果:

使用start()方法.。

 

public class TestDemo {

	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程A");
		MyThread mt2 = new MyThread("线程B");
		MyThread mt3 = new MyThread("线程C");
		mt1.start();
		mt2.start();
		mt3.start();
	}

}

执行结果如下,已经可以交替执行。

     疑问,为什么多线程不是调用run()?,而必须调用start()。

看一下 start()方法的定义。

  public synchronized void start() {
       
        if (threadStatus != 0)//状态校验  0:NEW 新建状态
             throw new IllegalThreadStateException();
         group.add(this);//添加进线程组
         
         boolean started = false;
         try {
             start0();//调用native方法执行线程run方法
             started = true;
         } finally {
             try {
                 if (!started) {
                     group.threadStartFailed(this);//启动失败,从线程组中移除当前前程。
                 }
             } catch (Throwable ignore) {
                 
             }
         }
     }
 
     private native void start0();

   首先方法再Thread类的start()方法里面存在有一个“IllegalThreadStateException()”异常抛出。本方法使用了Throws方法抛出异常,按照道理来讲应该使用try....catch处理,或则再start()方法声明上使用throws ,但是此处并没有这样的代码,因为此异常属于RuntimeException的子类,属于选择性处理。如果某一个线程重复启动就会抛出此异常。

      在start()方法里面要调用一个start0 ()方法,而且此方法与抽象方法结构类似,另外还使用了native声明。Java里面有一门叫JNI技术,即(Java Native Interface),这门技术的特点:是使用java调用本机操作系统提供的函数。但是这样的技术有一个特点就是不能够离开特点给的操作系统。

方式二:实现Runable接口(一般用这个)

       因为虽然Threa类可以实现多线程类定义,但是因为java的单继承局限不是很方便,在整个java里面类的继承问题都是应该回避的问题。在java里面提供了Runable接口。

        接口的定义如下:

@FunctionalInterface

public interface Runnable{

        public void run();

}

在接口里面任何的方法都是public定义的权限,不存在默认的权限。

那么只需要让一个类实现Runnable接口即可,并且覆写run()方法。

class MyThread implements Runnable{  //这是一个多线程操作类
	private String name; //定义类中的属性
	public MyThread(String name){
		this.name=name;   //定义构造方法
	}

	@Override
	public void run() {   //覆写run()方法,作为线程的主体操作方法。
		for(int x=0;x<20;x++){
			System.out.print(this.name+"-->"+x);
		}
		
	} 
	
}

   此时的myThread类在结构上与之前是没有区别的,但是有一点是有严重区别的,如果直接继承了Threrad类,那么就可以直接继承start()方法,但是如果实现的是Runnable接口是没有start()方法的。

    思考:不管在什么情况下,如果想要启动多线程久一定要依靠Thread类完成,在Thread类里面定义有一下构造方法。

  • 构造方法:Public Thread(Runnable target),接收的是Runnable接口对象;注意:这就标识可以接收任意的Runnable子类

范例:启动多线程

public class TestDemo {

	public static void main(String[] args) {
		MyThread mt1 = new MyThread("线程A");
		MyThread mt2 = new MyThread("线程B");
		MyThread mt3 = new MyThread("线程C");
		new Thread(mt1).start();
		new Thread(mt2).start();
		new Thread(mt3 ).start();
	}

}

此时就避免了单继承局限,其实一般在工作种使用Runnable比较多。

多线程两种实现方式的区别?(面试题)

两种方式有那些区别呢?

1.Runnable解决勒Thread类相比解决了单继承局限。这一点决定了如果使用多线程就多Runnable接口。

2. Thread定义如下 :public class Thread extends Object implements Runnable

     发现Thread类实现了Runnable接口,那么这样程序就编程了一下形式:

     此时整个的定义结构,看起来和代理模式很相似。如果是代理模式,那么客户端调用的代理类也应该是接口里提供的方法,也应该是run()才对。

3.使用Runnable接口能比Thread类更好的描述出数据共享的概念。此时的共享指的是多个线程访问同一资源的操作。

代码:观察代码(每一个呈现对象都必须通过start()启动)

package cn.mldn.demo1;

class MyThread extends Thread{  //这是一个多线程操作类
	private int ticket = 10;
	@Override
	public void run() {   //覆写run()方法,作为线程的主体操作方法。
		for(int x=0;x<100;x++){
			if(this.ticket>0){
				System.out.println("买票,ticket= "+this.ticket--);
			}
		}
		
	} 
	
}

public class TestDemo {

	public static void main(String[] args) {
 //由于MyThread类有一个start()方法,所以每一个myThread类对象就是一个线程,可以直接启动。
		MyThread mt1 = new MyThread();
		MyThread mt2 = new MyThread();
		MyThread mt3 = new MyThread();
		mt1.start();
		mt2.start();
		mt3.start();
	}

}

本程序声明了三次MyThread类对象,并且分别调用了三次start()方法,启动线程对象。代码运行结果如下图,

 发现里面有三个8,因此每一个线程对象都在买各自的10张票。

原因?内存图如下

范例:利用Runnable来实现

package cn.mldn.demo1;

class MyThread implements Runnable{  //这是一个多线程操作类
	private int ticket = 10;
	@Override
	public void run() {   //覆写run()方法,作为线程的主体操作方法。
		for(int x=0;x<100;x++){
			if(this.ticket>0){
				System.out.println("买票,ticket= "+this.ticket--);
			}
		}
		
	} 
	
}

public class TestDemo {

	public static void main(String[] args) {
		MyThread mt = new MyThread();
		new Thread(mt).start();
		new Thread(mt).start();
		new Thread(mt).start(); 
	}

}

 代码运行结果:

内存分析图如下:

此时也属于三个线程对象,可是唯一的区别就是,这三个线程对象都直接占用了统一个MyThread类的对象的引用,也就是说这三个线程对象都访问统一个线程资源。总结:其实Thread也是Runnable的子类,一起可以想前一条代码那样执行,但是本身因为有了start()方法再给新建一个Thread去执行,就有点别扭!
面试题:请解释Thread类与Runnable接口实现多线程的区别?或请解释多线程两种实现方式的区别?

答:Thread类是Runnable接口的子类,使用Runnable接口可以避免单继承局限。

    Runnable接口实现的多线程可以比Thread类实现的多线程更加清楚的描述数据共享的概念。

面试题:请写出多线程两种实现操作。

答:把上面代码写出来即可。

方式三:第三种实现方式(理解)

   虽然Runnable接口避免了单继承局限,但是其中的run()方法不能返回操作结果。为了解决这样的矛盾,提供了一个新的接口java.util.concurrent.Callable接口。

定义如下:

@FunctionlIterface

public interface Callable<v>{

        public v call() throws Exception;

}

call()方法执行完成线程的主题功能之后可以返回一个结果,而返回结果的类型由Callable接口上的泛型来决定。

范例:定义一个线程主题类

mport java.util.concurrent.Callable;

class MyThread implements Callable<String>{  //这是一个多线程操作类
	private int ticket = 10;
	@Override
	public String call() throws Exception{  
		for(int x=0;x<100;x++){
			if(this.ticket>0){
				System.out.println("买票:ticket = "+this.ticket);
			}
		}
		return "票已经卖光了";
	} 
}

  此时观察Thread类里面并没有直接支持Callable接口多线程的应用。

从JDK1.5开始提供有java.util.concurrent.FutureTask<V>类。这个类主要负责Callable接口对象的操作。

这个接口定义如下:

  public class FutureTask<V>

  extends Object

  implements RunnableFuture<V>

RunnableFuture<V>接口定义如下:

public interface RunnableFuture<V> extend Runnable,Future<V> 

FutureTask类里面定义有如下构造方法:public FutureTask(Callable<V> callable),接受的目的就是取得call()方法的返回结果。

package cn.mldn.demo1;

import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;

class MyThread implements Callable<String>{  //这是一个多线程操作类
	private int ticket = 10;
	@Override
	public String call() throws Exception{  
		for(int x=0;x<100;x++){
			if(this.ticket>0){
				System.out.println("买票:ticket = "+this.ticket--);
			}
		}
		return "票已经卖光了";
	} 
}
     
public class TestDemo {

	public static void main(String[] args) throws Exception {
		MyThread mt1 = new MyThread();
		MyThread mt2 = new MyThread();
		FutureTask<String> task1 = new FutureTask<String>(mt1);  //目的是为了取得call()的返回结果。
		FutureTask<String> task2 = new FutureTask<String>(mt2);
		//FutureTask是Runnable接口子类,所以可以使用Thread类的构造来接收task对象
	     new Thread(task1).start(); //启动多线程
	     new Thread(task2).start();
	     //多线程执行完毕后可以取得内容,依靠FutureTask的父接口的Future中的get()方法完成
	     System.out.println("A线程的返回结果:"+task1.get());
	     // 注意这里的task1使用的get()方法是来自于FutureTask类实现的接口RunnableFuture继承的Future类的方法	     
	     System.out.println("A线程的返回结果:"+task2.get());
	}

}


执行结果:

最麻烦的问题在于需要接受返回值,并且又要与原始的多线程的实现靠拢 (向Thread类)

总结:

1.对于多线程的实现,重点在于Runnable接口与Thread类启动的配合上。

2.对于JDK1.5新特性,了解就行了,知道区别在于返回结果上。

3.笔试如果要写多线程的实现,第三种方法是一个加分项。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值