在并发编程中,经常会遇到多个线程之间需要相互协作的情况,即并不是多个线程同时执行,而是按照一定的顺序循环执行的情况。
那么怎样去实现这种效果呢?这里介绍三种方案。
这里都以子线程循环10次,然后主线程循环10次,然后往复循环50次的思路来做例子。在下面的例子中flag代表一个共享变量。
public class communication01 {
public static void main(String[] args){
final besiness b=new besiness();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
b.sub(i);
}
}
}).start();
for (int i = 1; i <= 50; i++) {
b.main(i);
}
}
}
class besiness{
private static boolean flag=true;//flag为true时允许main访问,为false时允许suB访问
public synchronized void main(int i){
while(!flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 1; j <= 10; j++) {
System.out.println("main thread==" + j + ",loop of " + i);
}
flag=false;
this.notify();
}
public synchronized void sub(int i){
while (flag){
try {
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 1; j <= 10; j++) {
System.out.println("sub thread==" + j + ",loop of " + i);
}
flag=true;
this.notify();
}
}
这种方案是通过对两个线程中分别要执行的方法加锁synchronized,保证每次执行main时不被sub打断,执行sub循环时,不被main打断。
这里采用了对象object的notify和wait来实现线程之间的通信。当main方法执行完成后,让执行main方法的线程等待,
等待sub方法执行完成后,通知(notify)main线程然后继续执行。这种方式有一个缺点,由于notify和wait使用的是Object的方法,
所以不能单独的让某个特定的线程收到通知或者让他等待,而在存在多个线程同时等待时,只能通过notifyAll来通知所有的线程。不够灵活。
public static void main(String[] args){
final besiness b=new besiness();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
b.sub2(i);
}
}
}).start();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
b.sub3(i);
}
}
}).start();
for (int i = 1; i <= 50; i++) {
b.main(i);
}
}
static class besiness{
private int flag=1;//flag为true时允许main访问,为false时允许suB访问
//condition1来控制main和sub2之间的循环通信
Condition condition1=lock.newCondition() ;
//condition2来控制sub2和sub3之间的循环通信
Condition condition2=lock.newCondition() ;
//condition1来控制main和sub3之间的循环通信
Condition condition3=lock.newCondition() ;
public void main(int i){
lock.lock();
try{
while(flag!=1){
try {
condition1.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 1; j <= 20; j++) {
System.out.println("main thread==" + j + ",loop of " + i);
}
flag=2;
condition2.signal();
}finally {
lock.unlock();
}
}
public void sub2(int i){
lock.lock();
try{
while ( flag !=2){
try {
condition2.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 1; j <= 20; j++) {
System.out.println("sub2 thread==" + j + ",loop of " + i);
}
flag=3;
condition3.signal();
}finally {
lock.unlock();
}
}
public void sub3(int i){
lock.lock();
try{
while ( flag !=3){
try {
condition3.await();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for (int j = 1; j <= 20; j++) {
System.out.println("sub3 thread==" + j + ",loop of " + i);
}
flag=1;
condition1.signal();
}finally {
lock.unlock();
}
}
}
这种方式是利用了Java5中提供的lock和condition,利用共享变量flag来实现线程之间的相互通信。同时在这个小例子中,相比上一个例子中增加了一个线程的循环。这是为了体现使用condition的优点。
使用condition可以非常灵活的去控制线程与线程之间的通信。因为在一个类中可以创建多个condition的实例,
我们可以通过condition不同的实例的signal和await方法来标识不同的两个线程之间相互通信的标识,而不是统一使用object的notify和wait方法了。
同时利用lock方法可以利用锁的重入机制实现更加灵活的锁的应用。可以在需要的时候加锁或解锁。
这样我们就可以实现多个线程之间的协调通信了。
public static void main(String[] args){
final besiness b=new besiness();
new Thread(new Runnable() {
@Override
public void run() {
for(int i=1;i<=50;i++){
b.sub(i);
}
}
}).start();
for (int i = 1; i <= 50; i++) {
b.main(i);
}
}
static class besiness{
private int flag=1;
final Semaphore sp=new Semaphore(1);//声明一个信号,共享一个资源,每次只能允许一个线程执行
public void main(int i){
try {
sp.acquire();
while(flag!=1){
sp.release();
}
for (int j = 1; j <= 10; j++) {
System.out.println("main thread==" + j + ",loop of " + i);
}
flag=2;
} catch (InterruptedException e) {
e.printStackTrace();
}finally {
sp.release();
}
}
public void sub(int i){
try{
try {
sp.acquire();
} catch (InterruptedException e) {
e.printStackTrace();
}
while (flag !=2){
sp.release();
}
for (int j = 1; j <= 10; j++) {
System.out.println("sub thread==" + j + ",loop of " + i);
}
flag=1;
sp.release();
}finally {
sp.release();
}
}
}
这里semaphere代表一个信号量,它可以指示共享资源的个数,也就是同时访问资源的线程个数。这里主要通过semaphere的acquire和release实现锁的功能从而实现线程之间的通信。
利用semaphere不仅可以实现多个线程协调循环通信,在必要时还可以控制同一时间访问资源的个数。更加的灵活和方便。
package com.jihite;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public class CountDownLatchTest {
private static final int RUNNER_COUNT = 10;
public static void main(String[] args) throws InterruptedException {
final CountDownLatch begin = new CountDownLatch(1);
final CountDownLatch end = new CountDownLatch(RUNNER_COUNT);
final ExecutorService exec = Executors.newFixedThreadPool(10);
for (int i = 0; i < RUNNER_COUNT; i++) {
final int NO = i + 1;
Runnable run = new Runnable() {
@Override
public void run() {
try {
begin.await();
Thread.sleep((long)(Math.random() * 10000));
System.out.println("No." + NO + " arrived");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
end.countDown();
}
}
};
exec.submit(run);
}
System.out.println("Game Start ...");
begin.countDown();
end.await();
// end.await(30, TimeUnit.SECONDS);
System.out.println("Game Over.");
exec.shutdown();
}
}
以上是实习多个线程之间相互协调通信的几种方案。