您的位置:首页 > 编程语言 > Java开发

Java并发(二)现代并发应用的构件

2014-11-06 13:33 78 查看

   俗话说“工欲善其身,必先利其器”。要想编写好的多线程并发系统,就必须要有一些好的封装类来作为我们的sychironiziton aid。java.util.concurrent包下面就有许多封装好了的类用来帮助我们写好多线程并发系统的新工具。

一,原子类:java.util.concurrent.atomic

    AtomicInteger,AtomicLong,AtomicBoolean,AtomicReference。它们的语义基本上和volatile一样,只不过封装在一个API了,这个API包含为操作提供的适当的原子方法(要么全做要么不做)。在编写这些实现时利用了现代处理器的特性,所以如果能从硬件和操作系统上得到适当的支持,它们可以是非阻塞(无须线程锁)的。如在Java语言中,++i和i++操作并不是线程安全的,在使用的时候,不可避免的会用到synchronized关键字,在AtomicInteger或AtomicLong上的原子操作getAndIncrement()方法就解决了这个问题。

   注:原子类不是从有相似名称的类继承下来的,所以AtomicBoolean不能当Boolean用。

二,线程锁:java.util.concurrent.locks

  sychronized同步方式是基于锁这样一个简单的概念,这种方式有几个缺点:

  • 锁只有一种类型
  • 对被锁住对象的所以同步操作都是一样的作用
  • 在同步代码块或方法开始时取得线程锁,结束时释放线程锁
  • 线程或者得到锁,或者阻塞-没有其他可能

如果我们重构对线程锁的支持,有几处可以得到提升(这就是Lock出现的原因吧):

  • 添加不同类型的锁
  • 对锁的阻塞没有限制,即允许在一个方法中上锁,在另一个方法中解锁
  • 如果线程得不到锁,就允许线程后退或继续执行
  • 允许线程尝试取锁,并可以在超过等待时间后放弃

Lock接口的几个实现类:

  1. ReentrantLock:本质上跟用在同步块上那种锁是一样的,但是它稍微灵活点。
  2. ReentrantReadWriteLock.ReadLock(静态内部类)
  3. ReentrantReadWriteLock.WriteLock(静态内部类)

Lock类的具体用法可以上网查查和看看帮助文档,这里就不写了。

 

三,CountDownLatch

     JDK1.5 API 中文版的解释是:“一个同步辅助类,在完成一组正在其他线程中执行的操作之前,它允许一个或多个线程一直等待。”想了一会儿还是觉得不好理解,于是看了看英文版的解释,英文是这样解释的:“A synchronization aid that allows one or more threads to wait until a set of operations being performed in other threads completes.”  才发现又是中文翻译的不到位(还是我理解能力不好?)...直接简单的解释就是:CountDownLatch 可以让一个或者多个线程一直处于等待状态,直到其它线程完成各自的操作之后,一个或者多个线程才开始运行。

    具体用法可以查看帮助文档下面是一个例子(来自其它博客):

 

public class CountDownLatchTest {

// 模拟了100米赛跑,10名选手已经准备就绪,只等裁判一声令下。当所有人都到达终点时,比赛结束。
public static void main(String[] args) throws InterruptedException {

// 开始的倒数锁
final CountDownLatch begin = new CountDownLatch(1);

// 结束的倒数锁
final CountDownLatch end = new CountDownLatch(10);

// 十名选手
final ExecutorService exec = Executors.newFixedThreadPool(10);

for (int index = 0; index < 10; index++) {
final int NO = index + 1;
Runnable run = new Runnable() {
public void run() {
try {
// 如果当前计数为零,则此方法立即返回。
// 等待
begin.await();
Thread.sleep((long) (Math.random() * 10000));
System.out.println("No." + NO + " arrived");
} catch (InterruptedException e) {
} finally {
// 每个选手到达终点时,end就减一
end.countDown();
}
}
};
exec.submit(run);
}
System.out.println("Game Start");
// begin减一,开始游戏
begin.countDown();
// 等待end变为0,即所有选手到达终点
end.await();
System.out.println("Game Over");
exec.shutdown();
}
}

 

 

 三,ConcurrentHashMap,CopyOnWriteArrayList

      ConcurrentHashMap类是标准HashMap的并发版本。它改进了Collections类中提供的SychironizedMap()功能,因为那些方法返回的集合中包含的锁要比需要的多。

      CopyOnWriteArrayList的用法请参考Java中的CopyOnWrite容器 感觉写的比较好。

 四,Queue

     Java中有些多线程编程模式在很大程度上都依赖于Queue实现 线程的安全性。Queue常用来在线程之间传递工作单元,这个模式通常适合用Queue最简单的并发扩展BlockingQueue来实现。

     1.BlockingQueue

       BlockingQueue的两个特性:

  • 向Queue中put()时,如果Queue以满,则放入的线程将等待直到Queue腾出了空间
  • 从Queue中take()时,如果Queue为空,则会导致取出线程阻塞

     BlockingQueue接口的两个基本实现:LinkedBlockingQueue,ArrayBlockingQueue.它们的特性以及使用场景就不说了,看名字就知道了。

    2.使用工作单元

     比如说,你有一个表示工作单元的MyAwesomeClass类,想要用多线程方式处理,你可能会想到用BlockingQueue<MyAwesomeClass>来表示工作队列。但是请想想用BlockingQueue<WorkUnit<MyAwesomeClass>>这样会不会更好呢(WorkUnit这个名字随便自己怎么取)?

public class WorkUnit<T>
private final T workunit;
public WorkUnit(T workUnit){
this.workunit=workUnit
}
public T getWork(){
return workunit;
}

 

  有了这层间接引用(有没有感觉有点Proxy模式的味道?),不用修改原来的类(这里就是MyAwesomeClass)就可以添加额外的数据或者处理方法了如:

  •  测试(记录一个对象的修改历史)
  •  性能指标(比如到达时间或者服务质量)
  • 运行时系统信息

一个BlockingQueue + workunit的例子:

 

package concurrent.blockingQueue;

public abstract class Pet {

protected final String name;
public Pet(String name) {
super();
this.name = name;
}
public abstract void examine();
}

class Cat extends Pet{

public Cat(String name) {
super(name);
}
@Override
public void examine() {
System.out.println("Meow!");
}
}

class Dog extends Pet{

public Dog(String name) {
super(name);
}
@Override
public void examine() {
System.out.println("Woof!");
}

}

 

 

package concurrent.blockingQueue;
//workUnit
public class Appointment<T> {

private final T toBeSeen;

public T getPatient() {
return toBeSeen;
}
public Appointment(T toBeSeen) {
this.toBeSeen = toBeSeen;
}
}

 

 

package concurrent.blockingQueue;

import java.util.concurrent.BlockingDeque;

public class Veterinarian extends Thread {

protected final BlockingDeque<Appointment<Pet>> appts;
protected String text="";
protected final int restTime;
private boolean shutdown=false;
public Veterinarian(BlockingDeque<Appointment<Pet>> appts, int restTime) {
super();
this.appts = appts;
this.restTime = restTime;
}

public synchronized void shutdown(){
shutdown=true;
}

public void run(){
while(!shutdown){
this.seePatient();
try {
Thread.sleep(restTime);
} catch (Exception e) {
shutdown=true;
}
}
}

public void seePatient(){
try {
Appointment<Pet> ap=appts.take();
Pet patient=ap.getPatient();
patient.examine();
} catch (Exception e) {
shutdown=true;
}
}

}

 

 除了简单的take()和offer() API ,BlockingQueue还提供了另外一种与队列交互的方式,这种控制力度更大。就是带有超时放入或者取出的操作,它还允许线程在遇到问题时可以从与队列的交互中退出来,转而做点其它的事情。

 

以上内容全部总结与《Java程序员修炼之道》如果想要具体,深入的了解建议看看原书。

 

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: