在Java开发中,多线程并发是一个永恒不变的话题与热点。这里我们开始讨论如何在开发中使用多线程实现并发
Thread类
在Java中实现多线程最简单的一个方式就是继承Thread类、重写run方法,如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22
|
public class TicketWindow1 extends Thread {
private int num;
public TicketWindow1(String name) { super(name); }
@Override public void run() { num = 10; System.out.println("Thread [" + Thread.currentThread().getName() +"] 开始售票 ..." + "余票: " + num); while (num>0) { num--; System.out.println(this.getName() + ":余票数量: " + num); } } }
|
直接构造TicketWindow1线程实例,然后通过start方法来启动该线程即可
1 2 3 4
| public static void test1() { new TicketWindow1("#1售票窗口").start(); new TicketWindow1("#2售票窗口").start(); }
|
从测试结果,我们可以看到两个售票窗口的线程被正确的启动、运行。由于两个售票线程是分别构造的,故也可以看出实际上两个线程之间是相互独立的,分别售票,即两个线程的num变量是相互独立的
对于这种通过继承Thread类实现多线程的方式,好处是我们可以直接在子类中通过this来获取当前线程,而无需通过Thread.currentThread()方法。但就目前来看缺点同样明显,由于Java不支持多继承,仅仅为了支持多线程就使用了一个继承资格显然有些浪费
Runnable接口
那如果即不想浪费唯一的继承名额,又想实现多线程,那该怎么办呢?答案就是Runnable接口。通过实现Runnable接口的run方法同样可以达到并发的目的
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
|
public class TicketWindow2 implements Runnable{ private int num;
@Override public void run() { num = 10; System.out.println("Thread [" + Thread.currentThread().getName() +"] 开始售票 ..." + "余票: " + num); while (num>0) { num--; System.out.println(Thread.currentThread().getName() + ":余票数量: " + num); } } }
|
类似地,我们将Runnable实例传入Thread实例,即可构造创建一个新的线程。然后通过start方法来启动该线程即可
1 2 3 4
| public static void test2() { new Thread(new TicketWindow2(), "#1售票窗口" ).start(); new Thread(new TicketWindow2(), "#2售票窗口" ).start(); }
|
从测试结果,我们可以看到两个售票窗口的线程被正确的启动、运行。同样地,这里两个售票的Runnable实例是分别构造的,故也可以看出实际上这里两个线程之间是同样相互独立的,分别售票,即两个线程的num变量是相互独立的
当然利用Runnable接口实现多线程不仅可以避免继承名额的浪费,还可以像下面的示例一样,利用同一个Runnable任务实例来分别创建多个线程,即多个线程共同处理同一个资源
1 2 3 4 5 6
| public static void test3() { Runnable ticketWindow2 = new TicketWindow2();
new Thread( ticketWindow2, "#1售票窗口" ).start(); new Thread( ticketWindow2, "#2售票窗口" ).start(); }
|
这时从测试结果中我们可以看出,虽然两个售票窗口的线程被正确的启动、运行,但他们执行的是同一个Runnable任务示例。因此两个售票窗口所能出售的票是共有的,即两个售票窗口线程的num变量是共享的
Callable接口
不论是通过Thread类还是通过Runnable接口的方式实现多线程,均存在有一个弊端——任务没有返回值。为此Java在1.5版本中提供一个新的接口——Callable。其和Runnable接口类似,只不过其提供的不是run方法而是call方法,其可返回任务结果
1 2 3
| public interface Callable<V> { V call() throws Exception; }
|
下面即是一个实现Callable接口的实例
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
|
public class TicketWindow3 implements Callable<String> { private int num;
@Override public String call() { num = 10; System.out.println("Thread [" + Thread.currentThread().getName() +"] 开始售票 ..." + "余票: " + num); while (num>0) { num--; System.out.println(Thread.currentThread().getName() + ":余票数量: " + num); } return Thread.currentThread().getName() + ":票已售完"; } }
|
但是由于Callable接口没有继承Runnable接口,故我们是不能像前面那样直接将一个Callable实例丢入Thread构造器中。所以Java还提供了一个FutureTask类,其不仅实现了Runnable接口可以用于包装Callable实例,还对Callable的call方法执行结果进行了封装。测试代码如下所示
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27
| public static void test4() {
TicketWindow3 ticketWindow3 = new TicketWindow3();
FutureTask<String> futureTask1 = new FutureTask<>(ticketWindow3); new Thread( futureTask1, "#1售票窗口" ).start();
FutureTask<String> futureTask2 = new FutureTask<>(ticketWindow3); new Thread( futureTask2, "#2售票窗口" ).start();
try{ while( !futureTask1.isDone() || !futureTask2.isDone() ) { } String result1 = futureTask1.get(); String result2 = futureTask2.get();
System.out.println("result1: " + result1); System.out.println("result2: " + result2); } catch (InterruptedException | ExecutionException e) { e.printStackTrace(); } }
|
测试结果如下,可以看到我们可以通过FutureTask获取到其所包装的执行结果。同样地,由于这里两个线程执行的均是同一个Callable任务实例,故两个售票窗口线程的num变量同样是共享的
参考文献
- Java并发编程之美 翟陆续、薛宾田著