Java 线程对比(Thread,Runnable,Callable)实例详解
java 线程对比thread,runnable,callable
java 使用 thread 类代表线程,所有现场对象都必须是 thread 类或者其子类的实例。每个线程的作用是完成一定的任务,实际上就是执行一段程序流。java 使用线程执行体来代表这段程序流。
1.继承thread 类创建线程
启动多线程的步骤如下:
(1)定义thread 类的子类,并重写该类的run() 方法,该run() 方法的方法体就代表类线程需要完成的任务。因此把run() 方法称为线程执行体。
(2)创建 thread 子类的实例,即创建线程对象。
(3)调用线程的star()方法来启动该线程。
相关代码如下:
/** * 继承 thread 的内部类,以买票例子 */ public class firstthread extends thread{ private int i; private int ticket = 10; @override public void run() { for (;i<20;i++) { //当继承thread 时,直接使用this 可以获取当前的线程,getname() 获取当前线程的名字 // log.d(tag,getname()+" "+i); if(this.ticket>0){ log.e(tag, getname() + ", 卖票:ticket=" + ticket--); } } } } private void starticketthread(){ log.d(tag,"starticketthread, "+thread.currentthread().getname()); firstthread thread1 = new firstthread(); firstthread thread2 = new firstthread(); firstthread thread3 = new firstthread(); thread1.start(); thread2.start(); thread3.start(); //开启3个线程进行买票,每个线程都卖了10张,总共就30张票 }
运行结果:
可以看到 3 个线程输入的 票数变量不连续,注意:ticket 是 firstthread 的实例属性,而不是局部变量,但是因为程序每次创建线程对象都需要创建一个firstthread 的对象,所有多个线程不共享该实例的属性。
2.实现 runnable 接口创建线程
注意:public class thread implements runnable
(1)定义 runnable 接口的实现类,并重写该接口的run()方法,该run() 方法的方法体同样是该线程的线程执行体。
(2)创建 runnable 实例类的实例,此实例作为 thread 的 target 来创建thread 对象,该thread 对象才是真正的对象。
相关代码如下:
/** * 实现 runnable 接口,创建线程类 */ public class secondthread implements runnable{ private int i; private int ticket = 100; @override public void run() { for (;i<20;i++) { //如果线程类实现 runnable 接口 //获取当前的线程,只能用 thread.currentthread() 获取当前的线程名 log.d(tag,thread.currentthread().getname()+" "+i); if(this.ticket>0){ log.e(tag, thread.currentthread().getname() + ", 卖票:ticket=" + ticket--); } } } } private void starticketthread2(){ log.d(tag,"starticketthread2, "+thread.currentthread().getname()); secondthread secondthread = new secondthread(); //通过new thread(target,name)创建新的线程 new thread(secondthread,"买票人1").start(); new thread(secondthread,"买票人2").start(); new thread(secondthread,"买票人3").start(); //虽然是开启了3个线程,但是一共只买了100张票 }
运行结果:
可以看到 3 个线程输入的 票数变量是连续的,采用 runnable 接口的方式创建多个线程可以共享线程类的实例的属性。这是因为在这种方式下,程序所创建的runnable 对象只是线程的 target ,而多个线程可以共享同一个 target,所以多个线程可以共享同一个线程类(实际上应该是该线程的target 类)的实例属性。
3.使用 callable 和future 创建线程
从 java 5 开始,java 提供了 callable 接口,该接口是runnable 的增强版,callable 提供类一个 call() 方法可以作为线程执行体,但是call() 方法的功能更强大。
(1) call() 方法可以有返回值
(2) call() 方法可以声明抛出异常
因此我们完全可以提供一个callable 对象作为thread的 target ,而该线程的执行体就是该callable 对象的call() 方法。同时 java 5 提供了 future 接口 来代表callable 接口里 call() 方法的返回值,并且提供了一个 futuretask 的实现类,该实现类实现类 future 接口,并实现了runnable 接口—可以作为thread 类的target.
启动步骤如下:
(1)创建callable接口的实现类,并实现call() 方法,该call() 方法将作为线程的执行体,且该call() 方法是有返回值的。
(2)创建 callable实现类的实例,使用 futuretask 类来包装callable对象,该futuretask 对象封装 call() 方法的返回值。
(3)使用futuretask 对象作为thread对象的target创建并启动新线程。
(4)调用futuretask对象的get()方法来获取子线程执行结束后的返回值。
相关代码如下:
/** * 使用callable 来实现线程类 */ public class thirdthread implements callable<integer>{ private int ticket = 20; @override public integer call(){ for ( int i = 0;i<10;i++) { //获取当前的线程,只能用 thread.currentthread() 获取当前的线程名 // log.d(tag,thread.currentthread().getname()+" "+i); if(this.ticket>0){ log.e(tag, thread.currentthread().getname() + ", 卖票:ticket=" + ticket--); } } return ticket; } } private void starcallablethread(){ thirdthread thirdthread = new thirdthread(); futuretask<integer> task = new futuretask<integer>(thirdthread); new thread(task,"有返回值的线程").start(); try { integer integer = task.get(); log.d(tag,"starcallablethread, 子线程的返回值="+integer); } catch (interruptedexception e) { e.printstacktrace(); } catch (executionexception e) { e.printstacktrace(); } }
运行结果:
注意:callable的call() 方法允许声明抛出异常,并且允许带有返回值。
程序最后调用futuretask 对象的get()方法来返回call()方法的返回值,导致主线程被阻塞,直到call()方法结束并返回为止。
4.三种方式的对比
采用继承thread 类的方式创建多线程
劣势: 已经继承thread类不能再继承其他父类。
优势: 编写简单
采用继承runnable,callable 接口的方式创建多线程
劣势: 编程稍微有点复杂,如果需要访问当前线程必须使用thread.currentthread()
优势:
(1)还可以继承其他类
(2)多个线程可以共享一个target 对象,所以非常适合多个相同的线程来处理同一份资源的情况,从而将cpu,代码和数据分开,形成清晰的模型,较好的体现类面向对象的思想。
感谢阅读,希望能帮助到大家,谢谢大家对本站的支持!