問題:
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的常用方法:
- int addAndGet(int delta):
- boolean compareAndSet(int expect,int update):
- int getAndIncrement(): 原子遞增,但是返回的是自增以前的值
- incrementAndGet原子遞增,但是返回的是自增以後的值
- int getAndSet(int newValue)
原子更新數組類
AtomicIntegerArray,AtomicLongArray,AtomicReferenceArray
AtomicIntegerArray類主要是提供原子的方式更新數組裏的整型,其常用方法如下:
- int addAndGet(int i,int delta):
- boolean compareAndSet(int i,int expect,int update):
數組通過構造方法傳入,類會將數組複製一份,原數組不會發生變化
原子更新引用類型提供的類
- AtomicReference: 可以解決更新多個變量的問題
- AtomicStampedReference:解決ABA問題
- AtomicMarkableReference:解決ABA問題
原子更新字段類
Atomic包提供了以下3個類進行原子字段更新。
- AtomicReferenceFieldUpdater:
- AtomicIntegerFieldUpdater:
- 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
- lock()
- lockInterruptibly:可在獲取鎖的時候中斷
- tryLock:嘗試非阻塞地獲取鎖,如果沒有獲取鎖,就立即返回false
- 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:
允許多個讀線程同時進行,但是隻允許一個寫線程(不允許其他讀線程和寫線程),支持讀多寫少場景,性能會有提升。