多线程学习(6):Java 多线程间通信(五个通俗易懂例子)

2018年10月10日

目录

1、提出问题

2、测试Demo

2.1  线程依次执行

2.2 两个线程按照指定方式有序交叉运行

2.3 四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的

2.4 三个运动员各自准备,等到三个人都准备好后,再一起跑

2.5 子线程完成某件任务后,把得到的结果回传给主线程


1、提出问题

  1. 如何让两个线程依次执行?

  2. 那如何让 两个线程按照指定方式有序交叉运行呢?

  3. 四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的

  4. 三个运动员各自准备,等到三个人都准备好后,再一起跑

  5. 子线程完成某件任务后,把得到的结果回传给主线程

 

2、测试Demo

2.1  线程依次执行

package test_thread_communication;

//两个线程依次执行
public class test_thread_run_successively {

	public static void main(String[] args) {
		Thread thread1 = new Thread(new Runnable(){

			@Override
			public void run() {
				threadPrint("thread1");
			}
			
		});
		Thread thread2 = new Thread(new Runnable(){

			@Override
			public void run() {
				threadPrint("thread2");
			}
			
		});
		thread1.start();
		thread2.start();
	}

	public static void threadPrint(String threadName){
		int i=0;
		while(i++ < 3){
			try{
				Thread.sleep(100);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
			System.out.println(threadName + " print: "+i);
		}
	}
}

console:

thread1 print: 1
thread2 print: 1
thread1 print: 2
thread2 print: 2
thread2 print: 3
thread1 print: 3

 

2.2 两个线程按照指定方式有序交叉运行

Q:如果我们希望 B 在 A 全部打印 完后再开始打印呢?我们可以利用 thread.join() 方法


Thread.join() 源码,即:直到某个指定线程运行结束后,当前线程才可以继续执行,否则一直阻塞。

    /**
    * Waits for this thread to die.
    */
    public final void join() throws InterruptedException {
        join(0);
    }

demo:

package test_thread_communication;

//两个线程依次执行
public class test_thread2_run_after_thread1 {

	public static void main(String[] args) {
		final Thread thread1 = new Thread(new Runnable(){

			@Override
			public void run() {
				threadPrint("thread1");
			}
			
		});
		Thread thread2 = new Thread(new Runnable(){

			@Override
			public void run() {
			    System.out.println("thread2 开始等待 thread1");
				try{
					thread1.join();//Waits for this thread to die.
				}catch(InterruptedException e){
					e.printStackTrace();
				}
				threadPrint("thread2");
			}
			
		});		
		thread2.start();
		thread1.start();
	}
	public static void threadPrint(String threadName){
		int i=0;
		while(i++ < 3){
			try{
				Thread.sleep(500);
			}catch(InterruptedException e){
				e.printStackTrace();
			}
			System.out.println(threadName + " print: "+i);
		}
	}
}

console:

thread2 开始等待 thread1
thread1 print: 1
thread1 print: 2
thread1 print: 3
thread2 print: 1
thread2 print: 2
thread2 print: 3

结论:能看到 A.join() 方法会让 B 一直等待直到 A 运行完毕。

 

2.3 四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的

Q:thread.join(),可以让一个线程等另一个线程运行完毕后再继续执行,那我们可以在 D 线程里依次 join A B C,不过这也就使得 A B C 必须依次执行,而我们要的是这三者能同步运行。这时,我们可以使用CountDownLatch,它适用于一个线程去等待多个线程的情况。


CountDownLatch()构造器源码,只需要传入一个倒计时值即可;

    private final Sync sync; //保存倒计时值的对象Sync;

    /**
     * Constructs a {@code CountDownLatch} initialized with the given count.
     */
    public CountDownLatch(int count) {
        if (count < 0) throw new IllegalArgumentException("count < 0");
        this.sync = new Sync(count); //创建对象Sync
    }

关于上面new 的Sync对象,其本质是CountDownLatch类的一个内部类:

   /**
     * Synchronization control For CountDownLatch.
     * Uses AQS state to represent count.
     */
    private static final class Sync extends AbstractQueuedSynchronizer {
        private static final long serialVersionUID = 4982264981922014374L;

        Sync(int count) {  //构造函数!!!!
            setState(count);
        }

        int getCount() {
            return getState();
        }

        protected int tryAcquireShared(int acquires) {
            return (getState() == 0) ? 1 : -1;
        }

        protected boolean tryReleaseShared(int releases) {
            // Decrement count; signal when transition to zero
            for (;;) {
                int c = getState();
                if (c == 0)
                    return false;
                int nextc = c-1;
                if (compareAndSetState(c, nextc))
                    return nextc == 0;
            }
        }
    }

观察内部类的构造函数,setState是继承了 AbstractQueuedSynchronizer 抽象类,是它的一个protected修饰符修饰的方法,本质是保存在一个int 值,不过这是private 、 volatile修饰的:

    /**
     * The synchronization state.
     */
    private volatile int state;

    /**
     * Sets the value of synchronization state.
     */
    protected final void setState(int newState) {
        state = newState;
    }

点到为止,回到问题,下面是测试Demo:

package test_thread_communication;

import java.util.concurrent.CountDownLatch;

/**
 * CountDownLatch 就是一个倒计数器,
 * 我们把初始计数值设置为3,当 D 运行时,先调用 countDownLatch.await() 检查计数器值是否为 0,若不为 0 则保持等待状态;
 * 当A B C 各自运行完后都会利用countDownLatch.countDown(),将倒计数器减 1,
 * 当三个都运行完后,计数器被减至 0;此时立即触发 D 的 await() 运行结束,继续向下执行。
 * 
 * @result : CountDownLatch 适用于一个线程去等待多个线程的情况。
 * */

//四个线程 A B C D,其中 D 要等到 A B C 全执行完毕后才执行,而且 A B C 是同步运行的
public class test_four_threads_run_by_setting {

	public static void main(String[] args) {		
		int worker = 3; //倒计时初始值			
		final CountDownLatch countDownLatch = new CountDownLatch(worker);			
		
		Thread thread4 = new Thread(new Runnable(){
			@Override
			public void run() {
				System.out.println("thread4 is waiting for other three threads change to terminated..");
				try{
					/*Causes the current thread to wait until the latch has 
					 counted down to zero, unless the thread is interrupted. 
					 If the current count is zero then this method returns 
					  immediately.*/ 
					countDownLatch.await();
					System.out.println("All done,thread4 starts working");
				}catch (InterruptedException e) {
	                e.printStackTrace();
	            }				
			}			
		});
		thread4.start();
		for(int i =1;i<=3;i++){
			final String threadName = String.valueOf("thread"+i);
			new Thread(new Runnable(){
				@Override
				public void run() {
					System.out.println(threadName + " is working");					
					 try {
		                    Thread.sleep(100);
		                } catch (Exception e) {
		                    e.printStackTrace();
		             }
					 System.out.println(threadName + " is done.");
					/* Decrements the count of the latch, releasing all waiting threads if
				     * the count reaches zero.*/
					 countDownLatch.countDown();
				}				
			}).start();
		}
	}
}

console:

thread4 is waiting for other three threads change to terminated..
thread1 is working
thread2 is working
thread3 is working
thread1 is done.
thread2 is done.
thread3 is done.
All done,thread4 starts working

结论:

1)可以看到,1/2/3线程都是并行执行,4线程是等到前3者执行完成后再运行;

2)其实简单点来说,CountDownLatch 就是一个倒计数器,我们把初始计数值设置为3,当 D 运行时,先调用 countDownLatch.await() 检查计数器值是否为 0,若不为 0 则保持等待状态;当A B C 各自运行完后都会利用countDownLatch.countDown(),将倒计数器减 1,当三个都运行完后,计数器被减至 0;此时立即触发 D 的 await() 运行结束,继续向下执行。CountDownLatch 适用于一个线程去等待多个线程的情况。

 

2.4 三个运动员各自准备,等到三个人都准备好后,再一起跑

Q:上面的 CountDownLatch 可以用来倒计数,但当计数完毕,只有一个线程的 await() 会得到响应,无法让多个线程同时触发。为了实现线程间互相等待这种需求,我们可以利用 CyclicBarrier 数据结构。


 CyclicBarrier是多线程中一个重要的类,主要用于线程组内部之间的线程的相互等待问题。

CyclicBarrier()构造器源码: 下面是CyclicBarrier的两个构造函数:CyclicBarrier(int parties)和CyclicBarrier(int parties, Runnable barrierAction) :前者只需要声明需要拦截的线程数即可,而后者还需要定义一个等待所有线程到达屏障优先执行的Runnable对象。 

    /**
     * Creates a new <tt>CyclicBarrier</tt> that will trip when the
     * given number of parties (threads) are waiting upon it, and
     * does not perform a predefined action when the barrier is tripped.
     */
    public CyclicBarrier(int parties) {
        this(parties, null);
    }

    /**
     * Creates a new <tt>CyclicBarrier</tt> that will trip when the
     * given number of parties (threads) are waiting upon it, and which
     * will execute the given barrier action when the barrier is tripped,
     * performed by the last thread entering the barrier.
     *
     * @param parties the number of threads that must invoke {@link #await}
     *        before the barrier is tripped
     * @param barrierAction the command to execute when the barrier is
     *        tripped, or {@code null} if there is no action
     * @throws IllegalArgumentException if {@code parties} is less than 1
     */
    public CyclicBarrier(int parties, Runnable barrierAction) {
        if (parties <= 0) throw new IllegalArgumentException();
        this.parties = parties;
        this.count = parties;
        this.barrierCommand = barrierAction;
    }

  实现原理:在CyclicBarrier的内部定义了一个Lock对象,每当一个线程调用await方法时,将拦截的线程数减1,然后判断剩余拦截数是否为初始值parties,如果不是,进入Lock对象的条件队列等待。如果是,(如果非null,优先执行)执行barrierAction对象的Runnable方法,然后将锁的条件队列中的所有线程放入锁等待队列中,这些线程会依次的获取锁、释放锁。 


言归正传,回到问题:

package test_thread_communication;

import java.util.Random;
import java.util.concurrent.BrokenBarrierException;
import java.util.concurrent.CyclicBarrier;

public class test_three_threads_run_both_prepared {

	//三个运动员各自准备,等到三个人都准备好后,再一起跑
	public static void main(String[] args) {
		int runner = 3;
		final CyclicBarrier cyclicBarrier = new CyclicBarrier(runner);
		
		final Random random = new Random();
		for(int i=1;i<=runner;i++){
			final String threadName = String.valueOf("thread"+i);
			
			new Thread(new Runnable(){
				@Override
				public void run() {
					long prepareTime = random.nextInt(10000)+100;
					System.out.println(threadName + " is preparing for time: " + prepareTime);
					
					try{
						Thread.sleep(prepareTime);						
					}catch (Exception e) {
	                    e.printStackTrace();
	                }
					
					try {
	                    System.out.println(threadName + " is prepared, waiting for others");
	                    cyclicBarrier.await(); // 当前运动员准备完毕,等待别人准备好
	                } catch (InterruptedException e) {
	                    e.printStackTrace();
	                } catch (BrokenBarrierException e) {
	                    e.printStackTrace();
	                }
					System.out.println(threadName + " starts running"); // 所有运动员都准备好了,一起开始跑
				}
			}).start();
		}
	}
}

console:

thread1 is preparing for time: 1728
thread2 is preparing for time: 2508
thread3 is preparing for time: 8221
thread1 is prepared, waiting for others
thread2 is prepared, waiting for others
thread3 is prepared, waiting for others
thread3 starts running
thread1 starts running
thread2 starts running

 

2.5 子线程完成某件任务后,把得到的结果回传给主线程

Q:callable 和 runnable 接口区别是前者可以返回值并抛出异常;


Callable接口源码:

/**
 * A task that returns a result and may throw an exception.
 * Implementors define a single method with no arguments called
 * <tt>call</tt>.
 *
 * <p>The <tt>Callable</tt> interface is similar to {@link
 * java.lang.Runnable}, in that both are designed for classes whose
 * instances are potentially executed by another thread.  A
 * <tt>Runnable</tt>, however, does not return a result and cannot
 * throw a checked exception.
 *
 * <p> The {@link Executors} class contains utility methods to
 * convert from other common forms to <tt>Callable</tt> classes.
 *
 * @see Executor
 * @since 1.5
 * @author Doug Lea
 * @param <V> the result type of method <tt>call</tt>
 */
public interface Callable<V> {
    /**
     * Computes a result, or throws an exception if unable to do so.
     *
     * @return computed result
     * @throws Exception if unable to compute a result
     */
    V call() throws Exception;
}

Demo:

package test_thread_communication;

import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;

public class test_callable {


	public static void main(String[] args) {
		Callable<String> callable = new Callable<String>(){
			@Override
			public String call() throws Exception {
				System.out.println("callable task start...");
				Thread.sleep(2000);				
				StringBuilder result = new StringBuilder();
				for(int i =0;i<99;i++){
					result.append(i+",");
				}
				System.out.println("task finished and return result..");
				return result.toString();
			}
		};
		
		/*
		 * 这里我们可以学到,通过 FutureTask 和 Callable 可以直接在主线程获得子线程的运算结果,只不过需要阻塞主线程。
		 * 当然,如果不希望阻塞主线程,可以考虑利用 ExecutorService,把 FutureTask 放到线程池去管理执行。
		 * */
		FutureTask<String> 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();
	    }
	}
}

console:

Before futureTask.get()
callable task start...
task finished and return result..
Result: 0,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,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,
After futureTask.get()

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值