虽然通常每个子线程只需要完成自己的任务,但有时我们可能希望多个线程一起工作来完成一个任务,这涉及到线程间的通信。
本文中涉及的方法和类是:thread.join()
,object.wait()
,object.notify()
,CountdownLatch
,CyclicBarrier
,FutureTask
,Callable
,等。
以下是本文中介绍的代码
我将使用几个示例来解释如何在Java中实现线程间通信。
- 如何使两个线程按顺序执行?
- 如何使两个线程以指定的方式有序交叉?
- 有四个线程:A,B,C和D(直到A,B和C都执行完毕才会执行D,并且A,B和C将同步执行。)。
- 三名运动员分开准备,然后他们在每个运动员准备好后同时开始跑步。
- 子线程完成任务后,它会将结果返回给主线程。
如何使两个线程按顺序执行?
假设有两个线程:线程A和线程B.两个线程都可以按顺序打印三个数字(1-3)。我们来看看代码:
<span style="color:#333333"><code class="language-java"><span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>static</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>demo1</strong></span>() {
Thread A = <span style="color:#333333"><strong>new</strong></span> Thread(<span style="color:#333333"><strong>new</strong></span> Runnable() {
<span style="color:#999999"><strong>@Override</strong></span>
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>run</strong></span>() {
printNumber(<span style="color:#dd1144">"A"</span>);
}
});
Thread B = <span style="color:#333333"><strong>new</strong></span> Thread(<span style="color:#333333"><strong>new</strong></span> Runnable() {
<span style="color:#999999"><strong>@Override</strong></span>
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>run</strong></span>() {
printNumber(<span style="color:#dd1144">"B"</span>);
}
});
A.start();
B.start();
}</code></span>
实现printNumber(String)
如下,用于依次打印1,2和3三个数字:
<span style="color:#333333"><code class="language-java"><span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>static</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>printNumber</strong></span>(String threadName) {
<span style="color:#333333"><strong>int</strong></span> i=<span style="color:#008080">0</span>;
<span style="color:#333333"><strong>while</strong></span> (i++ < <span style="color:#008080">3</span>) {
<span style="color:#333333"><strong>try</strong></span> {
Thread.sleep(<span style="color:#008080">100</span>);
} <span style="color:#333333"><strong>catch</strong></span> (InterruptedException e) {
e.printStackTrace();
}
System.<span style="color:#333333"><strong>out</strong></span>.println(threadName + <span style="color:#dd1144">"print:"</span> + i);
}
}</code></span>
我们得到的结果是:
<span style="color:#333333"><code class="language-bash"><span style="color:#445588"><strong>B</strong></span> <span style="color:#0086b3">print</span>: <span style="color:#008080">1</span>
<span style="color:#445588"><strong>A</strong></span> <span style="color:#0086b3">print</span>: <span style="color:#008080">1</span>
<span style="color:#445588"><strong>B</strong></span> <span style="color:#0086b3">print</span>: <span style="color:#008080">2</span>
<span style="color:#445588"><strong>A</strong></span> <span style="color:#0086b3">print</span>: <span style="color:#008080">2</span>
<span style="color:#445588"><strong>B</strong></span> <span style="color:#0086b3">print</span>: <span style="color:#008080">3</span>
<span style="color:#445588"><strong>A</strong></span> <span style="color:#0086b3">print</span>: <span style="color:#008080">3</span></code></span>
您可以同时看到A和B打印数字。
那么,如果我们希望B在A打印完毕后开始打印怎么办?我们可以使用该thread.join()
方法,代码如下:
<span style="color:#333333"><code class="language-java"><span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>static</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>demo2</strong></span>() {
Thread A = <span style="color:#333333"><strong>new</strong></span> Thread(<span style="color:#333333"><strong>new</strong></span> Runnable() {
<span style="color:#999999"><strong>@Override</strong></span>
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>run</strong></span>() {
printNumber(<span style="color:#dd1144">"A"</span>);
}
});
Thread B = <span style="color:#333333"><strong>new</strong></span> Thread(<span style="color:#333333"><strong>new</strong></span> Runnable() {
<span style="color:#999999"><strong>@Override</strong></span>
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>run</strong></span>() {
System.out.println(<span style="color:#dd1144">"B starts waiting for A"</span>);
<span style="color:#333333"><strong>try</strong></span> {
A.join();
} <span style="color:#333333"><strong>catch</strong></span> (InterruptedException e) {
e.printStackTrace();
}
printNumber(<span style="color:#dd1144">"B"</span>);
}
});
B.start();
A.start();
}</code></span>
现在得到的结果是:
<span style="color:#333333"><code class="language-bash"><span style="color:#445588"><strong>B</strong></span> starts waiting <span style="color:#333333"><strong>for</strong></span> <span style="color:#445588"><strong>A</strong></span>
<span style="color:#445588"><strong>A</strong></span> <span style="color:#0086b3">print</span>: <span style="color:#008080">1</span>
<span style="color:#445588"><strong>A</strong></span> <span style="color:#0086b3">print</span>: <span style="color:#008080">2</span>
<span style="color:#445588"><strong>A</strong></span> <span style="color:#0086b3">print</span>: <span style="color:#008080">3</span>
<span style="color:#445588"><strong>B</strong></span> <span style="color:#0086b3">print</span>: <span style="color:#008080">1</span>
<span style="color:#445588"><strong>B</strong></span> <span style="color:#0086b3">print</span>: <span style="color:#008080">2</span>
<span style="color:#445588"><strong>B</strong></span> <span style="color:#0086b3">print</span>: <span style="color:#008080">3</span></code></span>
所以我们可以看到该A.join()
方法将使B等到A完成打印。
如何以指定的方式有序地使两个线程交叉?
那么如果现在我们希望B在A打印1之后开始打印1,2,3,那么A继续打印2,3?显然,我们需要更细粒度的锁来控制执行顺序。
在这里,我们可以利用object.wait()
和object.notify()
方法的优势。代码如下:
<span style="color:#333333"><code class="language-java"><span style="color:#999988">/**
* A 1, B 1, B 2, B 3, A 2, A 3
*/</span>
<span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>static</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>demo3</strong></span>() {
Object <span style="color:#333333"><strong>lock</strong></span> = <span style="color:#333333"><strong>new</strong></span> Object();
Thread A = <span style="color:#333333"><strong>new</strong></span> Thread(<span style="color:#333333"><strong>new</strong></span> Runnable() {
@Override
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>run</strong></span>() {
synchronized (<span style="color:#333333"><strong>lock</strong></span>) {
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"A 1"</span>);
<span style="color:#333333"><strong>try</strong></span> {
<span style="color:#333333"><strong>lock</strong></span>.wait();
} <span style="color:#333333"><strong>catch</strong></span> (InterruptedException e) {
e.printStackTrace();
}
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"A 2"</span>);
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"A 3"</span>);
}
}
});
Thread B = <span style="color:#333333"><strong>new</strong></span> Thread(<span style="color:#333333"><strong>new</strong></span> Runnable() {
@Override
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>run</strong></span>() {
synchronized (<span style="color:#333333"><strong>lock</strong></span>) {
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"B 1"</span>);
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"B 2"</span>);
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"B 3"</span>);
<span style="color:#333333"><strong>lock</strong></span>.notify();
}
}
});
A.start();
B.start();
}</code></span>
结果如下:
<span style="color:#333333"><code class="language-bash"><span style="color:#000080">A</span> <span style="color:#008080">1</span>
A waiting…
B <span style="color:#008080">1</span>
B <span style="color:#008080">2</span>
B <span style="color:#008080">3</span>
A <span style="color:#008080">2</span>
A <span style="color:#008080">3</span></code></span>
这就是我们想要的。
怎么了?
- 首先,我们创建一个由A和B共享的对象锁:
lock = new Object();
- 当A获得锁定时,它首先打印1,然后调用
lock.wait()
方法使其进入等待状态,然后移交锁定控制;- 在A调用
lock.wait()
释放控制的方法并且B获得锁定之前,B不会被执行;- B在获得锁定后打印1,2,3,然后调用
lock.notify()
方法唤醒正在等待的A;- A唤醒后继续打印剩余的2,3。
我将日志添加到上面的代码中,以便更容易理解。
<span style="color:#333333"><code class="language-java"><span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>static</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>demo3</strong></span>() {
Object <span style="color:#333333"><strong>lock</strong></span> = <span style="color:#333333"><strong>new</strong></span> Object();
Thread A = <span style="color:#333333"><strong>new</strong></span> Thread(<span style="color:#333333"><strong>new</strong></span> Runnable() {
@Override
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>run</strong></span>() {
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"INFO: A is waiting for the lock"</span>);
synchronized (<span style="color:#333333"><strong>lock</strong></span>) {
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"INFO: A got the lock"</span>);
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"A 1"</span>);
<span style="color:#333333"><strong>try</strong></span> {
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"INFO: A is ready to enter the wait state, giving up control of the lock"</span>);
<span style="color:#333333"><strong>lock</strong></span>.wait();
} <span style="color:#333333"><strong>catch</strong></span> (InterruptedException e) {
e.printStackTrace();
}
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"INFO: B wakes up A, and A regains the lock"</span>);
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"A 2"</span>);
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"A 3"</span>);
}
}
});
Thread B = <span style="color:#333333"><strong>new</strong></span> Thread(<span style="color:#333333"><strong>new</strong></span> Runnable() {
@Override
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>run</strong></span>() {
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"INFO: B is waiting for the lock"</span>);
synchronized (<span style="color:#333333"><strong>lock</strong></span>) {
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"INFO: B got the lock"</span>);
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"B 1"</span>);
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"B 2"</span>);
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"B 3"</span>);
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"INFO: B ends printing, and calling the notify method"</span>);
<span style="color:#333333"><strong>lock</strong></span>.notify();
}
}
});
A.start();
B.start();</code></span>
结果如下:
<span style="color:#333333"><code class="language-bash">INFO: A is waiting for the <span style="color:#333333"><strong>lock</strong></span>
INFO: A got the <span style="color:#333333"><strong>lock</strong></span>
A <span style="color:#008080">1</span>
INFO: A <span style="color:#333333"><strong>is</strong></span> ready <span style="color:#333333"><strong>to</strong></span> enter the <span style="color:#333333"><strong>wait</strong></span> state, giving up control <span style="color:#333333"><strong>of</strong></span> the <span style="color:#333333"><strong>lock</strong></span>
INFO: B <span style="color:#333333"><strong>is</strong></span> waiting <span style="color:#333333"><strong>for</strong></span> the <span style="color:#333333"><strong>lock</strong></span>
INFO: B got the <span style="color:#333333"><strong>lock</strong></span>
B <span style="color:#008080">1</span>
B <span style="color:#008080">2</span>
B <span style="color:#008080">3</span>
INFO: B ends printing, <span style="color:#333333"><strong>and</strong></span> <span style="color:#333333"><strong>calling</strong></span> the notify method
INFO: B wakes up A, <span style="color:#333333"><strong>and</strong></span> A regains the <span style="color:#333333"><strong>lock</strong></span>
A <span style="color:#008080">2</span>
A <span style="color:#008080">3</span></code></span>
D,A,B和C之后执行所有已完成同步执行
thread.join()
前面介绍的方法允许一个线程在等待另一个线程完成运行后继续执行。但是如果我们将A,B和C有序地连接到D线程中,它将依次执行A,B和C,而我们希望它们三个同步运行。
我们要实现的目标是:三个线程A,B和C可以同时开始运行,每个线程在完成独立运行后都会通知D; 在A,B和C全部完成运行之前,D不会开始运行。所以我们CountdownLatch
用来实现这种类型的通信。它的基本用法是:
- 创建一个计数器,并设置一个初始值,
CountdownLatch countDownLatch = new CountDownLatch(3;
countDownLatch.await()
在等待线程中调用该方法并进入等待状态,直到计数值变为0;countDownLatch.countDown()
在其他线程中调用该方法,该方法将计数值减1;- 当
countDown()
其他线程中的方法将计数值变为0时,countDownLatch.await()
等待线程中的方法将立即退出并继续执行以下代码。
实现代码如下:
<span style="color:#333333"><code class="language-java"><span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>static</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>runDAfterABC</strong></span>() {
<span style="color:#333333"><strong>int</strong></span> worker = <span style="color:#008080">3</span>;
CountDownLatch countDownLatch = <span style="color:#333333"><strong>new</strong></span> CountDownLatch(worker);
<span style="color:#333333"><strong>new</strong></span> Thread(<span style="color:#333333"><strong>new</strong></span> Runnable() {
@Override
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>run</strong></span>() {
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"D is waiting for other three threads"</span>);
<span style="color:#333333"><strong>try</strong></span> {
countDownLatch.await();
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"All done, D starts working"</span>);
} <span style="color:#333333"><strong>catch</strong></span> (InterruptedException e) {
e.printStackTrace();
}
}
}).start();
<span style="color:#333333"><strong>for</strong></span> (<span style="color:#333333"><strong>char</strong></span> threadName=<span style="color:#dd1144">'A'</span>; threadName <= <span style="color:#dd1144">'C'</span>; threadName++) {
final String tN = String.valueOf(threadName);
<span style="color:#333333"><strong>new</strong></span> Thread(<span style="color:#333333"><strong>new</strong></span> Runnable() {
@Override
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>run</strong></span>() {
System.<span style="color:#333333"><strong>out</strong></span>.println(tN + <span style="color:#dd1144">"is working"</span>);
<span style="color:#333333"><strong>try</strong></span> {
Thread.sleep(<span style="color:#008080">100</span>);
} <span style="color:#333333"><strong>catch</strong></span> (Exception e) {
e.printStackTrace();
}
System.<span style="color:#333333"><strong>out</strong></span>.println(tN + <span style="color:#dd1144">"finished"</span>);
countDownLatch.countDown();
}
}).start();
}
}</code></span>
结果如下:
<span style="color:#333333"><code class="language-bash"><span style="color:#445588"><strong>D</strong></span> <span style="color:#333333"><strong>is</strong></span> waiting <span style="color:#333333"><strong>for</strong></span> other three threads
<span style="color:#445588"><strong>A</strong></span> <span style="color:#333333"><strong>is</strong></span> working
<span style="color:#445588"><strong>B</strong></span> <span style="color:#333333"><strong>is</strong></span> working
<span style="color:#445588"><strong>C</strong></span> <span style="color:#333333"><strong>is</strong></span> working
<span style="color:#445588"><strong>A</strong></span> finished
<span style="color:#445588"><strong>C</strong></span> finished
<span style="color:#445588"><strong>B</strong></span> finished
<span style="color:#445588"><strong>All</strong></span> done, <span style="color:#445588"><strong>D</strong></span> starts working</code></span>
实际上,CountDownLatch
它本身就是倒计时器,我们将初始计数值设置为3.当D运行时,它首先调用countDownLatch.await()
方法来检查计数器值是否为0,如果值不为0,它将保持等待状态A,B和C各自将使用该countDownLatch.countDown()
方法在单独完成运行后将倒数计数器递减1。当所有这三个完成运行时,计数器将减少到0; 然后,await()
D 的方法将被触发结束A,B和C,D将开始继续执行。
因此,CountDownLatch
适用于一个线程需要等待多个线程的情况。
3名跑步者准备跑步
三个跑步者准备分开,然后他们在每个准备好之后开始同时跑步。
这次,三个线程A,B和C中的每一个都需要单独准备,然后它们在三个线程都准备就绪后开始同时运行。我们该如何实现这一目标?
在CountDownLatch
上面可以用倒计时,但在完成计数的时候,只有一个线程的一个await()
方法会得到响应,所以多线程不能在同一时间被触发。
为了实现线程相互等待的效果,我们可以使用CyclicBarrier
数据结构,其基本用法是:
- 首先创建一个公共对象
CyclicBarrier
,并设置同时等待的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);
- 这些线程开始同时准备。准备好之后,他们需要等待其他人完成准备,所以要求
cyclicBarrier.await()
方法等待其他人; - 当需要等待的指定线程全部调用该
cyclicBarrier.await()
方法时,这意味着这些线程已准备就绪,那么这些线程将开始同时继续执行。
实现代码如下。想象一下,有三个跑步者需要同时开始跑,所以他们需要等待其他人,直到他们都准备好了。
<span style="color:#333333"><code class="language-java"><span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>static</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>runABCWhenAllReady</strong></span>() {
<span style="color:#333333"><strong>int</strong></span> runner = <span style="color:#008080">3</span>;
CyclicBarrier cyclicBarrier = <span style="color:#333333"><strong>new</strong></span> CyclicBarrier(runner);
<span style="color:#333333"><strong>final</strong></span> Random random = <span style="color:#333333"><strong>new</strong></span> Random();
<span style="color:#333333"><strong>for</strong></span> (<span style="color:#333333"><strong>char</strong></span> runnerName=<span style="color:#dd1144">'A'</span>; runnerName <= <span style="color:#dd1144">'C'</span>; runnerName++) {
<span style="color:#333333"><strong>final</strong></span> String rN = String.valueOf(runnerName);
<span style="color:#333333"><strong>new</strong></span> Thread(<span style="color:#333333"><strong>new</strong></span> Runnable() {
<span style="color:#999999"><strong>@Override</strong></span>
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>run</strong></span>() {
<span style="color:#333333"><strong>long</strong></span> prepareTime = random.nextInt(<span style="color:#008080">10000</span>) + <span style="color:#008080">100</span>;
System.out.println(rN + <span style="color:#dd1144">"is preparing for time:"</span> + prepareTime);
<span style="color:#333333"><strong>try</strong></span> {
Thread.sleep(prepareTime);
} <span style="color:#333333"><strong>catch</strong></span> (Exception e) {
e.printStackTrace();
}
<span style="color:#333333"><strong>try</strong></span> {
System.out.println(rN + <span style="color:#dd1144">"is prepared, waiting for others"</span>);
cyclicBarrier.await(); <span style="color:#999988">// The current runner is ready, waiting for others to be ready</span>
} <span style="color:#333333"><strong>catch</strong></span> (InterruptedException e) {
e.printStackTrace();
} <span style="color:#333333"><strong>catch</strong></span> (BrokenBarrierException e) {
e.printStackTrace();
}
System.out.println(rN + <span style="color:#dd1144">"starts running"</span>); <span style="color:#999988">// All the runners are ready to start running together</span>
}
}).start();
}
}</code></span>
结果如下:
<span style="color:#333333"><code class="language-bash"><span style="color:#445588"><strong>A</strong></span> <span style="color:#333333"><strong>is</strong></span> preparing <span style="color:#333333"><strong>for</strong></span> time: <span style="color:#008080">4131</span>
<span style="color:#445588"><strong>B</strong></span> <span style="color:#333333"><strong>is</strong></span> preparing <span style="color:#333333"><strong>for</strong></span> time: <span style="color:#008080">6349</span>
<span style="color:#445588"><strong>C</strong></span> <span style="color:#333333"><strong>is</strong></span> preparing <span style="color:#333333"><strong>for</strong></span> time: <span style="color:#008080">8206</span>
<span style="color:#445588"><strong>A</strong></span> <span style="color:#333333"><strong>is</strong></span> prepared, waiting <span style="color:#333333"><strong>for</strong></span> others
<span style="color:#445588"><strong>B</strong></span> <span style="color:#333333"><strong>is</strong></span> prepared, waiting <span style="color:#333333"><strong>for</strong></span> others
<span style="color:#445588"><strong>C</strong></span> <span style="color:#333333"><strong>is</strong></span> prepared, waiting <span style="color:#333333"><strong>for</strong></span> others
<span style="color:#445588"><strong>C</strong></span> starts running
<span style="color:#445588"><strong>A</strong></span> starts running
<span style="color:#445588"><strong>B</strong></span> starts running</code></span>
子线程将结果返回到主线程
在实际开发中,我们经常需要创建子线程来执行一些耗时的任务,然后将执行结果传递回主线程。那么如何在Java中实现呢?
所以通常,在创建线程时,我们会将Runnable对象传递给Thread进行执行。Runnable的定义如下:
<span style="color:#333333"><code class="language-java"><span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>interface</strong></span> <span style="color:#445588"><strong>Runnable</strong></span> {
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>abstract</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>run</strong></span>();
}</code></span>
您可以看到该run()
方法在执行后不会返回任何结果。如果你想返回结果怎么办?在这里你可以使用另一个类似的接口类Callable
:
<span style="color:#333333"><code class="language-java"><span style="color:#999999"><strong>@FunctionalInterface</strong></span>
<span style="color:#333333"><strong>public</strong></span> <span style="color:#333333"><strong>interface</strong></span> <span style="color:#445588"><strong>Callable</strong></span><<span style="color:#445588"><strong>V</strong></span>> {
<span style="color:#999988">/**
* Computes a result, or throws an exception if unable to do so.
*
* <span style="color:#dd1144">@return</span> computed result
* <span style="color:#dd1144">@throws</span> Exception if unable to compute a result
*/</span>
V <span style="color:#990000"><strong>call</strong></span>() <span style="color:#333333"><strong>throws</strong></span> Exception;
}</code></span>
可以看出,最大的区别Callable
在于它返回了泛型。
那么接下来的问题是,如何将子线程的结果传回去?Java有一个FutureTask
可以使用的类Callable
,但请注意,get
用于获取结果的方法将阻塞主线程。
例如,我们希望子线程计算从1到100的总和,并将结果返回给主线程。
<span style="color:#333333"><code class="language-java"><span style="color:#333333"><strong>private</strong></span> <span style="color:#333333"><strong>static</strong></span> <span style="color:#333333"><strong>void</strong></span> <span style="color:#990000"><strong>doTaskWithResultInWorker</strong></span>() {
Callable<Integer> callable = <span style="color:#333333"><strong>new</strong></span> Callable<Integer>() {
@Override
<span style="color:#333333"><strong>public</strong></span> Integer <span style="color:#990000"><strong>call</strong></span>() throws Exception {
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"Task starts"</span>);
Thread.sleep(<span style="color:#008080">1000</span>);
<span style="color:#333333"><strong>int</strong></span> result = <span style="color:#008080">0</span>;
<span style="color:#333333"><strong>for</strong></span> (<span style="color:#333333"><strong>int</strong></span> i=<span style="color:#008080">0</span>; i<=<span style="color:#008080">100</span>; i++) {
result += i;
}
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"Task finished and return result"</span>);
<span style="color:#333333"><strong>return</strong></span> result;
}
};
FutureTask<Integer> futureTask = <span style="color:#333333"><strong>new</strong></span> FutureTask<>(callable);
<span style="color:#333333"><strong>new</strong></span> Thread(futureTask).start();
<span style="color:#333333"><strong>try</strong></span> {
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"Before futureTask.get()"</span>);
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"Result:"</span> + futureTask.<span style="color:#333333"><strong>get</strong></span>());
System.<span style="color:#333333"><strong>out</strong></span>.println(<span style="color:#dd1144">"After futureTask.get()"</span>);
} <span style="color:#333333"><strong>catch</strong></span> (InterruptedException e) {
e.printStackTrace();
} <span style="color:#333333"><strong>catch</strong></span> (ExecutionException e) {
e.printStackTrace();
}
}</code></span>
结果如下:
<span style="color:#333333"><code class="language-bash"><span style="color:#445588"><strong>Before</strong></span> futureTask.<span style="color:#333333"><strong>get</strong></span>()
<span style="color:#445588"><strong>Task</strong></span> starts
<span style="color:#445588"><strong>Task</strong></span> finished and <span style="color:#333333"><strong>return</strong></span> result
<span style="color:#445588"><strong>Result</strong></span>: <span style="color:#008080">5050</span>
<span style="color:#445588"><strong>After</strong></span> futureTask.<span style="color:#333333"><strong>get</strong></span>()</code></span>
可以看出,当主线程调用该futureTask.get()
方法时,它会阻塞主线程; 然后Callable
开始在内部执行并返回操作的结果; 然后futureTask.get()
获取结果,主线程恢复运行。
在这里我们可以了解到FutureTask
并且Callable
可以直接在主线程中获取子线程的结果,但是它们会阻塞主线程。当然,如果您不想阻止主线程,可以考虑使用ExecutorService
放入FutureTask
线程池来管理执行。
摘要
多线程是现代语言的常见功能,线程间通信,线程同步和线程安全是非常重要的主题。
原文地址:https://www.tutorialdocs.com/article/java-inter-thread-communication.html