在JAVA中如果一个程序开始运行,那么至少有2个线程,一个是我们熟悉的main线程,而另外一个则是JVM的垃圾回收器,实际上每启动一个程序都会建立一个JVM,即一个进程,而如果我们想在主线程之外建立一个新的线程,而不影响主线程的运行,这时我们有三种实现方式,以下是我的一些学习心得。
(一)java中线程的实现方式
在java实现一个线程的方式有三种,一种是继承Thread类,另外一种是实现Runnable接口,还有就是使用Callable和Future创建线程,其中,前两种方式的原理都是重新下run方法,一般来说实现Runnable接口比继承Thread类更加具有优势,主要原因有以下二点;
1.实现Runnable接口可以避免java单继承的限制,从而可以便于类的扩展;
2.在需要实现若干功能逻辑相同的线程时,采用实现Runnable的方式,可以有效的提高代码的重用性,避免代码的冗余,便于资源的共享。
以下是具体的代码的例子,实现Runnable接口的例子:
public class FristThread implements Runnable{
int countNum = 5 ;
public void run() {
for(int i = 0 ; i < 5 ; i++){
if(countNum > 0 ){
System.out.println(Thread.currentThread().getName()+"-----> countNum is --->" + countNum--);
}
}
}
}
主函数的测试代码为:
public static void main(String[] args){
FristThread thread1 = new FristThread();
Thread h1=new Thread(thread1,"A");
Thread h2=new Thread(thread1,"B");
Thread h3=new Thread(thread1,"C");
h1.start();
h2.start();
h3.start();
}
可以看到执行的结果为:
B-----> countNum is --->5
B-----> countNum is --->4
B-----> countNum is --->3
A-----> countNum is --->2
C-----> countNum is --->1
而采用继承Thread的方式的实现代码为:
public class FristThread extends Thread {
int countNum = 5;
@Override
public void run() {
super.run();
for (int i = 0; i < 5; i++) {
if(countNum > 0 ){
System.out.println(Thread.currentThread().getName()+"-----> countNum is --->" + countNum--);
}
}
}
}
主函数测试代码:
public static void main(String[] args){
FristThread thread1 = new FristThread();
FristThread thread2 = new FristThread();
FristThread thread3 = new FristThread();
thread1.start();
thread2.start();
thread3.start();
}
可以看到运行结果为:
Thread-1-----> countNum is --->5
Thread-1-----> countNum is --->4
Thread-1-----> countNum is --->3
Thread-1-----> countNum is --->2
Thread-1-----> countNum is --->1
Thread-0-----> countNum is --->5
Thread-0-----> countNum is --->4
Thread-0-----> countNum is --->3
Thread-0-----> countNum is --->2
Thread-0-----> countNum is --->1
Thread-2-----> countNum is --->5
Thread-2-----> countNum is --->4
Thread-2-----> countNum is --->3
Thread-2-----> countNum is --->2
Thread-2-----> countNum is --->1
从上面的运行结果我们可以看到,通过实现Runnable接口的方式实现线程,可以实现公共的资源的共享。
在这里还需要注意的是启动线程只能通过调用线程的start的方法,而调用run方法,只是相当于调用该类的一个方法。
最后的一种实现方式是使用Callable和Future创建线程
从java5开始,JDK提供了callable接口,java API提供的介绍是这样的:Callable 接口类似于 Runnable,两者都是为那些其实例可能被另一个线程执行的类设计的。但是Runnable 不会返回结果,并且无法抛出经过检查的异常,callable接口提供了一个call方法,这个方法相当于Runnable的run方法,Future接口表示异步计算的结果,它提供了检查计算是否完成的方法,以及等待计算的完成,并获取计算的结果,我们在使用它时常常使用的是,它的实现类FutureTask,FutrueTask类提供了对Future
的基本实现。仅在计算完成时才能获取结果;如果计算尚未完成,则阻塞 get 方法。一旦计算完成,就不能再重新开始或取消计算。可以用FutureTask来包装callable接口或者Runnable接口,因为FutureTask接口实现了Runnable,所以我们可以把它提供给Executor
执行,同样的道理,因为FutureTask实现了Runnable接口使用,可以在生成线程时把他当着Thread类构造函数的中的线程参数进行传入。
以下是一个简单的应用实例:
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class CallableTest implements Callable<Integer>{
public static void main(String[] args) {
/**
* 生成一个callable实现类的对象,这个实现类包含了一个call方法,这个call方法
* 相当于Runnable的run方法,是线程的具体任务。
*/
CallableTest callableTest=new CallableTest();
/**
* 使用FutureTask对象来包装callable实现对象,该FutureTask对象
* 实际上就是封装了callable类的call方法的返回结果
*/
FutureTask<Integer> futureTask=new FutureTask<Integer>(callableTest);
/**
* 使用futureTask创建线程
*/
new Thread(futureTask, "futureTask").start();
try {
System.out.println("子线程执行完成并且返回 : "+futureTask.get());
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
@Override
public Integer call() throws Exception {
int i = 0;
for( ;i < 5 ; i++ ){
System.out.println("子线程循环-----i = " + i);
}
return i;
}
}
执行结果为:
子线程循环-----i = 0
子线程循环-----i = 1
子线程循环-----i = 2
子线程循环-----i = 3
子线程循环-----i = 4
子线程执行完成并且返回 : 5
以上是三种线程的实现方式,详细的介绍可以参考JDK API进行查询。