定制并发类(十一)实现自定义的原子对象

声明:本文是《 Java 7 Concurrency Cookbook 》的第七章,作者: Javier Fernández González     译者:许巧辉 校对:方腾飞

实现你自己的原子(atomic)对象

Java版本5中引入原子变量,并提供对单个变量的原子操作。当一个线程在原子变量上执行操作时,这个类的实现包含一种机制用来检查这个操作在一个步骤内完成。基本上,这个操作是先获取变量的值,然后在本地变量中改变这个值,最后尝试将旧值变成这个新值。如果旧值仍然是相同的,它将改变成新值,否则,这个方法重新开始这个操作。(校对注:这段话描述了CAS的实现原理 )

在这个指南中,你将学习如何继承一个原子对象和如何实现遵从原子对象机制的两个操作,来保证所有的操作在一个步骤内完成。

准备工作…

这个指南的例子使用Eclipse IDE实现。如果你使用Eclipse或其他IDE,如NetBeans,打开它并创建一个新的Java项目。

如何做…

按以下步骤来实现的这个例子:

1.创建ParkingCounter类,并指定它继承AtomicInteger类。


1 public class ParkingCounter extends AtomicInteger {

2.声明一个私有的、int类型的属性maxNumber,用来存储停车场允许停放汽车的最大数量。


1 private int maxNumber;

3.实现这个类的构造器,并初始化它的属性。


1 public ParkingCounter(int maxNumber){
2 set(0);
3 this.maxNumber=maxNumber;
4 }

4.实现carIn()方法。这个方法增加车的计数器,如果它小于设置的最大数。构建一个无限循环,并使用get()方法获取内部计数器的值。


1 public boolean carIn() {
2 for (;;) {
3 int value=get();

5.如果计数器的值等于最maxNumber属性值,这个计数器不能再增加(停车场已满,其他车不能再进入)。这个方法返回false值。


1 if (value==maxNumber) {
2 System.out.printf("ParkingCounter: The parking lot is
3 full.\n");
4 return false;

6.否则,增加这个值,并compareAndSet()方法将旧值变成新值。如果这个方法返回false值,说明计数器没有增加,所以你必须重新开始这个循环。如果这个方法返回true值,它意味着改变操作成功,然后你返回了true值。


01 } else {
02 int newValue=value+1;
03 boolean changed=compareAndSet(value,newValue);
04 if (changed) {
05 System.out.printf("ParkingCounter: A car has
06 entered.\n");
07 return true;
08 }
09 }
10 }
11 }

7.实现carOut()方法。这个方法减少车的计数器值,如果它大于0。构建一个无限循环,并使用get()方法获取内部的计数器的值。


01 public boolean carOut() {
02 for (;;) {
03 int value=get();
04 if (value==0) {
05 System.out.printf("ParkingCounter: The parking lot is
06 empty.\n");
07 return false;
08 } else {
09 int newValue=value-1;
10 boolean changed=compareAndSet(value,newValue);
11 if (changed) {
12 System.out.printf("ParkingCounter: A car has gone
13 out.\n");
14 return true;
15 }
16 }
17 }
18 }

8.创建一个实现Runnable接口的Sensor1类。


1 public class Sensor1 implements Runnable {

9.声明一个私有的、ParkingCounter类型的属性counter。


1 private ParkingCounter counter;

10.实现这个类的构造器,并初始化它的属性。


1 public Sensor1(ParkingCounter counter) {
2 this.counter=counter;
3 }

11.实现run()方法。调用几次carIn()和carOut()操作。


01 @Override
02 public void run() {
03 counter.carIn();
04 counter.carIn();
05 counter.carIn();
06 counter.carIn();
07 counter.carOut();
08 counter.carOut();
09 counter.carOut();
10 counter.carIn();
11 counter.carIn();
12 counter.carIn();
13 }

12.创建一个实现了Runnable接口的Sensor2类。


1 public class Sensor2 implements Runnable {

13.声明一个私有的、ParkingCounter类型的属性counter。


1 private ParkingCounter counter;

14.实现这个类的构造器,并初始化它的属性。


1 public Sensor2(ParkingCounter counter) {
2 this.counter=counter;
3 }

15.实现run()方法。调用几次carIn()和carOut()操作。


01 @Override
02 public void run() {
03 counter.carIn();
04 counter.carOut();
05 counter.carOut();
06 counter.carIn();
07 counter.carIn();
08 counter.carIn();
09 counter.carIn();
10 counter.carIn();
11 counter.carIn();
12 }

16.实现这个例子的主类,通过实现Main()类,并实现main()方法。


1 public class Main {
2 public static void main(String[] args) throws Exception {

17.创建一个ParkingCounter对象,名为counter。


1 ParkingCounter counter=new ParkingCounter(5);

18.创建和启动一个Sensor1任务和一个Sensor2任务。


1 Sensor1 sensor1=new Sensor1(counter);
2 Sensor2 sensor2=new Sensor2(counter);
3 Thread thread1=new Thread(sensor1);
4 Thread thread2=new Thread(sensor2);
5 thread1.start();
6 thread2.start();

19.等待这两个任务的结束。


1 thread1.join();
2 thread2.join();

20.将计数器的实际值写入到控制台。


1 System.out.printf("Main: Number of cars: %d\n",counter.get());

21.写入一条信息到控制台表明程序的结束。


1 System.out.printf("Main: End of the program.\n");

它是如何工作的…

继承AtomicInteger类的ParkingCounter类有两个原子操作,carIn()和carOunt()。这个例子模拟一个系统来控制停车场内的汽车数。这个停车场可容纳的汽车数用maxNumber属性表示。

carIn()操作将实际汽车数与停车场(可容纳的汽车数)的最大值进行比较。如果它们相等,这辆汽车不能进行停车场并返回false值。否则,它使用以下的原子操作结构:

  • 用一个本地变量获取原子对象的值。
  • 用一个不同的变量来存储新值。
  • 使用compareAndSet()方法尝试将旧值替换成新值。如果这个方法返回true,作为参数传入的旧值是这个变量的值,因此,它使值变化。随着carIn()方法返回true值,这个操作将以原子方式完成。如果compareAndSet()方法返回false值,作为参数传入的旧值不是这个变量的值(其他线程已修改它),所以这个操作不能以原子方式完成。这个操作将重新开始,直到它可以以原子方式完成。

carOut()方法与carIn()方法类似。你已实现两个Runnable对象,使用carIn()和carOut()来模拟停车的活动。当你执行这个程序,你可以看出停车场没有克服汽车在停车场的最大值。

上一篇:Kubernetes-基于Dockerfile构建docker镜像实践


下一篇:洛谷 P1602 Sramoc问题