java常用线程通信方法_Java线程间通信

涉及到多个线程协同完成某个任务的时候,就用到了线程间通信的相关知识点。

这其中涉及到的知识点有:

(1)thread.join()

(2)object.wait()

(3)object.notify()

(4)CountdownLatch()

(5)CyclicBarrier

(6)FutureTask

(7)Callable

一、如何让两个线程依次执行?

假设有两个线程,线程A和线程B,依次让它们打印1到3:

private static void demo1()

{

Thread A=new Thread(() -> printNumber("A"));

Thread B=new Thread(() -> printNumber("B"));

A.start();

B.start();

}

private static void printNumber(String threadName)

{

int i=0;

while(i++<3)

{

try{

Thread.sleep(100);

}catch (InterruptedException e)

{

e.printStackTrace();

}

System.out.println(threadName+" print:"+i);

}

}

打印输出结果:

B print:1

A print:1

B print:2

A print:2

A print:3

B print:3

可以看出A和B是同时打印的。如果想让A和B依次执行的话,则需要使用thread.join()方法。

private static void demo2()

{

Thread A=new Thread(() -> printNumber("A"));

Thread B=new Thread(() -> {

try{

A.join();

}catch (InterruptedException e)

{

e.printStackTrace();

}

printNumber("B");

});

A.start();

B.start();

}

现在的打印结果:

A print:1

A print:2

A print:3

B print:1

B print:2

B print:3

可以看到现在是顺序执行。

二、如何让线程按照指定的顺序执行?

还是上面那个例子,如果想让A线程打印完A 1之后,接着执行完B线程,接着再执行A线程,thread.join()方法显然不能办到。这时候需要使用object的wait()和notify()方法。

private static void demo3()

{

Object lock=new Object();

Thread A=new Thread(() -> {

synchronized (lock)

{

System.out.println("A 1");

try{

lock.wait();

}catch (InterruptedException e)

{

e.printStackTrace();

}

System.out.println("A 2");

System.out.println("A 3");

}

});

Thread B=new Thread(() -> {

synchronized (lock)

{

System.out.println("B 1");

System.out.println("B 2");

System.out.println("B 3");

lock.notify();

}

});

A.start();

B.start();

}

打印结果:

A 1

B 1

B 2

B 3

A 2

A 3

lock为新建的线程A和线程B共享的对象锁。线程A获取锁,打印A 1,再调用lock.wait(),交出锁的控制权。线程B获取锁,执行完毕后,再调用lock.notify(),唤醒在等待的线程A,线程A继续执行完毕。

为了显示更清楚,更方便理解,现加入log:

private static void demo3Info() {

Object lock = new Object();

Thread A = new Thread(() -> {

System.out.println("INFO: A 等待锁 ");

synchronized (lock) {

System.out.println("INFO: A 得到了锁 lock");

System.out.println("A 1");

try {

System.out.println("INFO: A 准备进入等待状态,放弃锁 lock 的控制权 ");

lock.wait();

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println("INFO: 有人唤醒了 A, A 重新获得锁 lock");

System.out.println("A 2");

System.out.println("A 3");

}

});

Thread B = new Thread(() -> {

System.out.println("INFO: B 等待锁 ");

synchronized (lock) {

System.out.println("INFO: B 得到了锁 lock");

System.out.println("B 1");

System.out.println("B 2");

System.out.println("B 3");

System.out.println("INFO: B 打印完毕,调用 notify 方法 ");

lock.notify();

}

});

A.start();

B.start();

}

打印结果:

INFO: A 等待锁

INFO: A 得到了锁 lock

A 1

INFO: A 准备进入等待状态,放弃锁 lock 的控制权

INFO: B 等待锁

INFO: B 得到了锁 lock

B 1

B 2

B 3

INFO: B 打印完毕,调用 notify 方法

INFO: 有人唤醒了 A, A 重新获得锁 lock

A 2

A 3

三、四个线程A、B、C、D,A、B、C线程执行完毕之后,再去执行D线程。

thread.join()的作用是让一个线程执行完之后再执行另外一个线程,明显不符合当前的这个需求。在这里,我们希望A、B、C线程可以同时执行,执行完毕之后,再执行线程D。针对这种情况,我们可以使用CountdownLatch来实现这种通信要求。

创建一个计数器,设置初始值,CountdownLatch countDownLatch = new CountDownLatch(2);

在 等待线程 里调用 countDownLatch.await() 方法,进入等待状态,直到计数值变成 0;

在 其他线程 里,调用 countDownLatch.countDown() 方法,该方法会将计数值减小 1;

当 其他线程 的 countDown() 方法把计数值变成 0 时,等待线程 里的 countDownLatch.await() 立即退出,继续执行下面的代码。

private static void runDAfterABC() {

int worker = 3;

CountDownLatch countDownLatch = new CountDownLatch(worker);

new Thread(() -> {

System.out.println("D is waiting for other three threads");

try {

countDownLatch.await();

System.out.println("All done, D starts working");

} catch (InterruptedException e) {

e.printStackTrace();

}

}).start();

for (char threadName='A'; threadName <= 'C'; threadName++) {

final String tN = String.valueOf(threadName);

new Thread(() -> {

System.out.println(tN + " is working");

try {

Thread.sleep(100);

} catch (Exception e) {

e.printStackTrace();

}

System.out.println(tN + " finished");

countDownLatch.countDown();

}).start();

}

}

打印结果:

D is waiting for other three threads

B is working

C is working

A is working

B finished

A finished

C finished

All done, D starts working

CountDownLatch 适用于一个线程去等待多个线程的情况。线程D中调用await方法进入等待,线程A、B、C开始执行,每执行完一次,计时器减去1,当计时器为0时,触发await运行结束,继续执行线程D直到结束。

四、A、B、C三线程准备好之后,同时运行

为了实现线程之间的互相通信,我们可以利用 CyclicBarrier 数据结构,它的基本用法是:

先创建一个公共 CyclicBarrier 对象,设置 同时等待 的线程数,CyclicBarrier cyclicBarrier = new CyclicBarrier(3);

这些线程同时开始自己做准备,自身准备完毕后,需要等待别人准备完毕,这时调用 cyclicBarrier.await(); 即可开始等待别人;

当指定的 同时等待 的线程数都调用了 cyclicBarrier.await();时,意味着这些线程都准备完毕好,然后这些线程才 同时继续执行。

private static void runABCWhenAllReady() {

int runner = 3;

CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);

final Random random = new Random();

for (char runnerName='A'; runnerName <= 'C'; runnerName++) {

final String rN = String.valueOf(runnerName);

new Thread(() -> {

long prepareTime = random.nextInt(10000) + 100;

System.out.println(rN + " is preparing for time: " + prepareTime);

try {

Thread.sleep(prepareTime);

} catch (Exception e) {

e.printStackTrace();

}

try {

System.out.println(rN + " is prepared, waiting for others");

cyclicBarrier.await(); // 当前运动员准备完毕,等待别人准备好

} catch (InterruptedException e) {

e.printStackTrace();

} catch (BrokenBarrierException e) {

e.printStackTrace();

}

System.out.println(rN + " starts running"); // 所有运动员都准备好了,一起开始跑

}).start();

}

}

打印结果:

A is preparing for time: 5606

C is preparing for time: 8550

B is preparing for time: 5172

B is prepared, waiting for others

A is prepared, waiting for others

C is prepared, waiting for others

C starts running

A starts running

B starts running

五、子线程完成某项任务后,将得到的结果返回主线程

在这里需要使用Callable和FutureTask一起使用,但需要注意的是,FutureTask获取结果的时候回阻塞主线程。

例如,我们利用子线程将1加到100的结果返回给主线程:

private static void doTaskWithResultInWorker() {

Callable callable = () -> {

System.out.println("Task starts");

Thread.sleep(1000);

int result = 0;

for (int i=0; i<=100; i++) {

result += i;

}

System.out.println("Task finished and return result");

return result;

};

FutureTask futureTask = new FutureTask<>(callable);

new Thread(futureTask).start();

try {

System.out.println("Before futureTask.get()");

System.out.println("Result: " + futureTask.get());

System.out.println("After futureTask.get()");

} catch (InterruptedException e) {

e.printStackTrace();

} catch (ExecutionException e) {

e.printStackTrace();

}

}

打印结果:

Before futureTask.get()

Task starts

Task finished and return result

Result: 5050

After futureTask.get()

可以看到,主线程调用 futureTask.get() 方法时阻塞主线程;然后 Callable 内部开始执行,并返回运算结果;此时 futureTask.get() 得到结果,主线程恢复运行。

这里我们可以学到,通过 FutureTask 和 Callable 可以直接在主线程获得子线程的运算结果,只不过需要阻塞主线程。当然,如果不希望阻塞主线程,可以考虑利用 ExecutorService,把 FutureTask 放到线程池去管理执行。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值