Java并发13:并发三特性-原子性定义、原子性问题与原子性保证技术

在Java并发编程中,如果要保证代码的安全性,则必须保证代码的原子性、可见性和有序性。

在 Java并发12:并发三特性-原子性、可见性和有序性概述及问题示例中,对并发中的三个特性(原子性、可见性和有序性)进行了初步学习。

本章主要就Java中保障原子性的技术进行更加全面的学习。

1.整体回顾
原子性定义:一个或多个操作,要么全部执行且在执行过程中不被任何因素打断,要么全部不执行。
Java自带原子性:对基本数据类型的变量的读取和赋值操作是原子性操作。
2.原子性问题
由上面的章节已知,不采取任何的原子性保障措施的自增操作并不是原子性的。
下面的代码实现了一个自增器(不是原子性的)。

/**
 * <p>原子性示例:不是原子性</p>
 *
 * @author hanchao 2018/3/10 14:58
 **/
static class Increment {
    private int count = 1;

    public void increment() {
        count++;
    }

    public int getCount() {
        return count;
    }
}

下面的代码展示了在多线程环境中,调用此自增器进行自增操作。

int type = 0;//类型
int num = 50000;//自增次数
int sleepTime = 5000;//等待计算时间
int begin;//开始的值
Increment increment;
//不进行原子性保护的大范围操作
increment = new Increment();
begin = increment.getCount();
LOGGER.info("Java中普通的自增操作不是原子性操作。");
LOGGER.info("当前运行类:" +increment.getClass().getSimpleName() +  ",count的初始值是:" + increment.getCount());
for (int i = 0; i < num; i++) {
    new Thread(() -> {
        increment.increment();
    }).start();
}
//等待足够长的时间,以便所有的线程都能够运行完
Thread.sleep(sleepTime);
LOGGER.info("进过" + num + "次自增,count应该 = " + (begin + num) + ",实际count = " + increment.getCount());

某次运行结果:

2018-03-17 22:52:23 INFO ConcurrentAtomicityDemo:132 - Java中普通的自增操作不是原子性操作。
2018-03-17 22:52:23 INFO ConcurrentAtomicityDemo:133 - 当前运行类:Increment,count的初始值是:1
2018-03-17 22:52:33 INFO ConcurrentAtomicityDemo:141 - 进过50000次自增,count应该 = 50001,实际count = 49999

通过观察结果,发现程序确实存在原子性问题。

3.原子性技术保障
在Java中提供了多种原子性保障措施,这里主要涉及三种:

通过synchronized关键字定义同步代码块或者同步方法保障原子性。
通过Lock接口保障原子性。
通过Atomic类型保障原子性。
3.1.synchronized关键字
对Increment类进行扩展:

/**
 * <p>原子性示例:通过synchronized保证代码块的原子性</p>
 *
 * @author hanchao 2018/3/10 15:07
 **/
static class SynchronizedIncrement extends Increment {
    /**
     * <p>添加关键字synchronized,使之成为同步方法</p>
     *
     * @author hanchao 2018/3/10 15:12
     **/
    @Override
    public synchronized void increment() {
        super.count++;
    }
}

 

在多线程环境中进行SynchronizedIncrement 的自增:

//synchronized关键字能够保证原子性(代码块锁,多线程操作某一对象时,在某个代码块内只能单线程执行)
increment = new SynchronizedIncrement();
begin = increment.getCount();
LOGGER.info("可以通过synchronized关键字保障代码的原子性");
LOGGER.info("当前运行类:" +increment.getClass().getSimpleName() +  ",count的初始值是:" + increment.getCount());
for (int i = 0; i < num; i++) {
    new Thread(() -> {
        increment.increment();
    }).start();
}
//等待足够长的时间,以便所有的线程都能够运行完
Thread.sleep(sleepTime);
LOGGER.info("进过" + num + "次自增,count应该 = " + (begin + num) + ",实际count = " + increment.getCount());

运行结果(多次):

2018-03-18 00:41:30 INFO  ConcurrentAtomicityDemo:147 - 可以通过synchronized关键字保障代码的原子性
2018-03-18 00:41:30 INFO  ConcurrentAtomicityDemo:148 - 当前运行类:SynchronizedIncrement,count的初始值是:1
2018-03-18 00:41:40 INFO  ConcurrentAtomicityDemo:156 - 进过50000次自增,count应该 = 50001,实际count = 50001

通过多次运行,发现运行结果一致,所以可以确定synchronized关键字能够保证代码的原子性。

3.2.Lock接口

对Increment类进行扩展:

/**
* <p>原子性示例:通过Lock接口保证指定范围代码的原子性</p>
*
* @author hanchao 2018/3/10 15:14
**/
static class LockIncrement extends Increment {
   //定义个读写锁:锁内运行多线程读,单线程写
   private static final ReentrantReadWriteLock readWriteLock = new ReentrantReadWriteLock(true);

   /**
    * <p>运用读写所重写方法</p>
    *
    * @author hanchao 2018/3/10 15:13
    **/
   @Override
   public void increment() {
       //写锁 加锁
       readWriteLock.writeLock().lock();
       try {
           //开始写
           super.count++;
       } finally {
           //将解锁放在finally块中,保证必然执行,防止死锁
           readWriteLock.writeLock().unlock();
       }
   }
}

在多线程环境中进行LockIncrement的测试:

//通过Lock接口保证原子性操作
increment = new LockIncrement();
begin = increment.getCount();
LOGGER.info("可以通过Lock接口保证代码的原子性");
LOGGER.info("当前运行类:" +increment.getClass().getSimpleName() +  ",count的初始值是:" + increment.getCount());
for (int i = 0; i < num; i++) {
    new Thread(() -> {
        increment.increment();
    }).start();
}
//等待足够长的时间,以便所有的线程都能够运行完
Thread.sleep(sleepTime);
LOGGER.info("进过" + num + "次自增,count应该 = " + (begin + num) + ",实际count = " + increment.getCount());

运行结果(多次):

2018-03-18 10:12:12 INFO  ConcurrentAtomicityDemo:163 - 可以通过Lock接口保证代码的原子性
2018-03-18 10:12:12 INFO  ConcurrentAtomicityDemo:164 - 当前运行类:LockIncrement,count的初始值是:1
2018-03-18 10:12:29 INFO  ConcurrentAtomicityDemo:172 - 进过50000次自增,count应该 = 50001,实际count = 50001

通过多次运行,发现运行结果一致,所以可以确定Lock接口能够保证代码的原子性。

3.3.Atomic类型

对Increment类进行扩展:

/**
* <p>原子性示例:通过Atomic类型保证类型的原子性</p>
*
* @author hanchao 2018/3/10 15:19
**/
static class AtomicIncrement {
   private AtomicInteger count = new AtomicInteger(1);

   /**
    * <p>无需其他处理,直接自增即可</p>
    *
    * @author hanchao 2018/3/10 15:21
    **/
   public void increment() {
       count.getAndIncrement();
   }

   public AtomicInteger getCount() {
       return count;
   }
}

在多线程环境中进行AtomicIncrement的测试:

//通过Atomic变量保证变量操作的原子性
AtomicIncrement increment1 = new AtomicIncrement();
begin = increment1.getCount().get();
LOGGER.info("可以通过Atomic类型保证变量的原子性");
LOGGER.info("当前运行类:" +increment1.getClass().getSimpleName() +  ",count的初始值是:" + increment1.getCount());
for (int i = 0; i < num; i++) {
    new Thread(() -> {
        increment1.increment();
    }).start();
}
//等待足够长的时间,以便所有的线程都能够运行完
Thread.sleep(sleepTime);
LOGGER.info("进过" + num + "次自增,count应该 = " + (begin + num) + ",实际count = " + increment1.getCount());

运行结果(多次):

2018-03-18 10:14:37 INFO  ConcurrentAtomicityDemo:178 - 可以通过Atomic类型保证变量的原子性
2018-03-18 10:14:37 INFO  ConcurrentAtomicityDemo:179 - 当前运行类:AtomicIncrement,count的初始值是:1
2018-03-18 10:14:48 INFO  ConcurrentAtomicityDemo:187 - 进过50000次自增,count应该 = 50001,实际count = 50001

通过多次运行,发现运行结果一致,所以可以确定Atomic类型能够保证代码的原子性。

4.总结
经验证,以下三种措施,可以保证Java代码在运行时的原子性:

synchronized关键字
Lock接口
Atomic类型
并发三特性总结

 

Java并发13:并发三特性-原子性定义、原子性问题与原子性保证技术

上一篇:python的深浅拷贝,又忘记了,再复习一次。


下一篇:Python08