多線程系列(二)之CAS、Lock 原

問題:

count++,當多線程執行時,是否是原子執行?答案是否定的。

當然,加上sychronized後就是原子操作了,但是隻是實現這麼簡單的功能至於用這麼重的鎖麼。。。

一、CAS(Compare And Swap):

Compare And Swap就是比較並且交換的一個原子操作,由cpu在指令級別上進行保證。

CAS 操作包含三個操作數:1、內存中的地址V,2、期望的值A,3、要修改的值B。

如果說V上的變量的值時A的話,就用B重新賦值,如果不是A,那就什麼事也不做,操作的返回結果原值是多少。

循環CAS:在一個(死)循環【for(;;)】裏不斷進行CAS操作,直到成功爲止(自旋操作)。

來段代碼:

package com.btx.service;

import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.atomic.AtomicInteger;

public class CounterTest {
    private AtomicInteger atomicInteger = new AtomicInteger();
    private Integer i = 0;

    static class My implements Runnable
    {
        private CountDownLatch countDownLatch;

        private CounterTest counterTest;

        public My(CountDownLatch countDownLatch, CounterTest counterTest)
        {
            this.countDownLatch = countDownLatch;
            this.counterTest = counterTest;
        }
        public void run()
        {
            countDownLatch.countDown();
            for(int i=0; i<100;i++)
            {
                counterTest.count();
                counterTest.casCount();
            }
        }
    }

    private void count()
    {
        this.i++;
    }

    private void casCount()
    {
       // this.atomicInteger.incrementAndGet();
        for(;;)
        {
            int i = this.atomicInteger.get();
            boolean f = this.atomicInteger.compareAndSet(i,++i);

            if(f)
                break;
        }

    }
    public static void main(String[] args) throws InterruptedException {
        CounterTest counterTest = new CounterTest();
        CountDownLatch countDownLatch = new CountDownLatch(100);
        for(int i=0; i<100; i++)
        {
            new Thread(new My(countDownLatch,counterTest)).start();
        }

        countDownLatch.await();
        System.out.println("非原子操作:"+ counterTest.i);
        System.out.println("原子操作:"+ counterTest.atomicInteger.get());
    }
}

解釋下:

啓用100個線程,每個線程累加一100次。我們期望的結果值應該是100*100=10000。

結果:

結論證明:

count++確實不是原子操作,並且CAS確實可以實現原子性。

CAS實現原子操作的三個問題:

1、ABA問題:

時間點1 :線程1查詢值是否爲A 
時間點2 :線程2查詢值是否爲A 
時間點3 :線程2比較並更新值爲B 
時間點4 :線程2查詢值是否爲B 
時間點5 :線程2比較並更新值爲A 
時間點6 :線程1比較並更新值爲C

線程2是原子的,但是線程2對變量的修改對線程1來說是透明的。但是線程1卻將變量修改成功了,所以線程2就不是原子操作了。

2、循環時間很長的話,cpu的負荷比較大

3、對一個變量進行操作可以,同時操作多個共享變量有點麻煩

CAS的線程安全:

通過硬件層面的阻塞實現原子操作的安全

原子更新基本類型類

AtomicBoolean,AtomicInteger,AtomicLong,AtomicReference

AtomicInteger的常用方法:

  1. int addAndGet(int delta):
  2. boolean compareAndSet(int expect,int update):
  3. int getAndIncrement(): 原子遞增,但是返回的是自增以前的值
  4. incrementAndGet原子遞增,但是返回的是自增以後的值
  5. int getAndSet(int newValue)

原子更新數組類

AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray

AtomicIntegerArray類主要是提供原子的方式更新數組裏的整型,其常用方法如下:

  1. int addAndGet(int i,int delta):
  2. boolean compareAndSet(int i,int expect,int update):

數組通過構造方法傳入,類會將數組複製一份,原數組不會發生變化

原子更新引用類型提供的類

  1. AtomicReference: 可以解決更新多個變量的問題
  2. AtomicStampedReference:解決ABA問題
  3. AtomicMarkableReference:解決ABA問題

原子更新字段類

Atomic包提供了以下3個類進行原子字段更新。

  1. AtomicReferenceFieldUpdater:
  2. AtomicIntegerFieldUpdater:
  3. AtomicLongFieldUpdater:

二、Lock

有了synchronized爲什麼還要Lock?

1、 嘗試非阻塞地獲取鎖

2、 獲取鎖的過程可以被中斷

3、 超時獲取鎖

Lock的標準用法

public static void main(String[] args) {
        Lock lock = new ReentrantLock();
        lock.lock();
        try{
            // do my work.....
        }finally{
            lock.unlock();
        }
    }

Lock的常用API

  1. lock()
  2. lockInterruptibly:可在獲取鎖的時候中斷
  3. tryLock:嘗試非阻塞地獲取鎖,如果沒有獲取鎖,就立即返回false
  4. unlock()

可重入鎖

遞歸的時候發生鎖的重入,如果不是可重入鎖,就會死鎖

公平鎖和非公平鎖

公平鎖,先對鎖發出獲取請求的一定先被滿足。公平鎖的效率比非公平鎖效率要低。

Lock接口的實現:

ReentrantLock、ReentrantReadWriteLock

Condition接口有何用處?

condition接口和Lock配合來實現等待通知機制

Condition接口的使用規範:

public class ConditionTemplete {

    Lock lock = new ReentrantLock();
    Condition condition = lock.newCondition();

    public void waitc() throws InterruptedException {
        lock.lock();
        try{
            condition.await();
        }finally{
            lock.unlock();
        }
    }

    public void waitnotify() throws InterruptedException {
        lock.lock();
        try{
            condition.signal();
            //condition.signalAll();儘量少使用
        }finally{
            lock.unlock();
        }
    }


}

 

ReentrantLock:

獨佔鎖,只能有一個線程獲取鎖

Talk is cheap. Show me the code

結合ReentrantLock和Condition實現線程安全的有界隊列

public class BlockingQueueLC<T> {
    private List queue = new LinkedList<>();
    private final int limit;
    Lock lock = new ReentrantLock();
    private Condition needNotEmpty = lock.newCondition();
    private Condition needNotFull = lock.newCondition();


    public BlockingQueueLC(int limit) {
        this.limit = limit;
    }

    public void enqueue(T item) throws InterruptedException {
        lock.lock();
        try{
            while(this.queue.size()==this.limit){
                needNotFull.await();
            }
            this.queue.add(item);
            needNotEmpty.signal();
        }finally{
            lock.unlock();
        }
    }

    public  T dequeue() throws InterruptedException {
        lock.lock();
        try{
            while(this.queue.size()==0){
                needNotEmpty.await();
            }
            T result = (T) this.queue.remove(0);
            needNotFull.signal();
            return result;
        }finally{
            lock.unlock();
        }
    }
}

 

ReentrantReadWriteLock:

允許多個讀線程同時進行,但是隻允許一個寫線程(不允許其他讀線程和寫線程),支持讀多寫少場景,性能會有提升。

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章