java接口开发 多线程_Java并发之初识多线程开发

在Java开发中,多线程并发是一个永恒不变的话题与热点。这里我们开始讨论如何在开发中使用多线程实现并发

Thread类

在Java中实现多线程最简单的一个方式就是继承Thread类、重写run方法,如下所示

/*** 售票窗口,继承Thread类*/

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--;

// 可直接使用this来获取当前线程 System.out.println(this.getName() + ":余票数量: " + num);

}

}

}

直接构造TicketWindow1线程实例,然后通过start方法来启动该线程即可

public static void test1() {

new TicketWindow1("#1售票窗口").start();

new TicketWindow1("#2售票窗口").start();

}

从测试结果,我们可以看到两个售票窗口的线程被正确的启动、运行。由于两个售票线程是分别构造的,故也可以看出实际上两个线程之间是相互独立的,分别售票,即两个线程的num变量是相互独立的

对于这种通过继承Thread类实现多线程的方式,好处是我们可以直接在子类中通过this来获取当前线程,而无需通过Thread.currentThread()方法。但就目前来看缺点同样明显,由于Java不支持多继承,仅仅为了支持多线程就使用了一个继承资格显然有些浪费

Runnable接口

那如果即不想浪费唯一的继承名额,又想实现多线程,那该怎么办呢?答案就是Runnable接口。通过实现Runnable接口的run方法同样可以达到并发的目的

/*** 售票窗口, 实现Runnable接口*/

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方法来启动该线程即可

public static void test2() {

new Thread(new TicketWindow2(), "#1售票窗口" ).start();

new Thread(new TicketWindow2(), "#2售票窗口" ).start();

}

从测试结果,我们可以看到两个售票窗口的线程被正确的启动、运行。同样地,这里两个售票的Runnable实例是分别构造的,故也可以看出实际上这里两个线程之间是同样相互独立的,分别售票,即两个线程的num变量是相互独立的

当然利用Runnable接口实现多线程不仅可以避免继承名额的浪费,还可以像下面的示例一样,利用同一个Runnable任务实例来分别创建多个线程,即多个线程共同处理同一个资源

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方法,其可返回任务结果

public interface Callable {

V call() throws Exception;

}

下面即是一个实现Callable接口的实例

/*** 售票窗口,实现Callable接口*/

public class TicketWindow3 implements Callable {

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方法执行结果进行了封装。测试代码如下所示

public static void test4() {

TicketWindow3 ticketWindow3 = new TicketWindow3();

// 创建异步任务 FutureTask futureTask1 = new FutureTask<>(ticketWindow3);

// 启动线程 new Thread( futureTask1, "#1售票窗口" ).start();

FutureTask 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并发编程之美 翟陆续、薛宾田著

你可能感兴趣的:(java接口开发,多线程)