在Java中一个很重要的基础就是线程,而每次提到线程大家必想到的是进程,所以在总结Java多线程之前我们先对进程和线程进行一下区分:
进程:一个计算机程序的运行实例,每个进程有独立的代码和数据空间(进程上下文),而一个进程中可以有多个线程;
线程:是进程中运行的一个实体,同一类线程共享代码和数据空间,每个下城有独立的运行栈;
但是它们都有五个运行的阶段:创建-->就绪-->运行-->阻塞-->终止;
在Java中多线程的启动主要有三种方式:继承Thread类重写该类的run()方法,实现Runnable接口;使用Callable和Future接口创建线程;下面就一一来看,这三种不同的启动方式的代码实现:
➷ 扩展Thread类重写该类的run()方法
/**
* 测试扩展Thread类实现的多线程程序
*
*/
publicclass TestThread extends Thread{
public TestThread(String name) {
super(name);
}
publicvoid run() {
for(int i = 0;i<5;i++){
for(long k= 0; k <100000000;k++);
System.out.println(this.getName()+" :"+i);
}
}
publicstaticvoid main(String[] args) {
Thread t1 = new TestThread("阿三");
Thread t2 = new TestThread("李四");
t1.start();
t2.start();
}
}
执行结果如下:
阿三 :0
李四 :0
阿三 :1
李四 :1
阿三 :2
李四 :2
阿三 :3
阿三 :4
李四 :3
李四 :4
Process finished with exit code 0
➷ 实现Runnable接口
<span style="font-size:18px;">/**
* 实现Runnable接口的类
*
*/
publicclass DoSomethingimplements Runnable {
private String name;
public DoSomething(String name) {
this.name = name;
}
publicvoid run() {
for (int i = 0; i < 5; i++) {
for (long k = 0; k < 100000000; k++) ;
System.out.println(name + ": " + i);
}
}
}</span>
<span style="font-size:18px;">/**
* 测试Runnable类实现的多线程程序
*
*/
publicclass TestRunnable {
publicstaticvoid main(String[] args) {
DoSomething ds1 = new DoSomething("阿三");
DoSomething ds2 = new DoSomething("李四");
Thread t1 = new Thread(ds1);
Thread t2 = new Thread(ds2);
t1.start();
t2.start();
}
}</span>
执行的结果为:
李四: 0
阿三: 0
李四: 1
阿三: 1
李四: 2
李四: 3
阿三: 2
李四: 4
阿三: 3
阿三: 4
Process finished with exit code 0
➷ 使用Callable和Future接口创建线程
具体是创建Callable接口的实现类,并实现call方法;使用Future Task类来包装Callable实现类的对象,且以此FutureTask 对象作为Thread对象的target来创建线程。
<span style="font-size:18px;">public class ThreadTest {
public static void main(String[] args) {
Callable<Integer> myCallable = new MyCallable(); // 创建MyCallable对象
FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //使用FutureTask来包装MyCallable对象
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
if (i == 30) {
Thread thread = new Thread(ft); //FutureTask对象作为Thread对象的target创建新的线程
thread.start(); //线程进入到就绪状态
}
}
System.out.println("主线程for循环执行完毕..");
try {
int sum = ft.get(); //取得新创建的新线程中的call()方法返回的结果
System.out.println("sum = " + sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
class MyCallable implements Callable<Integer> {
private int i = 0;
// 与run()方法不同的是,call()方法具有返回值
@Override
public Integer call() {
int sum = 0;
for (; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + " " + i);
sum += i;
}
return sum;
}
}</span>
这时我们可以发现,在实现Callable接口的不再是run()方法了,而是call方法,这个call方法作为线程的执行体,同时还具有返回值!在创建新的线程时,是通过FutureTask来包装MyCallable对象,同时作为了Thread对象的target,下面是Future Task类的定义:
<span style="font-size:18px;"> public class FutureTask<V> implements RunnableFuture<V> {
//....
}
public interface RunnableFuture<V> extends Runnable, Future<V> {
void run();
}</span>
从上面的代码我们可以发现,Future Task 类实际上是同时实现了Runnable和Future接口,由此才使得其具有Future和Runnable双重特性。
上面主要讲解了三种常见的线程创建方式,但是相比较而言,Runnable接口实现要比继承Thread类具有很大的优势,如:
1.Runnable适合多个相同的程序代码的线程去处理同一个资源
2.可以避免Java中的单继承的限制
3.增加程序的健壮性,代码可以被多个线程共享,代码和数据独立
至于线程的启动,都是调用start()方法,需要特别注意的:我们不能对同一线程对象两次调用start()方法;
以上就是关于多线程的基本介绍和三种创建方式,希望能帮助大家更好的去理解Java中的多线程,更高效地使用线程开发程序;