本文共 3359 字,大约阅读时间需要 11 分钟。
很多程序的复杂性都是由于并发引起的,如果在并发情况下,如果对象是无状态那可以很好的运行,无论是在单线程还是多线程环境下都没问题,但是很多时候对象的状态是需要上下文维护的,因此在并发情况下就很容易导致不一致性的情况。我们先看下面一个例子:
class File { private int num; public void incr() throws InterruptedException, BrokenBarrierException { this.num ++; } public int getNum() { return num; }}
测试代码:
for (int j = 0;j < 100;j ++) { // 测试100次 final ExecutorService executorService = Executors.newCachedThreadPool(); Setset = new HashSet (); final File file = new File(); // 启动1000个线程 for (int i = 0;i < 1000;i ++) { executorService.execute(new Runnable () { @Override public void run() { try { file.incr(); } catch (Exception ex) { } } }); } executorService.shutdown(); while (true) { // 等待线程任务执行完毕 if (executorService.isTerminated()) { System.out.println(file.getNum()); break; } } }
就是模拟多个线程去对num进行自增。线程安全性的定义是怎样这个没一个标准的定义,但是最广泛的说法就是与单线程执行保持一致性,包括数据库的最高事务隔离级别也是可串行化级别。如果这个操作是单线程执行,最后的结果必然是1000。运行100次也是同样的结果。但是在并发的情况下,这里i ++操作是非原子操作,这个操作实际上是分三步,读出i值,给i加1,赋值给i,然后将i写会主内存。这里需要介绍的内存模型,如图:
class File { private AtomicInteger num = new AtomicInteger(0); public void incr() throws InterruptedException, BrokenBarrierException { num.addAndGet(1); } public int getNum() { return num.get(); }}
这样自增相当于是一个原始操作,可以达到类似于以下效果:
class File { private Integer num = new Integer(0); private Object lock = new Object(); public void incr() throws InterruptedException, BrokenBarrierException { synchronized (this.lock) { // 其他线程阻塞到之前 this.num = this.num + 1; } } public int getNum() { return num; }}
保证自增为原子操作。值得注意的是我在测试的时候发现一个问题,如果是对Integer对象加锁:
synchronized (this.num) { this.num = this.num + 1; }
则不能保证其他线程阻塞在this.num = this.num + 1;之前。无法完成同步。.
当然除了采用Java内置锁还可以采用并发包提供的lock。例如:private Integer num = new Integer(0);private ReentrantLock lock = new ReentrantLock();public void incr() throws InterruptedException, BrokenBarrierException { try { this.lock.lock(); this.num ++; } catch (Exception ex) {} finally { this.lock.unlock(); } }
一样能够保证自增的原子性。如果不采用锁机制:
private volatile Integer num = new Integer(0);
这样:
public void incr() throws InterruptedException, BrokenBarrierException { this.num ++; }
能够保证线程安全吗?线程安全包含两个要素,操作的原子性和可见性,volatile只能保证可见性而不能保证操作的原子性,因为不会采用锁机制。它的原理是如果多个线程栈内存缓存了某个值,如果其中一个线程修改了这个值,会立刻更新到共享内存,也会通知其他线程该变量指向的值是失效的,其他线程将不会使用该线程缓存的值而是强制去刷共享内存的值。
线程 : 读取i == 1 - >修改 i = 2 - > 写回内存(立即刷新共享内存) volatile并不阻碍另一个线程去获取i的值。因此还是可能读到修改前的数据,无法保证线程安全性。 加锁机制: 线程1:读取i == 1 - >修改 i = 2 - > 写回内存 线程2:阻塞 - ——————————-> 读取i = 2 ..写回内存3 线程3 : 阻塞 - ————————————————————>读取i == 3 因此上述代码不是线程安全的。 总而言之volatile只能保证更新的数据能够立刻更新到共享内存,定义变量为volatile尽量不要让更新后的值依赖之前的值。当然由于 volatile不会加锁,因此会具有更好的并发性能。