Java并发编程的艺术 记录(二)
volatile的应用
volatile的定义如下:Java编程语言允许线程访问共享变量,为了确保共享变量能被准确和一致地更新,线程应该确保通过排他锁单独获得这个变量。Java语言提供了volatile,在某些情况下比锁要更加方便。如果一个字段被声明成volatile,Java线程内存模型确保所有线程看到这个变量的值是一致的。
缓存一致性协议(处理器):每个处理器通过嗅探在总线上传播的数据来检查自己缓存的值是不是过期了,当处理器发现自己缓存行对应的内存地址被修改,就会将当前处理器的缓存行设置成无效状态,当处理器对这个数据进行修改操作的时候,会重新从系统内存中把数据读到处理器缓存里。
volatile的两条实现原则 :
1)Lock前缀指令会引起处理器缓存回写到内存 。
2)一个处理器的缓存回写到内存会导致其他处理器的缓存无效 。
LinkedTransferQueue 待看。
synchronized的实现原理与应用
1.6及之后对synchronized做过优化。synchronized用的锁是存在Java对象头里的。
synchronized实现同步的基础:Java中的每一个对象都可以作为锁 。
synchronized锁的4种状态:级别从低到高依次是:无锁状态、偏向锁状态、轻量级锁状态和重量级锁状态,这几个状态会随着竞争情况逐渐升级。锁可以升级但不能降级,意味着偏向锁升级成轻量级锁后不能降级成偏向锁。这种锁升级却不能降级的策略,目的是为了提高获得锁和释放锁的效率 。
偏向锁引入的目的:为了让线程获得锁的代价更低而引入了偏向锁 。
偏向锁的获得和撤销流程:偏向锁使用了一种等到竞争出现才释放锁的机制,所以当其他线程尝试竞争偏向锁时,持有偏向锁的线程才会释放锁。
轻量级锁及膨胀流程图
锁的优缺点
处理器实现原子操作的两种方式:
1.总线锁
2.缓存锁
在Java中可以通过锁和循环CAS的方式来实现原子操作 。CAS(Compare and swap):CAS操作需要输入两个值,一个新值,一个旧值,在操作期间先比较旧值有没有发生变化,若无发生变化,则交换新值。
自旋CAS:循环进行CAS操作直到成功为止 。
示例:
package com.gjjun.concurrent; import java.util.ArrayList; import java.util.List; import java.util.concurrent.atomic.AtomicInteger; /** * 线程安全的自增和线程不安全的自增 * @author gjjun * @create 2018/8/12 **/ public class CounterDemo { private AtomicInteger ai = new AtomicInteger(0); private int i = 0; public static void main(String[] args) { final CounterDemo cas = new CounterDemo(); List<Thread> threads = new ArrayList<>(16); long start = System.currentTimeMillis(); for (int i = 0; i < 10; i++) { Thread t = new Thread(new Runnable() { @Override public void run() { for (int j = 0; j < 10000; j++) { cas.count(); cas.safeCount(); } } }); threads.add(t); } for (Thread thread : threads) { thread.start(); } for (Thread thread : threads) { try { thread.join(); } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(cas.i); System.out.println(cas.ai.get()); System.out.println(System.currentTimeMillis() - start); } /** * 不安全的计数 */ private void count() { i++; } /** * 使用cas实现线程安全计数器 */ private void safeCount() { for(;;) { int i = ai.get(); boolean suc = ai.compareAndSet(i, ++i); if (suc) { break; } } } }
CAS的三个问题:
1.ABA问题。一个值由A到B再到A,CAS则认为是无变化的,但实际上是变化的。使用版本号解决:1A-2B-3A,类为AtomicStampedReference。
2.循环时间长开销大。可以使用处理器的pause指令,来略微提升效率。
3.只能保证一个共享变量的原子操作。可以使用锁,或者使用AtomicReference类保证引用对象之间的原子性。