java并发编程-构建块

java 并发编程-构建块

关键字: java concurrent

java 并发编程-Executor框架

java5引入了很多新的并发容器和工具,极大的简化了并发程序的编写。本文先介绍Collections.synchronizedXXX工厂方 法创建的同步容器的不足,再介绍 ConcurrentHashMap,CopyOnWriterArrayList,BlockingQueue,CountDownLatch,Semaphore,CyclicBarrier 和显示锁类。

 

一、引言

所有的并发问题都来源于如何协调访问并发状态,可变状态越少,并发控制也就越容易容易。没有可变状态的对象永远是现成安全。对有可变状态的对象的并 发访问必须进行加锁,对每个方法都加锁并不能保证并发访问一定处于一致的状态。在构建并发程序的时候,可以把并发的控制代理到已有的并发类,比如类库提供 的并发容器或工具。

二、同步容器的不足

对Collections.synchronizedXXX工厂方法创建的同步容器,每个方法都是使用该容器的内部锁进行控制的,这会带来一性能问 题,因为多个安全的读操作也要等待排它锁。即使每个方法的调用都是线程安全的,但同时调用多个操作室,不一定是线程安全的,比如缺少即添加,相等即修改等 炒作,是两步原子操作构成的,和在一起并不是原子的,必须在调用代码里使用容器的锁进行控制。另外在迭代集合的时候,还会由于并发修改而抛出 ConcurrentModefiedExceptioin,这往往是调用程序不希望的结果。

三、ConcurrentHashMap,CopyOnWriterArrayList

ConcurrentHashMap使用的内部细粒度的分离锁,这个锁机制允许任意数量的读线程并发访问,提高了吞吐率。在迭代时不会抛出 ConcurrentModefiedExceptioin,如果在迭代期间有修改发生,返回的是迭代开始时的状态。另外对缺少即添加,相等即修改等二元 操作也有相应的方法支持。ConcurrentHashMap实现了ConcurrentMap提供的几个特殊原子操作:

public V putIfAbsent(K key,  V value)

如果指定键已经不再与某个值相关联,则将它与给定值关联。

public boolean remove(Object key, Object value)

只有目前将键的条目映射到给定值时,才移除该键的条目。

public boolean replace(K key, V oldValue, V newValue)

只有目前将键的条目映射到给定值时,才替换该键的条目。

public V replace(K key, V value)

只有目前将键的条目映射到某一值时,才替换该键的条目。

 

CopyOnWriterArrayListArrayList 的一个并发替代品,通常情况下,它提供比较好的并发性,允许多个现在并发的对其进行迭代。每次需要修改时,便会创建并重新发布一个新的容器拷贝,以此来实现可变性。因为底层使用数组实现,如果数组元素较多是,复制多付出的代价较大。

三、BlockingQueue

阻塞队列提供了可以阻塞的put和take方法,以及与之等价的可以指定超时的offer和poll。如果Queue是空的,那么take方法会一 直阻塞,直到有元素可用。如果Queue是有线长度的,队列满的时候put方法也会阻塞。BlockingQueue可以很好的支持生产者和消费者模式, 生产者往队列里put,消费者从队列里get,两者能够得好很好的同步。BlockingQueue的实现类LinkedBlockingQueue和 ArrayBlockingQueue是FIFO队列,PriorityBlockingQueue是一个按优先级排序的队列。使用 BlockingQueue构建的一个生产者与消费例子:

消费者:

Java 代码
  1. public   class  Consumer  implements  Runnable {  
  2.   
  3.     private  BlockingQueue<Food> queue;  
  4.     private  ExecutorService exec;  
  5.   
  6.     public  Consumer(BlockingQueue<Food> queue, ExecutorService exec) {  
  7.         this .queue = queue;  
  8.         this .exec = exec;  
  9.     }  
  10.   
  11.     @Override   
  12.     public   void  run() {  
  13.         while  (!exec.isShutdown()) {  
  14.             try  {  
  15.                 Thread.sleep(2000 );  
  16.                 Food food = queue.take();  
  17.                 System.out.println("Consumer "  + food);  
  18.             } catch  (InterruptedException e) {  
  19.                 e.printStackTrace();  
  20.             } catch  (RejectedExecutionException e) {  
  21.   
  22.             }  
  23.         }  
  24.     }  
  25. }  
public class Consumer implements Runnable {

	private BlockingQueue<Food> queue;
	private ExecutorService exec;

	public Consumer(BlockingQueue<Food> queue, ExecutorService exec) {
		this.queue = queue;
		this.exec = exec;
	}

	@Override
	public void run() {
		while (!exec.isShutdown()) {
			try {
				Thread.sleep(2000);
				Food food = queue.take();
				System.out.println("Consumer " + food);
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (RejectedExecutionException e) {

			}
		}
	}
}

 生产者:

Java 代码
  1. public   class  Producer  implements  Runnable {  
  2.   
  3.     private  BlockingQueue<Food> queue;  
  4.     private  ExecutorService exec;  
  5.   
  6.     public  Producer(BlockingQueue<Food> queue, ExecutorService exec) {  
  7.         this .queue = queue;  
  8.         this .exec = exec;  
  9.     }  
  10.   
  11.     @Override   
  12.     public   void  run() {  
  13.         while  (!exec.isShutdown()) {  
  14.             Food food = new  Food();  
  15.             try  {  
  16.                 Thread.sleep(4000 );  
  17.                 queue.put(food);  
  18.                 System.out.println("Produce "  + food);  
  19.             } catch  (InterruptedException e) {  
  20.                 e.printStackTrace();  
  21.             } catch  (RejectedExecutionException e) {  
  22.   
  23.             }  
  24.         }  
  25.     }  
  26. }  
public class Producer implements Runnable {

	private BlockingQueue<Food> queue;
	private ExecutorService exec;

	public Producer(BlockingQueue<Food> queue, ExecutorService exec) {
		this.queue = queue;
		this.exec = exec;
	}

	@Override
	public void run() {
		while (!exec.isShutdown()) {
			Food food = new Food();
			try {
				Thread.sleep(4000);
				queue.put(food);
				System.out.println("Produce " + food);
			} catch (InterruptedException e) {
				e.printStackTrace();
			} catch (RejectedExecutionException e) {

			}
		}
	}
}

 Main:

Java 代码
  1. BlockingQueue<Food> queue =  new  ArrayBlockingQueue<Food>( 5 );  
  2. ExecutorService exec = Executors.newFixedThreadPool(3 );  
  3. Producer p1 = new  Producer(queue, exec);  
  4. Producer p2 = new  Producer(queue, exec);  
  5.   
  6. Consumer c1 = new  Consumer(queue, exec);  
  7.   
  8. exec.execute(p1);  
  9. exec.execute(p2);  
  10. exec.execute(c1);  
  11. try  {  
  12.     Thread.sleep(10000 );  
  13. catch  (InterruptedException ignored) {  
  14. }  
  15. exec.shutdown();  
		BlockingQueue<Food> queue = new ArrayBlockingQueue<Food>(5);
		ExecutorService exec = Executors.newFixedThreadPool(3);
		Producer p1 = new Producer(queue, exec);
		Producer p2 = new Producer(queue, exec);

		Consumer c1 = new Consumer(queue, exec);
	
		exec.execute(p1);
		exec.execute(p2);
		exec.execute(c1);
		try {
			Thread.sleep(10000);
		} catch (InterruptedException ignored) {
		}
		exec.shutdown();

 四、CountDownLatch

闭锁(Latch),它可以延迟线程的进度知道线程到达终止状态。一个闭锁工作方式就像一道门,直到闭锁到达终点状态之前,门一直关闭着。终点状态到了之后,所有阻塞的线程都可以通过。CountDownLatch 使用一个计数器作为终点状态,知道计数器的值到达0时,闭锁才会打开。调用await 方法,线程会阻塞知道计数器为0,countDown 方法使计数器减一。

闭锁有两种常见的用法,开始闭锁,结束闭锁。开始闭锁用于等待一个条件到达后所有线程一起执行,结束闭锁可以用来等待所有条件或所有线程结束后再进行后续处理。例子:

Java 代码
  1. final  CountDownLatch startLatch =  new  CountDownLatch( 1 );  
  2. final  CountDownLatch endLatch =  new  CountDownLatch( 3 );  
  3. Runnable prepare = new  Runnable() {  
  4.     @Override   
  5.     public   void  run() {  
  6.         try  {  
  7.             startLatch.await();//等待开始闭锁,线程同时开始执行   
  8.             System.out.println("收拾东西,准备出门" );  
  9.             Random rnd = new  Random();  
  10.             Thread.sleep(rnd.nextInt(1000 ));  
  11.         } catch  (InterruptedException ignored) {  
  12.         }  
  13.         endLatch.countDown();  
  14.     }  
  15. };  
  16.   
  17. Thread mum = new  Thread(prepare);  
  18. Thread dad = new  Thread(prepare);  
  19. Thread me = new  Thread(prepare);  
  20. mum.start();  
  21. dad.start();  
  22. me.start();  
  23. startLatch.countDown();  
  24. try  {  
  25.     endLatch.await();  
  26. catch  (InterruptedException ignored) {  
  27. }  
  28. System.out.println("逛街" );  
final CountDownLatch startLatch = new CountDownLatch(1);
final CountDownLatch endLatch = new CountDownLatch(3);
Runnable prepare = new Runnable() {
	@Override
	public void run() {
		try {
			startLatch.await();//等待开始闭锁,线程同时开始执行
			System.out.println("收拾东西,准备出门");
			Random rnd = new Random();
			Thread.sleep(rnd.nextInt(1000));
		} catch (InterruptedException ignored) {
		}
		endLatch.countDown();
	}
};

Thread mum = new Thread(prepare);
Thread dad = new Thread(prepare);
Thread me = new Thread(prepare);
mum.start();
dad.start();
me.start();
startLatch.countDown();
try {
	endLatch.await();
} catch (InterruptedException ignored) {
}
System.out.println("逛街");

 五、Semaphore,信号量

使用信号量进行同步和互斥的控制是最经典的并发模型,java 中也提高支持。一个Semaphore管理一个有效的许可 集,许可基的数量通过构造函数传入,通过acquire方法申请一个许可,许可数为0则阻塞线程,否则许可数减一,使用release方法释放一个许个, 许可数加一。一个技术量为1的Semaphore为二元信号量,相当于一个互斥锁,表示不可重入的锁。一个使用信号量控制并发容器上届的例子:

Java 代码
  1. public   class  BoundedHashSet<T> {  
  2.     private   final  Set<T> set;  
  3.     private   final  Semaphore sem;  
  4.   
  5.     public  BoundedHashSet( int  bound) {  
  6.         set = Collections.synchronizedSet(new  HashSet<T>());  
  7.         sem = new  Semaphore(bound);  
  8.     }  
  9.   
  10.     public   boolean  add(T o)  throws  InterruptedException {  
  11.         sem.acquire();  
  12.         boolean  wasAdded =  false ;  
  13.         try  {  
  14.             wasAdded = set.add(o);  
  15.             return  wasAdded;  
  16.         } finally  {  
  17.             if  (!wasAdded)  
  18.                 sem.release();  
  19.         }  
  20.     }  
  21.   
  22.     public   boolean  remove(Object o) {  
  23.         boolean  wasRemoved = set.remove(o);  
  24.         if  (wasRemoved)  
  25.             sem.release();  
  26.         return  wasRemoved;  
  27.     }  
  28. }  
public class BoundedHashSet<T> {
	private final Set<T> set;
	private final Semaphore sem;

	public BoundedHashSet(int bound) {
		set = Collections.synchronizedSet(new HashSet<T>());
		sem = new Semaphore(bound);
	}

	public boolean add(T o) throws InterruptedException {
		sem.acquire();
		boolean wasAdded = false;
		try {
			wasAdded = set.add(o);
			return wasAdded;
		} finally {
			if (!wasAdded)
				sem.release();
		}
	}

	public boolean remove(Object o) {
		boolean wasRemoved = set.remove(o);
		if (wasRemoved)
			sem.release();
		return wasRemoved;
	}
}

 六、CyclicBarrier

关卡(Barrier)类似于闭锁,他们都能阻塞一组线程,知道某些事件发生,不同之处在于所有CyclicBarrier等待的是现线程,只有一 定数目的线程到达这个点时,才允许同时通过。它允许一组线程互相等待,直到到达某个公共屏障点 (common barrier point)。在涉及一组固定大小的线程的程序中,这些线程必须不时地互相等待,此时 CyclicBarrier 很有用。因为该 barrier 在释放等待线程后可以重用,所以称它为循环 的 barrier。CyclicBarrier 支持一个可选的 Runnable 命令,在一组线程中的最后一个线程到达之后(但在释放所有线程之前),该命令只在每个屏障点运行一次。若在继续所有参与线程之前更新共享状态,此屏障操作 很有用。

Java 代码
  1. public   class  Main {  
  2.   
  3.     public   static  CyclicBarrier getCyclicBarrier( int  count) {  
  4.         if  (count <=  0 )  
  5.             return   null ;  
  6.         final  CyclicBarrier cyclicBarrier =  new  CyclicBarrier(count,  
  7.                 new  Runnable() {  
  8.                     public   void  run() {  
  9.                         try  {  
  10.                             Thread.sleep(1000 );  
  11.                         } catch  (InterruptedException e) {  
  12.                             e.printStackTrace();  
  13.                         }  
  14.                         System.out.println("conditon is arrive and CycleBarrier is running" );  
  15.                     }  
  16.                 });  
  17.         return  cyclicBarrier;  
  18.     }  
  19.   
  20.     public   static  Thread getThread(String nameOfThread,  
  21.             final  CyclicBarrier cyclicBarrier) {  
  22.         Thread thread = new  Thread(nameOfThread) {  
  23.             public   void  run() {  
  24.                 System.out.println(this .getName() +  
  25. "is begin; and count is " + (++count));  
  26.                 try  {  
  27.                     cyclicBarrier.await();  
  28.                 } catch  (InterruptedException e) {  
  29.                     e.printStackTrace();  
  30.                 } catch  (BrokenBarrierException e) {  
  31.                     e.printStackTrace();  
  32.                 }  
  33.                 System.out.println(this .getName() +  "finished" );  
  34.             }  
  35.         };  
  36.         return  thread;  
  37.   
  38.     }  
  39.   
  40.     static   int  count =  0 ;  
  41.   
  42.     public   static   void  main(String[] args) {  
  43.         /** define a cyclicBarrier and number of barrier is 2. */   
  44.         CyclicBarrier cyclicBarrier = getCyclicBarrier(2 );  
  45.         Thread threadOne = getThread("threadOne" , cyclicBarrier);  
  46.         threadOne.start();  
  47.         Thread threadTwo = getThread("threadTwo" , cyclicBarrier);  
  48.         threadTwo.start();  
  49.     }  
  50. }  
public class Main {

	public static CyclicBarrier getCyclicBarrier(int count) {
		if (count <= 0)
			return null;
		final CyclicBarrier cyclicBarrier = new CyclicBarrier(count,
				new Runnable() {
					public void run() {
						try {
							Thread.sleep(1000);
						} catch (InterruptedException e) {
							e.printStackTrace();
						}
						System.out.println("conditon is arrive and CycleBarrier is running");
					}
				});
		return cyclicBarrier;
	}

	public static Thread getThread(String nameOfThread,
			final CyclicBarrier cyclicBarrier) {
		Thread thread = new Thread(nameOfThread) {
			public void run() {
				System.out.println(this.getName() +
"is begin; and count is "+ (++count));
				try {
					cyclicBarrier.await();
				} catch (InterruptedException e) {
					e.printStackTrace();
				} catch (BrokenBarrierException e) {
					e.printStackTrace();
				}
				System.out.println(this.getName() + "finished");
			}
		};
		return thread;

	}

	static int count = 0;

	public static void main(String[] args) {
		/** define a cyclicBarrier and number of barrier is 2. */
		CyclicBarrier cyclicBarrier = getCyclicBarrier(2);
		Thread threadOne = getThread("threadOne", cyclicBarrier);
		threadOne.start();
		Thread threadTwo = getThread("threadTwo", cyclicBarrier);
		threadTwo.start();
	}
}

 该例子中CyclicBarrier等待两个线程到达后输出conditon is arrive and CycleBarrier is running,两个线程都从await中返回。

七、显式锁

 

java 5之前,用于调节共享对象访问的机制只有synchronized和volatile。java 5提供了新的选择:ReentrantLock。ReentrantLock能够提供更多的高级特性,比如轮询和可定时的加锁,可中断的加锁。以及一个支 持读锁和写锁的ReentrantReadWriteLock。使用ReentrantLock必须手动使用lock或其他操作加锁,在finally块 中unlock。

ReentrantLock:一个可重入的互斥锁Lock,它具有与使用synchronized方法和语句所访问的隐式监视器锁相同的一些基本行为和语义,但功能更强大。 使用ReentrantLock构建的同步Map:

Java 代码
  1. public   class  LockedMap<K, V> {  
  2.     private  Map<K, V> map;  
  3.     private  Lock lock =  new  ReentrantLock();  
  4.       
  5.     public  LockedMap(Map<K, V> map) {  
  6.         this .map = map;  
  7.     }  
  8.   
  9.     public  V get(K key) {  
  10.         lock.lock();  
  11.         try  {  
  12.             return  map.get(key);  
  13.         } finally  {  
  14.             lock.unlock();  
  15.         }  
  16.     }  
  17.   
  18.     public   void  put(K key, V value) {  
  19.         lock.lock();  
  20.         try  {  
  21.             map.put(key, value);  
  22.         } finally  {  
  23.             lock.unlock();  
  24.         }  
  25.     }  
  26. }  
public class LockedMap<K, V> {
	private Map<K, V> map;
	private Lock lock = new ReentrantLock();
	
	public LockedMap(Map<K, V> map) {
		this.map = map;
	}

	public V get(K key) {
		lock.lock();
		try {
			return map.get(key);
		} finally {
			lock.unlock();
		}
	}

	public void put(K key, V value) {
		lock.lock();
		try {
			map.put(key, value);
		} finally {
			lock.unlock();
		}
	}
}
Java 代码
  1. public   class  ReentrantLockTest {  
  2.   
  3.     private  List<Integer> numbers =  new  ArrayList <Integer>();  
  4.     private  Lock numbersLock =  new  ReentrantLock();  
  5.   
  6.     public   void  addNumbers( int  num) {  
  7.         try  {  
  8.             numbersLock.lock();  
  9.             numbers.add(num);  
  10.         } finally  {  
  11.             numbersLock.unlock();  
  12.         }  
  13.     }  
  14.   
  15.     public   void  outputNumbers() {  
  16.         try  {  
  17.             if  (numbersLock.tryLock( 1 , TimeUnit.SECONDS)) {  
  18.                 for  ( int  num : numbers) {  
  19.                     System.out.println(num);  
  20.                 }  
  21.             }  
  22.         } catch  (InterruptedException ex) {  
  23.             ex.printStackTrace();  
  24.         } finally  {  
  25.             numbersLock.unlock();  
  26.         }  
  27.     }      
  28.   
  29.     public   static   void  main(String[] args) {  
  30.         final  ReentrantLockTest test =  new  ReentrantLockTest();  
  31.         Executor pool = Executors.newFixedThreadPool(3 );  
  32.         pool.execute(new  Runnable() {  
  33.   
  34.             public   void  run() {  
  35.                 Random rnd = new  Random();  
  36.                 while  ( true ) {  
  37.                     int  number = rnd.nextInt();  
  38.                     test.addNumbers(number);  
  39.                     try  {  
  40.                         Thread.sleep(100 );  
  41.                     } catch  (InterruptedException ignored) {  
  42.                     }  
  43.                 }  
  44.             }  
  45.         });  
  46.   
  47.         pool.execute(new  Runnable() {  
  48.   
  49.             public   void  run() {  
  50.                 while  ( true ) {  
  51.                     test.outputNumbers();  
  52.                     try  {  
  53.                         Thread.sleep(1000 );  
  54.                     } catch  (InterruptedException ignored) {  
  55.                     }  
  56.                 }  
  57.             }  
  58.         });  
  59.     }  
  60.   
  61.   
  62. }  
public class ReentrantLockTest {

    private List<Integer> numbers = new ArrayList
<Integer>();
    private Lock numbersLock = new ReentrantLock();

    public void addNumbers(int num) {
        try {
            numbersLock.lock();
            numbers.add(num);
        } finally {
            numbersLock.unlock();
        }
    }

    public void outputNumbers() {
        try {
            if (numbersLock.tryLock(1, TimeUnit.SECONDS)) {
                for (int num : numbers) {
                    System.out.println(num);
                }
            }
        } catch (InterruptedException ex) {
            ex.printStackTrace();
        } finally {
            numbersLock.unlock();
        }
    }    

    public static void main(String[] args) {
        final ReentrantLockTest test = new ReentrantLockTest();
        Executor pool = Executors.newFixedThreadPool(3);
        pool.execute(new Runnable() {

            public void run() {
                Random rnd = new Random();
                while (true) {
                    int number = rnd.nextInt();
                    test.addNumbers(number);
                    try {
                        Thread.sleep(100);
                    } catch (InterruptedException ignored) {
                    }
                }
            }
        });

        pool.execute(new Runnable() {

            public void run() {
                while (true) {
                    test.outputNumbers();
                    try {
                        Thread.sleep(1000);
                    } catch (InterruptedException ignored) {
                    }
                }
            }
        });
    }


}
 

ReentrantReadWriteLock提供了对读锁和写锁的支持,同一时刻,可允许多个读锁,但只允许有一个写锁,读锁的获取和写锁的获取 是互斥的。从ReentrantReadWriteLock对象的readLock方法可以获得相应的读锁,writeLock方法可以获得相应的写锁。 使用ReentrantReadWriteLock构建的Map,允许多个get操作并发执行:

Java 代码
  1. public   class  ReadWriteMap<K,V>  {  
  2.     private  Map<K,V> map;  
  3.     private  ReadWriteLock lock =  new  ReentrantReadWriteLock();  
  4.     private  Lock readLock = lock.readLock();  
  5.     private  Lock writeLock  = lock.writeLock();   
  6.       
  7.     public  ReadWriteMap(Map<K,V> map){  
  8.         this .map = map;  
  9.     }  
  10.       
  11.     public  V get(K key){  
  12.         readLock.lock();  
  13.         try {  
  14.             return  map.get(key);  
  15.         }  
  16.         finally {  
  17.             readLock.unlock();  
  18.         }     
  19.     }  
  20.       
  21.     public   void  put(K key,V value){  
  22.         writeLock.lock();  
  23.         try {  
  24.             map.put(key, value);  
  25.         }  
  26.         finally {  
  27.             writeLock.unlock();  
  28.         }  
  29.     }  
  30.       
  31. }  
public class ReadWriteMap<K,V>  {
	private Map<K,V> map;
	private ReadWriteLock lock = new ReentrantReadWriteLock();
	private Lock readLock = lock.readLock();
	private Lock writeLock  = lock.writeLock();	
	
	public ReadWriteMap(Map<K,V> map){
		this.map = map;
	}
	
	public V get(K key){
		readLock.lock();
		try{
			return map.get(key);
		}
		finally{
			readLock.unlock();
		}	
	}
	
	public void put(K key,V value){
		writeLock.lock();
		try{
			map.put(key, value);
		}
		finally{
			writeLock.unlock();
		}
	}
	
}
 

所有代码见附件。本文参考《Java 并发编程实践 》。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值