怎样实现多线程?
实现多线程的方式有3种:
- 继承Thread类:Thread类中有一个run方法,覆写run方法,运行线程要调用Thread类的start方法。实际上Thread类中的run方法也是覆写的;
- 实现Runnable接口:继承Thread类有单继承的缺陷,Thread类本身也实现了Runnable接口,所以实现Runnable接口让代码更加灵活,并且实现Runnable接口可以更好的实现程序共享的概念;
- 实现Callable接口,覆写call():V方法;
在JDK1.5新加了开发包:java.uti.concurrent;这个包主要是用于高并发编程使用,Callable接口就是这里的。在Runnable里的run方法没有返回值,某些线程在结束时可能会带回一些返回结果,这个时候就可以用Callable接口里的call方法,call方法的返回值需要Future接口里的get():V方法来接收,现在我们来看看这些方法的内部结构,这里涉及到了Future接口,FutureTask类,Runnable接口,Thread类,RunnableFuture接口,Callable接口,以及一个自己定义的类Mythread;
看一下源码:
FutureTask类实现了RunnableFuture接口
RunnableFuture接口则继承了Runnable接口和Future接口,得到run方法和get方法
Thread类实现了Runnable接口,也有run方法,下面来看一下使用Callable接口实现多线程的例子:
package TestThread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class TestCallable implements Callable<String> {
@Override
public String call() throws Exception {
int tikle = 20;
for (int i = 0; i < 20; i++) {
System.out.println("当前线程:"+Thread.currentThread().getName()+"剩余"+tikle--+"票");
}
return "票卖完了";
}
}
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable tc1 = new TestCallable();
FutureTask ft1 = new FutureTask(tc1);
Thread thread = new Thread(ft1);
Thread thread1 = new Thread(ft1);
Thread thread2 = new Thread(ft1);
thread.start();
thread1.start();
thread2.start();
System.out.println(ft1.get());
}
}
看一下运行结果:
程序中已经说明了Callable接口是如何实现多线程的,实例化一个Callable对象,传给FurtherTask构造,得到一个任务,然后用任务调用三个线程,这个过程是使用Callable接口的内部结构决定的,不懂的再去看看结构图。
但是从结果上来看,只有一个线程在工作,细心的人可以发现,只创建了一个FutureTask,这时候你就会想这会不会是因为只有一个FutureTask所以才只调用了一个线程呢,实际上FutureTask可以保证多线程场景下,任务只会被一个线程执行一次,其他线程不再执行此任务,那么任务是什么呢,MyThread的对象就是一个任务,一个任务在FutureTask的分配下只能有一个线程执行一次,呢么怎么调用多个线程来执行任务呢,我们将任务分配给多个FutureTask,就相当于备份了多个任务,这样就可以调用多个线程来执行了,代码及结果如图:
package TestThread;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
class TestCallable implements Callable<String> {
@Override
public String call() throws Exception {
int tikle = 10;
for (int i = 0; i < 10; i++) {
System.out.println("当前线程:"+Thread.currentThread().getName()+"剩余"+tikle--+"票");
}
return "票卖完了";
}
}
public class CallableTest {
public static void main(String[] args) throws ExecutionException, InterruptedException {
TestCallable tc1 = new TestCallable();
FutureTask ft1 = new FutureTask(tc1);
FutureTask ft2 = new FutureTask(tc1);
FutureTask ft3 = new FutureTask(tc1);
Thread thread1 = new Thread(ft1,"A");
Thread thread2 = new Thread(ft2,"B");
Thread thread3 = new Thread(ft3,"C");
thread1.start();
System.out.println(ft1.get());
thread2.start();
System.out.println(ft2.get());
thread3.start();
System.out.println(ft3.get());
}
}
结果(此处已实验过10000张票,结果相同,此处为容易观看,减少次数):
当前线程:A剩余10票
当前线程:A剩余9票
当前线程:A剩余8票
当前线程:A剩余7票
当前线程:A剩余6票
当前线程:A剩余5票
当前线程:A剩余4票
当前线程:A剩余3票
当前线程:A剩余2票
当前线程:A剩余1票
票卖完了
当前线程:B剩余10票
当前线程:B剩余9票
当前线程:B剩余8票
当前线程:B剩余7票
当前线程:B剩余6票
当前线程:B剩余5票
当前线程:B剩余4票
当前线程:B剩余3票
当前线程:B剩余2票
当前线程:B剩余1票
票卖完了
当前线程:C剩余10票
当前线程:C剩余9票
当前线程:C剩余8票
当前线程:C剩余7票
当前线程:C剩余6票
当前线程:C剩余5票
当前线程:C剩余4票
当前线程:C剩余3票
当前线程:C剩余2票
当前线程:C剩余1票
票卖完了
Process finished with exit code 0
你看了结果会发现确实不是一个线程执行任务了,但是多个线程并没有并发执行呀,又出错了,出错了没关系,如果你知道这么一个概念:Future接口中的get()方法会阻塞当前线程直到取得Callable的返回值,现在你就应该知道为什么线程没有并发执行,我们修改一下代码试试,我们把三行get方法的代码注释掉,看看结果(只截取一部分结果):
当前线程:C剩余42票
当前线程:C剩余41票
当前线程:C剩余40票
当前线程:B剩余17票
当前线程:A剩余23票
当前线程:A剩余22票
当前线程:A剩余21票
当前线程:A剩余20票
当前线程:B剩余16票
当前线程:C剩余39票
当前线程:C剩余38票
当前线程:C剩余37票
当前线程:C剩余36票
可以看到线程的交替执行,这已经说明了线程是并发的了。