目录
2、CAS应用场景隐含竞争是短暂的,否则不断的自旋尝试会过度消耗CPU
3、CAS只能保证一个共享变量的原子操作,解决方法是使用锁或者合并多个变量
AbstractQueuedSynchronizer抽象队列同步器:AQS是JDK下提供的一套用于实现基于FIFO等待队列的阻塞锁和相关的同步器的一个同步框架。AQS维护了一个volatile int state(代表共享资源)和一个FIFO线程等待队列(多线程争用资源被阻塞时会进入此队列)抽象队列同步器中包括两个部分:临时资源和一个FIFO的CLH阻塞队列.
如果被请求的共享资源空闲,则将当前请求资源的线程设置为有效的工作线程,并将共享资
源设置为锁定状态,如果被请求的共享资源被占用,那么就需要一套线程阻塞等待以及被唤醒时锁分配的机制,这个机制AQS是用CLH队列锁实现的,即将暂时获取不到锁的线程加入到队列中。内部使用AQS的例子:以ReentrantLock为例,state初始化为0,表示未锁定状态。A线程lock()时,会调用tryAcquire()独占该锁并将state+1。此后其他线程再tryAcquire()时就会失败,直到A线程unlock()到state=0(即释放锁)为止,其它线程才有机会获取该锁。当然,释放锁之前,A线程自己是可以重复获取此锁的(state会累加),这就是可重入的概念。但要注意,获取多少次就要释放多么次,这样才能保证state是能回到零态的。

- public class ReentrantLock implements Lock, java.io.Serializable {
- private final Sync sync; //锁的具体实现,依赖于同步器实现锁机制
-
- //内部类:
- abstract static class Sync extends AbstractQueuedSynchronizer //同步器实际上就是AQS的一个子实现
-
- static final class NonfairSync extends Sync//非公平同步器的实现类
-
- static final class FairSync extends Sync// 公平同步器的实现类
Java5中引入了AutomicInteger、AutomicLong、AutomicReference等特殊的原子性变量类,它们提供的如compareAndSet、incrementAndSet和getAndIncrement等方法都使用了CAS操作。都是由硬件指令来保证的原子方法。
CAS即比较并交换。是解决多线程并行情况下使用锁造成性能损耗的一种机制,CAS操作包含三个操作数——内存位置V、预期原值A和新值B。如果内存位置的值与预期原值相匹配,那么处理器会自动将该位置值更新为新值。否则,处理器不做任何操作。无论哪种情况,它都会在CAS指令之前返回该位置的值。
比较和交换(Compare And Swap)是用于实现多线程同步的原子指令。 它将内存位置的内容与给定值进行比较,只有在相同的情况下,将该内存位置的内容修改为新的给定值。这是作为单个原子操作完成的。
原子性保证新值基于最新信息计算; 如果该值在同一时间被另一个线程更新,则写入将失败。操作结果必须说明是否进行替换; 这可以通过一个简单的布尔响应(这个变体通常称为比较和设置),或通过返回从内存位置读取的值来完成
所谓的原子量即操作变量的操作是原子的,该操作不可再分,因此是线程安全的
为何要使用原子变量呢,
原因是多个线程对单个变量操作也会引起一些问题。
在Java5之前,可以通过volatile、synchronized关键字来解决并发访问的安全问题,但这样太麻烦。
Java5后专门提供了用来进行单变量多线程并发安全访问的工具包
java.util.concurrent.atomic,其中的类也很简单。例如:
AtomicLong aLong=new AtomicLong(10000); //原子量,每个线程都可以自由操作
Java中的Unsafe类提供了类似C++手动管理内存的能力。Unsafe类对普通程序员来说是危险的,一般应用开发者不会用到这个类。getIntVolatile方法用于在对象指定偏移地址处volatile读取一个int。putIntVolatile方法用于在对象指定偏移地址处volatile写入一个int。
- //自定义计数器
- public class AtomicCounter {
- private static AtomicCounter instance = new AtomicCounter();
-
- private AtomicCounter() {
- }
-
- public static AtomicCounter getIntance() {
- return instance;
- }
-
- // 存储的数据,同时利用CAS提供线程安全的特性
- private AtomicInteger counter = new AtomicInteger();
-
- public int getCounter() {
- return counter.get();
- }
-
- public int increase() {
- // 内部使用死循环for(;;)调用compareAndSet(current, next)
- return counter.incrementAndGet();
- // return counter.getAndIncrement();
- }
-
- public int increase(int i) {
- // 内部使用死循环for(;;)调用compareAndSet(current, next)
- return counter.addAndGet(i);
- // return counter.getAndAdd(i);
- }
-
- public int decrease() {
- // 内部使用死循环调用compareAndSet(current, next)
- return counter.decrementAndGet();
- // return counter.getAndDecrement();
- }
-
- public int decrease(int i) {
- // 内部使用死循环调用compareAndSet(current, next)
- return counter.addAndGet(-i);
- // return counter.getAndAdd(-i);
- }
-
- public static void main(String[] args) throws Exception {
- final AtomicCounter ac = AtomicCounter.getIntance();
- // 可缓存线程池
- ExecutorService service = Executors.newCachedThreadPool();
- Set
set = new CopyOnWriteArraySet<>(); -
- for (int i = 0; i < 1000; i++) {
- service.execute(new Runnable() {
- public void run() {
- int rr=ac.increase();
- set.add(rr);
- System.out.println(Thread.currentThread() + "::" + rr);
- }
- });
- }
- Thread.currentThread().sleep(2000);
- System.out.println(set.size());
- service.shutdown();
- }
- }
解决方法:JAVA中提供了AtomicStampedReference/AtomicMarkableReference来处理会发生ABA问题的场景,主要是在对象中额外再增加一个标记来标识对象是否有过变更。
解决方法加入超时设置
AtomicReference提供了以无锁方式访问共享资源的能力
AtomicInteger、AtomicBoolean、AtomicLong、AtomicReference这些原子类型,它们无一例外都采用了基于volatile 关键字 +CAS 算法无锁的操作方式来确保共享数据在多线程操作下的线程安全性。
- volatile关键字保证了线程间的可见性,当某线程操作了被volatile关键字修饰的变量,其他线程可以立即看到该共享变量的变化。
- CAS算法,即对比交换算法,是由UNSAFE提供的,实质上是通过操作CPU指令来得到保证的。CAS算法提供了一种快速失败的方式,当某线程修改已经被改变的数据时会快速失败。
- 当CAS算法对共享数据操作失败时,因为有自旋算法的加持,对共享数据的更新终究会得到计算。
总之,原子类型用自旋+CAS的无锁操作保证了共享变量的线程安全性和原子性。