java5中的线程并发库

在java.util.concurrent包及其子包中

1. 原子性操作类

java提供了很多原子性的操作类供我们使用,支持在单个变量上解除锁的线程安全编程。

1.1 AtomicInteger类

可以用原子方式更新的 int 值,当多个线程共享一个int类型的数据的时候,我们就可以将该变量声明为AtomicInteger类型的。

1.1.1 addAndGet(int delta)

以原子方式将给定值与当前值相加

1.1.2 getAndIncrement

以原子方式将当前值加 1。

1.1.3 decrementAndGet

以原子方式将当前值减 1。

具体其他API可以参考JDK参考API文档

1.2 AtomicIntegerArray类

用于操作数组中的整数

参考JDK API文档

1.3 AtomicIntegerFieldUpdater类

用于操作对象中的整数

参考JDK API

2. 线程池

2.1 工具类Executors

Executors内部封装了很多创建线程的静态方法供我们创建线程

2.1.1 固定数量的线程池——newFixedThreadPool

ExecutorService pool = Executors.newFixedThreadPool(3);
for(int i = 0 ; i < 10 ; i++){
	final int task = i;
	pool.execute(new Runnable() {
		@Override
	    public void run() {
			System.out.println(Thread.currentThread().getName() + "  " + task);
		}
	});
}

2.1.2 缓存线程池 ——newCachedThreadPool

ExecutorService pool = Executors.newCachedThreadPool();

2.1.3  只有一个线程的线程池——newSingleThreadExecutor

ExecutorService pool = Executors.newSingleThreadExecutor();

注意:该方式与传统的创建线程的方式相比,有一个好处是,如果池中的线程死了,那么线程池会在创建一个线程 

2.2 关闭线程池

1. shutdown()

2. shutdownNow()

两个方法都有关闭线程池的功能,第一种方式,当池中还有正在执行的线程,则等待其执行完毕,在关闭,第二种方式,不管池中是否存在正在执行的线程,都关闭。

2.3 线程池启动定时器

2.3.1 newScheduledThreadPool

1. schedule方法,多少时间之后执行

10秒之后执行

ScheduledExecutorService sPool = Executors.newScheduledThreadPool(3);
sPool.schedule(new Runnable() {
		@Override
		public void run() {
			System.out.println("嘭");
		}
}, 10, TimeUnit.SECONDS);

2. scheduleAtFixedRate方法,以固定频率执行 

10秒后开始执行,以后每隔2秒执行

ScheduledExecutorService sPool = Executors.newScheduledThreadPool(3);
sPool.scheduleAtFixedRate(new Runnable() {
	@Override
	public void run() {
		System.out.println("嘭");
	}
}, 10, 2,TimeUnit.SECONDS);

2.3.2 newSingleThreadScheduledExecutor

创建单一的定时线程池,用法同上。

3. 带返回结果的线程

3.1 Callable与Future

Callable与Future的作用是程序启动一个线程,线程运行结束之后,可以返回线程执行完毕之后的结果。

1. Future取得的结果类型与Callable返回的结果类型必须一致,这是通过泛型来实现的。

2. Callable要采用ExecutorService的submit方法提交,返回的Future对象可以取消任务。

例如:

public static void main(String[] args) throws Exception, ExecutionException {
	// 创建一个线程池
	ExecutorService service = Executors.newSingleThreadExecutor();
	// 提交任务返回线程执行的结果
	Future<Map<String,String>> future = service.submit(new Callable<Map<String,String>>() {
		@Override
		public Map<String,String> call() throws Exception {
			Map<String,String> map = new HashMap<String,String>();
			map.put("msg", "执行成功");
			return map;
		}
	});
	// 得到结果
	Map<String, String> map = future.get();
	System.out.println(map);
}

注意:Callable支持泛型,泛型类型就是call方法的返回类型,该类型也是Future的泛型类型。

例如:用String做为返回值

public static void main(String[] args) throws Exception, ExecutionException {
	// 创建一个线程池
	ExecutorService service = Executors.newSingleThreadExecutor();
	// 提交任务返回线程执行的结果
	Future<String> future = service.submit(new Callable<String>() {
		@Override
		public String call() throws Exception {			
			return "ok";
		}
	});
	
	String rtnResult = future.get();
	System.out.println(rtnResult);
}

3.2 CompletionService

CompletionService用于提交一组Callable任务,其take方法返回已完成的一个Callable任务对应的Future对象。

CompletionService是一个接口,我们创建其对象用的是其子类ExecutorCompletionService,构造函数接收一个线程对象。

API

1. submit:执行一个任务,参数是一个任务对象,例如Callable

2. take:获取执行完毕的任务的Future对象

例如:创建一个线程任务交给任务组执行,并获取返回值

public static void main(String[] args) throws Exception, ExecutionException {
	// 创建一个固定大小的线程池
	ExecutorService pool = Executors.newFixedThreadPool(10);
	// 创建一个任务组
	CompletionService<Integer> cService = new ExecutorCompletionService<Integer>(pool);
	// 创建10个任务,并交给任务组执行
	for(int i = 0 ; i < 10 ; i++){
		final Integer seq = i;
		cService.submit(new Callable<Integer>() {
			@Override
			public Integer call() throws Exception {
				Thread.sleep(new Random().nextInt(10000));
				// 返回线程编号
				return seq;
			}
		});
	}
		
	// 获取线程执行结果
	for(int i = 0 ; i < 10 ; i++){
		// 得到已经执行完毕的任务的返回结果的Future对象
		Future<Integer> take = cService.take();
		// 获取返回结果
		Integer result = take.get();
		System.out.println(result);
	}
}

3. 线程锁

3.1 Lock

        lock比传统线程模型中的synchronized方式更加面向对象,与生活中的锁类似,锁本身也应该是一个对象。两个线程执行的代码片段要实现同步互斥的效果,它们必须用同一个Lock对象。锁是上在代表要操作的资源类的内部方法中,而不是线程代码中。

        Lock是一个接口,我们要使用,一般是创建其子类的实例。

例如:Lock lock = new ReentrantLock();

private Lock lock = new ReentrantLock();
public void lockTest01() {
	lock.lock();  // 上锁
	try {
		for(int i = 0 ; i < 100 ; i++){
			System.out.println("张三");
		}
	} finally {
		lock.unlock(); // 释放锁
	}
		
}

注意:这里用finally的原因是,放置代码出现异常,导致锁不能被释放。 

3.1.1 读写锁——ReadWriteLock

        锁又分为读写锁,多个读锁不互斥,读锁与写锁互斥,写锁与写锁互斥,这是由JVM自己控制的,你只要上好相应的锁即可。

        如果你的代码只读数据,可以很多人同时读,但是不能同时写,那就上读锁;

        如果你的代码修改数据,只能有一个人在写,且不能同时读取,那么就上写锁。总之,读的时候上读锁,写的时候上写锁。

ReadWriteLock是一个接口,我们使用其子类ReentrantReadWriteLock来创建锁对象,然后分别调用readLock用来读,writeLock用来写

例如:

package com.bjc.thread.demo4;

import java.util.Random;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class ReadWriteLockTest {
	public static void main(String[] args) {
		final Queue3 q3 = new Queue3();
		for(int i=0;i<3;i++){
			new Thread(){
				public void run(){
					while(true){
						q3.get();						
					}
				}
				
			}.start();

			new Thread(){
				public void run(){
					while(true){
						q3.put(new Random().nextInt(10000));
					}
				}			
				
			}.start();
		}
		
	}
}

class Queue3{
	private Object data = null;//共享数据,只能有一个线程能写该数据,但可以有多个线程同时读该数据。
	ReadWriteLock rwl = new ReentrantReadWriteLock();
	public void get(){
		rwl.readLock().lock();
		try {
			System.out.println(Thread.currentThread().getName() + " be ready to read data!");
			Thread.sleep((long)(Math.random()*1000));
			System.out.println(Thread.currentThread().getName() + "have read data :" + data);			
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			rwl.readLock().unlock();
		}
	}
	
	public void put(Object data){

		rwl.writeLock().lock();
		try {
			System.out.println(Thread.currentThread().getName() + " be ready to write data!");					
			Thread.sleep((long)(Math.random()*1000));
			this.data = data;		
			System.out.println(Thread.currentThread().getName() + " have write data: " + data);					
		} catch (InterruptedException e) {
			e.printStackTrace();
		}finally{
			rwl.writeLock().unlock();
		}
		
	
	}
}

自定义一个缓存类

package com.bjc.thread.demo4;

import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.locks.ReadWriteLock;
import java.util.concurrent.locks.ReentrantReadWriteLock;

public class CacheDemo {

	private Map<String,Object> cache = new HashMap<String,Object>();
	ReadWriteLock rwl = new ReentrantReadWriteLock();
	
	/**
	 * @param key
	 * @return
	 * 当有多个线程同时读的时候,当线程1发现value为null,那么线程1就会查询数据库
	 * 写数据,这时候,线程2也进来了,也要写数据,所以,我们这里设计一个写锁,当线程1
	 * 写数据的时候,其他线程等待线程1写锁释放之后,在开始并发读数据
	 */
	public Object getData(String key){
		rwl.readLock().lock();
		Object value = null;
		try {
			value = cache.get(key);
			if(null == value){
				rwl.readLock().unlock();  // 开始写的时候,释放读锁
				rwl.writeLock().lock();
				try {
					/*
					 * 这里在加一次判断,是因为当线程1和线程2线程3同时读到30行rwl.writeLock().lock();的时候
					 * 只有一个线程会拿到写锁,这时候,当该线程写完数据,释放掉写锁的时候,另外两个线程会接着拿着写锁,‘
					 * 去写数据,导致资源的无畏的浪费。所以这里再加一个判断。
					 * */
					if(null == value){
						value = "查询数据库!";
					}
				} finally{
					// 读到了数据释放写锁
					rwl.writeLock().unlock();
					// 再回复读锁
					rwl.readLock().lock();
				}
			}
		} finally {
			rwl.readLock().unlock();  // 前面已经释放了读锁了,这里再次释放
		}
		return value;
	}

}

3.2 执行条件Condition

        锁只能实现互斥,但是不能实现通信,接下来我们需要引入通信的API。

        Condition的功能类似在传统线程技术中的Object.wait和Object.notify的功能,在等待Condition时,允许发生虚假唤醒,这通常作为对基础平台语义的让步。

        对于大多数应用程序,这带来的实际影响很小,因为Condition应该总是在一个循环中被等待,并测试正被等待的状态声明。某个实现可以随意移除可能的虚假唤醒,但建议应用程序总是假定这些虚假唤醒可能发生,因此总是在一个循环中等待。

3.3.1 重要API

1. 获取Condition对象

Condition condition = lock.newCondition()

2. 等待

condition .await();

3. 唤醒

condition .signal()

condition .signalAll()

3.3.2 案例

package com.bjc.thread.demo5;

import java.util.concurrent.locks.Condition;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

public class MyThread {
	private Boolean flag = true;
	private Lock lock = new ReentrantLock();
	Condition condition = lock.newCondition();
	
	public void sub(int i){
		try {
			lock.lock();
			while(flag){
				condition.await();
			}
			for(int j=1;j<=10;j++){
				System.out.println("sub  " + j + ",from " + i);
			}
			flag = true;
			condition.signal();
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			lock.unlock();
		}
	}
	
	public void main(int i) {
		try {
			lock.lock();
			while(!flag){
				condition.await();
			}
			for(int j=1;j<=100;j++){
				System.out.println("main  " + j + ",from " + i);
			}
			flag = false;
			condition.signal();
		} catch (Exception e) {
			e.printStackTrace();
		} finally{
			lock.unlock();
		}
	}
	
}

调用

package com.bjc.thread.demo5;

public class Demo1 {

	public static void main(String[] args) {
		MyThread myThread = new MyThread();
		new Thread(){
			@Override
			public void run() {
				for(int i=1;i<=50;i++){
					myThread.sub(i);
				}
			};
		}.start();
		for(int i=1;i<=50;i++){
			myThread.main(i);
		}
	}
}

3.3.3 条件阻塞队列

        通过上面的按钮,我们发现,Condition的用法与传统的线程通信的用法与作用差不多,既然这样,我们为什么还要引入Condition了?这是因为Condition可以实现条件阻塞队列的应用即多路等待通知。 

        一个锁内部可以有多个Condition,即有多路等待和通知,可以参看Lock与Condition实现的可阻塞队列的应用案例。在传统的线程机制中,一个监视器对象上只能有一路等待和通知,要想实现多路等待和通知,必须嵌套使用多个同步监视器对象,即创建多个Condition

看JDK给出的例子

class BoundedBuffer {
   final Lock lock = new ReentrantLock();
   final Condition notFull  = lock.newCondition(); 
   final Condition notEmpty = lock.newCondition(); 

   final Object[] items = new Object[100];
   int putptr, takeptr, count;

   public void put(Object x) throws InterruptedException {
     lock.lock();
     try {
       while (count == items.length) 
         notFull.await();
       items[putptr] = x; 
       if (++putptr == items.length) putptr = 0;
       ++count;
       notEmpty.signal();
     } finally {
       lock.unlock();
     }
   }

   public Object take() throws InterruptedException {
     lock.lock();
     try {
       while (count == 0) 
         notEmpty.await();
       Object x = items[takeptr]; 
       if (++takeptr == items.length) takeptr = 0;
       --count;
       notFull.signal();
       return x;
     } finally {
       lock.unlock();
     }
   } 
 }

另一个比较经典的例子,三个线程一次交替运行

例如:

public class ThreeConditionCommunication {

	/**
	 * @param args
	 */
	public static void main(String[] args) {
		
		final Business business = new Business();
		new Thread(
				new Runnable() {
					
					@Override
					public void run() {
					
						for(int i=1;i<=50;i++){
							business.sub2(i);
						}
						
					}
				}
		).start();
		
		new Thread(
				new Runnable() {
					
					@Override
					public void run() {
					
						for(int i=1;i<=50;i++){
							business.sub3(i);
						}
						
					}
				}
		).start();		
		
		for(int i=1;i<=50;i++){
			business.main(i);
		}
		
	}

	static class Business {
			Lock lock = new ReentrantLock();
			Condition condition1 = lock.newCondition();
			Condition condition2 = lock.newCondition();
			Condition condition3 = lock.newCondition();
		  private int shouldSub = 1;
		  public  void sub2(int i){
			  lock.lock();
			  try{
				  while(shouldSub != 2){
					  try {
						condition2.await();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				  }
					for(int j=1;j<=10;j++){
						System.out.println("sub2 thread sequence of " + j + ",loop of " + i);
					}
				  shouldSub = 3;
				  condition3.signal();
			  }finally{
				  lock.unlock();
			  }
		  }

		  public  void sub3(int i){
			  lock.lock();
			  try{
				  while(shouldSub != 3){
					  try {
						condition3.await();
					} catch (Exception e) {
						// TODO Auto-generated catch block
						e.printStackTrace();
					}
				  }
					for(int j=1;j<=20;j++){
						System.out.println("sub3 thread sequence of " + j + ",loop of " + i);
					}
				  shouldSub = 1;
				  condition1.signal();
			  }finally{
				  lock.unlock();
			  }
		  }		  
		  
		  public  void main(int i){
			  lock.lock();
			  try{
				 while(shouldSub != 1){
				  		try {
							condition1.await();
						} catch (Exception e) {
							// TODO Auto-generated catch block
							e.printStackTrace();
						}
				  	}
					for(int j=1;j<=100;j++){
						System.out.println("main thread sequence of " + j + ",loop of " + i);
					}
					shouldSub = 2;
					condition2.signal();
		  }finally{
			  lock.unlock();
		  }
	  }
	
	}
}

4. Semaphere同步工具(信号量)

4.1 概述

         Semaphore也叫作信号灯技术,是一种在多线程环境下使用的设施,该设施负责协调各个线程,以保证它们能够正确、合理的使用公共资源的设施,也是操作系统中用于控制进程同步互斥的量。Semaphore是一种计数信号量,用于管理一组资源,内部是基于AQS的共享模式。它相当于给线程规定一个量从而控制允许活动的线程数。

        简单来说,Semaphere可以维护当前访问自身的线程个数,并提供了同步机制,使用Semaphere可以控制同时访问资源的线程个数,例如:实现一个文件允许的并发访问数。

      其本质是一个共享锁。

4.2 重要API

4.2.1 构造方法

Semaphore(int permits):创建具有给定许可数的计数信号量并设置为非公平信号量。

Semaphore(int permits,boolean fair):当fair等于true时,创建具有给定许可数的计数信号量并设置为公平信号量,即当fair=true的时候,先进来的先执行。

注意:单个信号量的Semaphore对象可以实现互斥锁的功能,并且可以是由一个线程获得了锁,再由另一个线程释放锁,这个可以应用于死锁恢复的一些场合

4.2.2 获取

void acquire():从此信号量获取一个许可,前线程将一直阻塞。相当于一辆车占了一个车位。

void acquire(int n):从此信号量获取给定数目许可,在提供这些许可前一直将线程阻塞。比如n=2,就相当于一辆车占了两个车位

void acquireUninterruptibly():从此信号量中获取许可,在有可用的许可前将其阻塞

void acquireUninterruptibly(int permits):从此信号量获取给定数目的许可,在提供这些许可前一直将线程阻塞。

int availablePermits():当前可用的许可数

int drainPermits():获取并返回立即可用的所有许可

protected Collection<Thread> getQueuedThreads():返回一个 collection,包含可能等待获取的线程。

int getQueueLength():返回正在等待获取的线程的估计数目。

boolean hasQueuedThreads():查询是否有线程正在等待获取

boolean isFair():如果此信号量的公平设置为 true,则返回 true

4.2.3 释放信号

void release():释放一个许可,将其返回给信号量。就如同车开走返回一个车位

void release(int n):释放n个许可。

例如:

package com.bjc.thread.demo6;

import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Semaphore;

public class SemaphoreTest {
	public static void main(String[] args) {
		// 创建一个缓存线程池
		ExecutorService service = Executors.newCachedThreadPool();
		// 创建一个有3个信号量的信号灯
		final  Semaphore sp = new Semaphore(3);
		for(int i=0;i<10;i++){
			Runnable runnable = new Runnable(){
				public void run(){
					try {
						// 获取许可
						sp.acquire();
					} catch (InterruptedException e1) {
						e1.printStackTrace();
					}
					System.out.println("线程" + Thread.currentThread().getName() + 
							"进入,当前已有" + (3-sp.availablePermits()) + "个并发");
					try {
						Thread.sleep((long)(Math.random()*10000));
					} catch (InterruptedException e) {
						e.printStackTrace();
					}
					System.out.println("线程" + Thread.currentThread().getName() + 
							"即将离开");		
					// 执行完毕,释放信号灯
					sp.release();
					//下面代码有时候执行不准确,因为其没有和上面的代码合成原子单元
					System.out.println("线程" + Thread.currentThread().getName() + 
							"已离开,当前已有" + (3-sp.availablePermits()) + "个并发");					
				}
			};
			// 执行线程
			service.execute(runnable);			
		}
	}

}

4.3 案例

4.3.1 案例1——非公平信号

用Semphore类创建对象从而初始化信号量,控制可活动的线程数

package com.bjc.thread.demo6;
import java.util.concurrent.Semaphore;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.LinkedBlockingQueue;
public class TestDemo1 {
	private static final Semaphore semaphore=new Semaphore(3);
	private static final ThreadPoolExecutor threadPool=new ThreadPoolExecutor(5,10,60,TimeUnit.SECONDS,new LinkedBlockingQueue<Runnable>());
	
	private static class InformationThread extends Thread{
		private final String name;
		private final int age;
		public InformationThread(String name,int age) {
			this.name=name;
			this.age=age;
		}
		
		public void run() {
			try {
				semaphore.acquire();
				System.out.println(Thread.currentThread().getName()+":大家好,我是"+name+"我今年"+age+"岁");
				Thread.sleep(1000);
				System.out.println(name+"要准备释放许可证了");
				System.out.println("当前可使用的许可数为:"+semaphore.availablePermits());
				semaphore.release();
			} catch(InterruptedException e) {
				e.printStackTrace();
			}
		}
	}
	public static void main(String[] args) {
		String[] name= {"纪大","和二","张三","李四","王五","赵六","钱七"};
		for(int i=0;i<7;i++) {
			Thread t1=new InformationThread(name[i],(i + 1 * 23));
			threadPool.execute(t1);
		}
	}
 
}

4.3.2 案例2——公平信号灯

只需要将上述的private static final Semaphore semaphore=new Semaphore(3);改成private static final Semaphore semaphore=new Semaphore(3,true);即可。

注意:所谓的公平,就是先进先出

4.3.3 实现单例模式

只需要将private static final Semaphore semaphore=new Semaphore(3) 改成 private static final Semaphore semaphore=new Semaphore(1)即可,也就是将构造函数的信号量个数设为1就行了

5. CyclicBarrier同步工具(栅栏)

5.1 概述

        CyclicBarrier是一个同步辅助类,它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。

        CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。

5.2 重要API

5.3.1 构造函数

CyclicBarrier(int parties):设置parties、count及barrierCommand属性

CyclicBarrier(int parties, Runnable barrierAction):当await的数量到达了设定的数量后,首先执行该Runnable对象

5.3.2 其他API

await():通知barrier已完成线程 

例如:

package com.bjc.thread.demo6;
import java.util.concurrent.CyclicBarrier;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CyclicBarrierTest {

	public static void main(String[] args) {
		ExecutorService service = Executors.newCachedThreadPool();
		final  CyclicBarrier cb = new CyclicBarrier(3);
		for(int i=0;i<3;i++){
			Runnable runnable = new Runnable(){
					public void run(){
					try {
						Thread.sleep((long)(Math.random()*10000));	
						System.out.println("线程" + Thread.currentThread().getName() + 
								"即将到达集合地点1,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));						
						cb.await();
						
						Thread.sleep((long)(Math.random()*10000));	
						System.out.println("线程" + Thread.currentThread().getName() + 
								"即将到达集合地点2,当前已有" + (cb.getNumberWaiting()+1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));
						cb.await();	
						Thread.sleep((long)(Math.random()*10000));	
						System.out.println("线程" + Thread.currentThread().getName() + 
								"即将到达集合地点3,当前已有" + (cb.getNumberWaiting() + 1) + "个已经到达," + (cb.getNumberWaiting()==2?"都到齐了,继续走啊":"正在等候"));						
						cb.await();						
					} catch (Exception e) {
						e.printStackTrace();
					}				
				}
			};
			service.execute(runnable);
		}
		service.shutdown();
	}
}

5.3 应用场景

        比如一个大型的任务,常常需要分配好多子任务去执行,只有当所有子任务都执行完成时候,才能执行主任务,这时候,就可以选择CyclicBarrier了。

例如:我们需要统计全国的业务数据。其中各省的数据库是独立的,也就是说按省分库。并且统计的数据量很大,统计过程也比较慢。为了提高性能,快速计算。我们采取并发的方式,多个线程同时计算各省数据,最后再汇总统计。在这里CyclicBarrier就非常有用

package com.bjc.thread.demo6;

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

/**
 * 各省数据独立,分库存偖。为了提高计算性能,统计时采用每个省开一个线程先计算单省结果,最后汇总。
 * 
 * @author guangbo email:[email protected]
 * 
 */
public class TestDemo3 {

	// private ConcurrentHashMap result = new ConcurrentHashMap();

	public static void main(String[] args) {
		TotalService totalService = new TotalServiceImpl();
		CyclicBarrier barrier = new CyclicBarrier(5,
				new TotalTask(totalService));

		// 实际系统是查出所有省编码code的列表,然后循环,每个code生成一个线程。
		new BillTask(new BillServiceImpl(), barrier, "北京").start();
		new BillTask(new BillServiceImpl(), barrier, "上海").start();
		new BillTask(new BillServiceImpl(), barrier, "广西").start();
		new BillTask(new BillServiceImpl(), barrier, "四川").start();
		new BillTask(new BillServiceImpl(), barrier, "黑龙江").start();

	}
}

/**
 * 主任务:汇总任务
 */
class TotalTask implements Runnable {
	private TotalService totalService;

	TotalTask(TotalService totalService) {
		this.totalService = totalService;
	}

	public void run() {
		// 读取内存中各省的数据汇总,过程略。
		totalService.count();
		System.out.println("=======================================");
		System.out.println("开始全国汇总");
	}
}

/**
 * 子任务:计费任务
 */
class BillTask extends Thread {
	// 计费服务
	private BillService billService;
	private CyclicBarrier barrier;
	// 代码,按省代码分类,各省数据库独立。
	private String code;

	BillTask(BillService billService, CyclicBarrier barrier, String code) {
		this.billService = billService;
		this.barrier = barrier;
		this.code = code;
	}

	public void run() {
		System.out.println("开始计算--" + code + "省--数据!");
		billService.bill(code);
		// 把bill方法结果存入内存,如ConcurrentHashMap,vector等,代码略
		System.out.println(code + "省已经计算完成,并通知汇总Service!");
		try {
			// 通知barrier已经完成
			barrier.await();
		} catch (InterruptedException e) {
			e.printStackTrace();
		} catch (BrokenBarrierException e) {
			e.printStackTrace();
		}
	}

}

6. CountDownLatch同步工具(计数器)

6.1 概述

        CountDownLatch是Java1.5之后引入的Java并发工具类,放在java.util.concurrent包下面。

        CountDownLatch能够使一个或多个线程等待其他线程完成各自的工作后再执行;CountDownLatch是JDK 5+里面闭锁的一个实现。通俗的理解,CountDownLatch犹如一个倒计时器,调用其countDown方法的时候,就将计数器减1,当计数等于0的时候,则所有等待着或者单个等待着开始执行。

注意:

1. 闭锁(Latch)的概念:一种同步方法,可以延迟线程的进度直到线程到达某个终点状态。通俗的讲就是,一个闭锁相当于一扇大门,在大门打开之前所有线程都被阻断,一旦大门打开所有线程都将通过,但是一旦大门打开,所有线程都通过了,那么这个闭锁的状态就失效了,门的状态也就不能变了,只能是打开状态。也就是说闭锁的状态是一次性的,它确保在闭锁打开之前所有特定的活动都需要在闭锁打开之后才能完成。

2. countdownlatch 是一个同步类工具,不涉及锁定,当count的值为零时当前线程继续运行,不涉及同步,只涉及线程通信的时候,使用它较为合适

6.2 API

构造函数:

        CountDownLatch(int count):指定计数的次数,只能被设置1次

void countDown():调用此方法则计数减1

void await():调用此方法会一直阻塞当前线程,直到计时器的值为0,除非线程被中断。

Long getCount():得到当前的计数

boolean await(long timeout, TimeUnit unit):调用此方法会一直阻塞当前线程,直到计时器的值为0,除非线程被中断或者计数器超时,返回false代表计数器超时。

例如:

package com.bjc.thread.demo6;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class CountdownLatchTest {

	public static void main(String[] args) {
		ExecutorService service = Executors.newCachedThreadPool();
		// 创建命令,计数器初始值为1
		final CountDownLatch cdOrder = new CountDownLatch(1);
		// 创建响应,计数器初始值为3
		final CountDownLatch cdAnswer = new CountDownLatch(3);		
		for(int i=0;i<3;i++){
			Runnable runnable = new Runnable(){
					public void run(){
					try {
						System.out.println("线程" + Thread.currentThread().getName() + 
								"正准备接受命令");	
						// 等待命令计数器为0在执行
						cdOrder.await();
						System.out.println("线程" + Thread.currentThread().getName() + 
						"已接受命令");								
						Thread.sleep((long)(Math.random()*10000));	
						System.out.println("线程" + Thread.currentThread().getName() + 
								"回应命令处理结果");	
						// 响应计数器减1
						cdAnswer.countDown();						
					} catch (Exception e) {
						e.printStackTrace();
					}				
				}
			};
			service.execute(runnable);
		}		
		try {
			Thread.sleep((long)(Math.random()*10000));
		
			System.out.println("线程" + Thread.currentThread().getName() + 
					"即将发布命令");	
			// 命令计数器减1为0,开始执行
			cdOrder.countDown();
			System.out.println("线程" + Thread.currentThread().getName() + 
			"已发送命令,正在等待结果");	
			// 响应计数器开始等待直到计数器为0
			cdAnswer.await();
			System.out.println("线程" + Thread.currentThread().getName() + 
			"已收到所有响应结果");	
		} catch (Exception e) {
			e.printStackTrace();
		}				
		service.shutdown();
	}
}

运行结果:

6.3 应用

1. 开启多个线程分块下载一个大文件,每个线程只下载固定的一截,最后由另外一个线程来拼接所有的分段

2. 应用程序的主线程希望在负责启动框架服务的线程已经启动所有的框架服务之后再执行

3. 确保一个计算不会执行,直到所需要的资源被初始化

4. 操作Excel文件等

7. Exchanger同步工具(交换者)

7.1 简介

        Exchanger可以在两个线程之间交换数据,只能是2个线程,他不支持更多的线程之间互换数据

7.2 实现原理

        Exchanger提供了一个同步点,在这个同步点两个线程可以交换彼此的数据。这两个线程通过exchange方法交换数据, 如果第一个线程先执行exchange方法,它会一直等待第二个线程也执行exchange,当两个线程都到达同步点时,这两个线程就可以交换数据,将本线程生产出来的数据传递给对方。因此使用Exchanger的重点是成对的线程使用exchange()方法,当有一对线程达到了同步点,就会进行交换数据。因此该工具类的线程对象是成对的

7.3 重要API

String exchange(V x):用于交换,启动交换并等待另一个线程调用exchange

String exchange(V x,long timeout,TimeUnit unit):用于交换,启动交换并等待另一个线程调用exchange,并且设置最大等待时间,当等待时间超过timeout便停止等待

例如:

package com.bjc.thread.demo6;
import java.util.concurrent.Exchanger;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

public class ExchangerTest {

	public static void main(String[] args) {
		ExecutorService service = Executors.newCachedThreadPool();
		// 定义交换者对象
		final Exchanger exchanger = new Exchanger();
		service.execute(new Runnable(){
			public void run() {
				try {				
					// 定义数据1
					String data1 = "张三";
					System.out.println("线程" + Thread.currentThread().getName() + 
					"正在把数据" + data1 +"换出去");
					Thread.sleep((long)(Math.random()*10000));
					// 交换-直到两个线程都执行了交换之后,程序才接着执行,否则一直等下去
					String data2 = (String)exchanger.exchange(data1);
					System.out.println("线程" + Thread.currentThread().getName() + 
					"换回的数据为" + data2);
				}catch(Exception e){
					
				}
			}	
		});
		service.execute(new Runnable(){
			public void run() {
				try {				
					// 定义数据2
					String data1 = "李四";
					System.out.println("线程" + Thread.currentThread().getName() + 
					"正在把数据" + data1 +"换出去");
					Thread.sleep((long)(Math.random()*10000));		
					// 交换-直到两个线程都执行了交换之后,程序才接着执行,否则一直等下去
					String data2 = (String)exchanger.exchange(data1);
					System.out.println("线程" + Thread.currentThread().getName() + 
					"换回的数据为" + data2);
				}catch(Exception e){
					
				}				
			}	
		});		
	}
}

运行结果:

发布了128 篇原创文章 · 获赞 6 · 访问量 3229

猜你喜欢

转载自blog.csdn.net/weixin_43318134/article/details/103658196