java提高班 -- java多线程 (2)同步

本文版权归作者所有,转载请标明出处。未与作者联系不得用于任何商业用途

2011年5月18日

关新全

山东东营 石油大学

2.2 同步

       线程同步是用来调节线程之间的执行进度,在很多情况下,需要在某一点处,所有线程等待一次汇集,统计临时结果。或者主线程等待所有子线程执行结束,统计最终结果等。(这里的同步概念与经典的同步略有不同,这里仅强调协调线程的执行进度,与锁相关的内容不放入此处同步概念)。

       Java提供了阻塞队列、CountDownLatch和CyclicBarrier三种方式的同步策略。阻塞队列使用了生产者消费者模式完成同步,CountDownLatch和CyclicBarrier是java专门设计的同步器。

2.2.1 阻塞队列

       BlockingQueue比较适用于生产者消费者模式,生产者线程向队列中添加元素,消费者从队列中提取元素。在生产者向队列中添加元素时(put)如果队列已经满了,那么生产者线程需要阻塞式等待,直到有消费者线程从队列中提取了元素后,队列状态变为不满,生产者线程才可能被唤醒。同理,当消费者线程从队列中取元素(take)时,如果队列为空,那么消费者需要阻塞等待,直至有生产者线程向队列中添加元素,此时队列状态为非空时,消费者线程才可能被唤醒。

       阻塞队列就是用来调节多个线程之间的执行进度的,使用阻塞队列可以完成线程的同步。

Demo2-6给出了使用阻塞队列完成线程同步的例子。主线程开启5个子线程,然后等待所有子线程执行结束(通过消耗阻塞队列中的元素)后,主线程输出自身的线程名,每个子进程都输出自己的线程名,同时向阻塞队列中生产一个元素。如果主线程调用take方法时,队列中没有元素,那么主线程会阻塞等待,直到其中的一个子线程向队列中添加元素为止。注意Demo2-6与Demo2-1的区别在于Demo2-6每次都是最后输出主线程。

阻塞队列还有很多复杂的方法,可以查看相应的官方文档,做更多的了解,而且Demo2-6只使用了阻塞队列中的一个实现(ArrayBlockingQueue),阻塞队列还有LinkedBlockingQueue、PriorityBlockingQueue等实现,可以从官方文档获取相关信息。

Demo2-6 阻塞队列完成线程同步

package com.upc.upcgrid.guan.advancedJava.chapter02;

 

import java.util.concurrent.ArrayBlockingQueue;

import java.util.concurrent.BlockingQueue;

 

 

 

 

public class BlockingQueueSync {

   

    public static final int MAX_THREAD_SIZE = 5;

    public static void main(String[] args) throws InterruptedException {

       final BlockingQueue<Integer> queue = new ArrayBlockingQueue<Integer>(MAX_THREAD_SIZE);

       for(int i = 0 ; i < MAX_THREAD_SIZE ; i++)

       {

           new Thread(){

              public void run() {

                  System.out.println(Thread.currentThread().getName());

                  try {

                     queue.put(1);

                  } catch (InterruptedException e) {}

              };

           }.start();

       }

      

      

       for(int i = 0 ; i < MAX_THREAD_SIZE ; i++)

           queue.take();

System.out.println(Thread.currentThread().getName());

    }

}

Demo2-6 执行结果:

Thread-0

Thread-2

Thread-1

Thread-3

Thread-4

main

2.2.2 CountDownLatch

       倒计时门栓,可以用来处理主线程等待子线程这种类型的同步。CountDownLatch中有一个计数器,创建CountDownLatch实例时需要指定计数器的值,主线程调用await方法等待计数器的值为0,每次调用countDown函数,计数器的值减一,直至计数器的值减少为0,主线程将会被唤醒。

       Demo2-7给出了一个使用CountDownLatch完成同步的例子,这个与Demo2-6是等效的。在这个例子主线程先创建一个CountDownLatch的实例,并指定计数器的值为5,主线程调用await方法阻塞式等待latch实例计数器值减少为0。每个子线程在执行结束后都会调用countDown方法,将计数器的值减1。当所有子线程都执行结束,主线程将被唤醒。

Demo2-7 使用CountDownLatch完成同步

package com.upc.upcgrid.guan.advancedJava.chapter02;

 

import java.util.concurrent.CountDownLatch;

 

public class CountDownLatchSync {

 

    public static final int MAX_THREAD_SIZE = 5;

    public static void main(String[] args) throws InterruptedException {

       final CountDownLatch latch = new CountDownLatch(MAX_THREAD_SIZE);

       for(int i = 0 ; i < MAX_THREAD_SIZE ; i++)

       {

           new Thread(){

              public void run() {

                  System.out.println(Thread.currentThread().getName());

                  latch.countDown();

              };

           }.start();

       

    latch.await();

       System.out.println(Thread.currentThread().getName());

    }

}

2.2.3 CyclicBarrier

       CyclicBarrier与CountDownLatch相似,其内部也是维护一个计数器,不过与CountDownLatch不同的是,CyclicBarrier在子线程中调用await方法,并且调用await方法后,计数器的值减少1的同时,子线程被阻塞,直到指定数目的线程都调用了await方法后,所有的被CyclicBarrier阻塞的线程将都被唤醒。

Demo2-8给出了使用CyclicBarrier实现从0加到99的示例。在Demo2-8中,每个子线程都将自己计算的临时结果放入临时数组中,当所有子线程计算结束后,CyclicBarrier会开启一个新的线程计算汇总结果。在创建CyclicBarrier的实例时可以传入一个Runnable接口,当指定数目的线程到达await后,CyclicBarrier就会开启一个新线程执行传入的Runnable实例,当开启的Runnable实例执行结束后,每个线程又会从自己阻塞的地方继续执行未执行完的代码逻辑。在Demo2-8中,所有线程执行完计算任务后,CyclicBarrier开启一个新的线程计算统计结果,新线程结束后,主线程将结果输出,其他子线程继续执行await后面的未完成的输出线程名字的任务。

Demo2-8 计算从0-99的和,使用CyclicBarrier完成同步

package com.upc.upcgrid.guan.advancedJava.chapter02;

 

import java.util.concurrent.Callable;

import java.util.concurrent.CyclicBarrier;

import java.util.concurrent.ExecutionException;

import java.util.concurrent.FutureTask;

 

 

class OnceComputSync implements Runnable{

    private int index;//下标

    private int width;//计算加和数的个数

    private CyclicBarrier bar;//障栅

    private int[] sumArray;//临时和数组

    public OnceComputSync(int index ,int width, CyclicBarrier bar,int[] sumArray) {

       this.index = index;

       this.width = width;

       this.bar = bar;

       this.sumArray = sumArray;

    }

 

    @Override

    public void run() {

       int end = (index + 1)*width;

       int sum = 0;

       for(int i = index * width ;i < end ; i++)

           sum += i;

       sumArray[index] = sum;

       try {

           bar.await();//阻塞等待

       } catch (Exception e) {        

       }

//被唤醒后执行输出语句

       System.err.println(Thread.currentThread().getName());

      

    }

   

}

 

public class CyclicBarrierSync {

    public static final int MAX_THREAD_SIZE = 10;

   

    public static void main(String[] args) throws InterruptedException, ExecutionException {

       final int[] sumArray = new int[MAX_THREAD_SIZE];

       FutureTask<Integer> task;

      

       CyclicBarrier bar = new CyclicBarrier(MAX_THREAD_SIZE,task = new FutureTask<Integer>( new Callable<Integer>() {

           @Override

           public Integer call() throws Exception {//统计所有线程的临时和

              int sum = 0;

              for(int i = 0 ;i < MAX_THREAD_SIZE; i++)

                  sum += sumArray[i];

              return sum;

           }

       }));

       for(int i = 0 ;i < MAX_THREAD_SIZE ; i++)//开启计算子线程

       {

           new Thread(new OnceComputSync(i,10, bar,sumArray)).start();

       }

      

        System.err.println(task.get());//调用get方法会阻塞式等待计算结果

      

    }

}

Demo2-8执行结果:

4950

Thread-2

Thread-5

Thread-4

Thread-3

Thread-1

Thread-0

Thread-9

Thread-8

Thread-7

Thread-6

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值