知识点:
掌握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.笔试如果要写多线程的实现,第三种方法是一个加分项。