Java线程基础(2)-实现线程的三种基本方法

来源:互联网 发布:android aop 编程 编辑:程序博客网 时间:2024/06/14 20:28
一:关于三种方法的简介
1、在Java中,所有的线程对象都是Thread类或Thread类的实例,相较于C#来说,Java不支持将任意一段代码包装成线程执行体,而是通过Thread的run方法、Runnable的run方法或者Callable的call方法来进行包装。使用Runnable或者callable接口都需要使其成为Thread类的target来创建Thread对象。
2、callable相较于继承Thread类实现run方法或者实现Runnable并实现run方法,其可以通过Future接口来获得线程的返回值,判断线程是否取消或者正常运行结束。该接口出现在Java5之后,这或许是参考了它的模仿者C#。Java还提供了Future的实现类FutureTask类来满足开发者的使用。
3、从Java8之后Runnable接口使用了@FunctionInterface修饰,这表示其为函数式接口,可以使用Lambda表达式来创建,Callable同样为函数式接口。

二:三种基本创建线程的方法:
1、继承Thread类并实现Run方法。
Thread实现了Runnable接口。
eg:
public class TestThread extends Thread {
public TestThread(String name) {
setName(name);
}
public static void main(String[] args) {
for (int i = 0; i < 3; i++) {
new TestThread("thread-a-" + i).start();
}
new Thread("thread-b") {
@Override
public void run() {
System.out.print("Thread id:" + this.getId() + "\tThread name" + this.getName()+"\n");
StackTraceElement[] elements = this.getStackTrace();
for (StackTraceElement element : elements) {
System.out.println("cls name:" + element.getClassName()
+ "\nfile name:" + element.getFileName()
+ "\nmethod name:" + element.getMethodName()
+ "\nline number:" + element.getLineNumber());
}
}
}.start();
}
@Override
public void run() {
// 执行体
System.out.println("Thread id:" + this.getId() + "\tThread name" + this.getName());
}
}
备注:通过继承Thread类并实现其run方法即可以实现线程的创建,直接继承Thread类,可以通过其方法getName()、getId()来获得线程的名称以及线程Id,使用getStackTrace方法可以获取到更多的详细信息,如当前运行的方法名、类名等等。特别应该注意的是,实现Thread的实例并不代表线程已经就绪准备运行,只有调用了Thread的start方法才能够使其进行就绪状态,并有JVM来进行调度执行。

2、实现Runnable接口并实现run方法
通过implements Runnable接口使该实现类可以成为Thread类的target,run方法即为Thread类的线程执行体。这里应该注意的是Runnable接口的实例仅仅是作为Thread的target来使用,实际的线程对象依然是Thread或Thread子类的实例。
eg:
public class TestRunnable implements Runnable {
public static void main(String[] args) {
new Thread(new TestRunnable(), "runThread").start();
}
@Override
public void run() {
Thread runThread = Thread.currentThread();
StackTraceElement[] elements = runThread.getStackTrace();
for (StackTraceElement element : elements) {
String threadName = runThread.getName();
String methodName = element.getMethodName();
int lineNum = element.getLineNumber();
String FileNmae = element.getFileName();
String clsName = element.getClassName();
System.out.print("【Thread name : " + threadName);
System.out.print("】【" + "Method name : " + methodName);
System.out.print("】【" + "Line number : " + lineNum);
System.out.print("】【" + "File name : " + FileNmae);
System.out.print("】【" + "Class name : " + clsName + "】");

}
}
}
备注:使用Runnable接口实现的类中,应该使用Thread的静态方法currentThread()来获取到当前的线程对象,并通过该对象来调用相应的方法。

3、实现Callable接口并实现call方法
使用Callable接口来创建线程就不得不提它的搭档Future接口了,Future提供了以下几个接口来满足使用:
a、boolean cancel(boolean arg0) :用于尝试取消Future包装的Callable任务,不能保证一定可以取消。
b、V get():用于获得Futrue包装的callable任务call的返回值,该方法的调用会使调用线程阻塞,并等待获得返回值之后才正常运行。
c、V get(long timeout,TimeUnit unit):该方法相较于V get()方法多了最多让调用线程阻塞的时间限制,如果过了该指定的时间callable仍然没有返回值,那么将抛出超时异常。
d、isCancelled():该方法用于判断线程任务是否已取消。
e、isDone():该方法用于判断线程任务是否已经执行完成。
Callable并不能直接当作Thread的target来使用,应该将其使用Future接口的实现类来进行包装成Thread可用的target,上文已经提到,Java已经为我们实现了一个FutureTask类来供我们使用。
eg:
public class TestCallable implements Callable<String> {
@Override
public String call() throws Exception {
Thread t = Thread.currentThread();
return t.getName() + "--" + t.getId();
}
public static void main(String[] args) {
FutureTask<String> fuTask = new FutureTask<>(new TestCallable());
new Thread(fuTask,"callableTest").start();;
try {
String str = fuTask.get();
System.out.println(str);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
三、关于Thread生命周期等

Thread的状态有:创建(new),就绪(runnable),运行(running),阻塞(blocked),休眠(time waiting),等待(waiting),消亡(dead)几个生命周期,由于线程的运行并非并行的,所以在线程的运行过程中,它将在运行状态跟阻塞状态之间多次切换。值得注意的是,我们在调用Thread方法的Start时,线程只是进入了就绪状态,并没有运行,JVM会为其创建方法调用栈和程序计时器。千万要注意的是我们不应该去调用run方法,否则该方法中的执行体将直接运行在当前进程,而非子线程当中。


参考文献:

[1] 《Java疯狂讲义第三版》 李刚.电子工业出版社.2014年7月

[2] http://www.cnblogs.com/dolphin0520/p/3920357.html

[3] Java API 文档

0 0
原创粉丝点击