当在多个线程之间共享数据时,必须使用同步机制保护对数据块的访问。可以在修改数据的方法声明中使用synchronized关键字,这样每次只能有一个线程修改数据。另一种方式是使用Lock类创建具有修改数据指令的临界区。
自从版本5开始,Java引入原子变量。当线程使用原子变量执行操作时,类的实现检查操作是否在一个步骤中完成。通常情况下,此操作得到变量值,在局部变量中修改值,然后试图将新值更改旧值。如果旧值仍然相同,那么它将执行更改。如果没有,该方法将再次开始操作。Java提供了以下类型的原子变量:
- AtomicBoolean
- AtomicInteger
- AtomicLong
- AtomicReference
在某些情况下,Java的原子变量性能比基于同步机制的解决方案更好(特别是关注每个独立变量的原子性时)。java.util.concurrent类包的一些类使用原子变量代替同步机制。本节将开发范例展示原子属性如何比同步机制提供更好的性能。
准备工作
本范例通过Eclipse开发工具实现。如果使用诸如NetBeans的开发工具,打开并创建一个新的Java项目。
实现过程
通过如下步骤实现不可变类:
-
创建名为TaskAtomic的类,指定其实现Runnable接口:
public class TaskAtomic implements Runnable{
-
声明名为number的私有AtomicInteger属性:
private final AtomicInteger number;
-
实现类构造函数,初始化属性:
public TaskAtomic () { this.number=new AtomicInteger(); }
-
实现run()方法,在重复1000000次的循环中,使用set()方法,将步骤的数量作为值分配给原子属性:
@Override public void run() { for (int i=0; i<1000000; i++) { number.set(i); } } }
-
创建名为TaskLock的类,指定其实现Runnable接口:
public class TaskLock implements Runnable {
-
声明名为number的私有int属性和名为lock的私有Lock属性:
private Lock lock; private int number;
-
实现类构造函数,初始化属性:
public TaskLock() { this.lock=new ReentrantLock(); }
-
实现run()方法,在重复1000000次的循环中,将步骤的数量分发给整型属性。在分配之前需要得到锁,之后再释放锁:
@Override public void run() { for (int i=0; i<1000000; i++) { lock.lock(); number=i; lock.unlock(); } } }
-
实现本范例主类,创建名为Main的类,包含main()方法:
public class Main { public static void main(String[] args) {
-
创建名为atomicTask的TaskAtomic对象:
TaskAtomic atomicTask=new TaskAtomic();
-
创建名为lockTask的TaskLock对象:
TaskLock lockTask=new TaskLock();
-
声明线程数量,创建Thread对象数组存储线程:
int numberThreads=50; Thread threads[]=new Thread[numberThreads]; Date begin, end;
-
加载指定数量的线程执行TaskLock对象,计算和输出线程执行时间到控制台:
begin=new Date(); for (int i=0; i<numberThreads; i++) { threads[i]=new Thread(lockTask); threads[i].start(); } for (int i=0; i<numberThreads; i++) { try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } end=new Date(); System.out.printf("Main: Lock results: %d\n", (end.getTime()-begin.getTime()));
-
加载指定数量的线程执行TaskAtomic对象,计算和输出线程执行时间到控制台:
begin=new Date(); for (int i=0; i<numberThreads; i++) { threads[i]=new Thread(atomicTask); threads[i].start(); } for (int i=0; i<numberThreads; i++) { try { threads[i].join(); } catch (InterruptedException e) { e.printStackTrace(); } } end=new Date(); System.out.printf("Main: Atomic results: %d\n", (end.getTime()-begin.getTime())); } }
扩展学习
当运行本范例时,将看到使用原子变量的TaskAtomic任务的执行时间是如何始终比使用锁的TaskLock任务更少。如果使用synchronized关键字代替锁,将得到相似的结果。
本节结论是使用原子变量将比其它同步方法提供更好的性能。如果没有适合需要的原子类型,那么可以尝试自定义原子类型。
更多关注
- 第八章“定制并发类”中的“实现自定义原子对象”小节