java高併發之從零到放棄(四)

前言

本篇主要講解如何去優化鎖機制
或者克服多線程由於鎖可致使性能降低的問題安全

ThreadLocal線程變量

有這樣一個場景,前面是一大桶水,10我的去喝水,爲了保證線程安全,咱們要在杯子上加鎖
致使你們輪着排隊喝水,由於加了鎖的杯子是同步的,只能有一我的拿着這個惟一的杯子喝水
這樣子你們都喝完一杯水須要很長的時間
若是咱們給每一個人分發一個杯子呢?是否是每人喝到水的時間縮小到了十分之一多線程

多線程併發也是一個道理
在每一個Thread中都有本身的數據存放空間(ThreadLocalMap)
而ThreadLocal就是在當前線程的存放空間中存放數據
下面這個例子,在每一個線程中存放一個arraylist,而不是你們去公用一個arraylist併發

public class ThreadLocalTest {
    public static ThreadLocal<ArrayList> threadLocal = new ThreadLocal<ArrayList>();
    public static ArrayList list = new ArrayList();
    public static class Demo implements Runnable {
        private int i;

        public Demo(int i) {
            this.i = i;
        }
        @Override
        public void run() {
            list.add(i);
            threadLocal.set(list);
            System.out.println(threadLocal.get());
        }
    }
    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(5);
        for (int j = 0; j < 200; j++) {
            es.execute(new Demo(j));
        }
        Thread.sleep(3000);
        System.out.println(list.size());
        es.shutdown();
    }
}

在每一個線程內部有一塊存儲區域叫作ThreadLocalMap
能夠看到,ThreadLocal採用set,get存取值方式
只有線程徹底關閉時,在ThreadLocalMap中的數據纔會被GC回收ide

這時有一個值得考慮的問題
咱們使用線程池來開發的時候,線程池中的線程並不會關閉,它只是處於空閒狀態
也就是說,咱們若是把過大的數據存儲在當前線程的ThreadLocalMap中,線程不斷的調用,被空閒...
最後會致使內存溢出
解決方法是當不須要這些數據時
使用ThreadLocal.remove()方法將變量給移除性能

CAS操做

還有一種脫離鎖的機制,那就是CAS
CAS帶着三個變量,分別是:
V更新變量:須要返回的變量
E預期值:原來的值
N新值,傳進來的新變量優化

只有當預期值和新值相等時,纔會把V=N,若是不相等,說明該操做會讓數據沒法同步
根據上面的解釋,大概就能知道CAS其實也是在保護數據的同步性this

當多個線程進行CAS操做時,可想只有一個線程能成功更新,以後其它線程的E和V會不地進行斷比較
因此CAS的同步鎖的實現是同樣的atom

CAS操做的併發包在Atomic包中,atomic實現了不少類型
無論是AtomicInteger仍是AtomicReference,都有相同點,請觀察它們的源碼:spa

private volatile V value;
private static final long valueOffset;

以上是AtomicReferenc線程

private volatile int value;
private static final long valueOffset;

以上是AtomicIntege

都有value,這是它們的當前實際值
valueOffset保存的是value的偏移量

下面給出一個簡單的AtomicIntege例子:

public class AtomicTest {
    public static AtomicInteger atomicInteger = new AtomicInteger();
    //public static AtomicReference atomicReference = new AtomicReference();
    public static class Demo implements Runnable{

        @Override
        public void run() {
            for (int j=0;j<1000;j++){
                atomicInteger.incrementAndGet();        //當前值加1而且返回當前值
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        ExecutorService es = Executors.newFixedThreadPool(10);
        for (int i =0;i<10;i++){
            es.submit(new Demo());
        }
        Thread.sleep(5000);
        System.out.println(atomicInteger);
    }
}

你試着執行一下,若是打印出10000說明線程安全
使用CAS操做比同步鎖擁有更好的性能

咱們來看下incrementAndGet()的源碼:

public final int incrementAndGet() {
        return unsafe.getAndAddInt(this, valueOffset, 1) + 1;
    }

來看下getAndAddInt()源碼:

public final int getAndAddInt(Object var1, long var2, int var4) {
        int var5;
        do {
            var5 = this.getIntVolatile(var1, var2);
        } while(!this.compareAndSwapInt(var1, var2, var5, var5 + var4));

        return var5;
    }

這裏有一個循環,再細看源碼發現是native的,雖然看不到原生代碼,可是能夠看出它這裏作了一個CAS操做,不斷地進行多個變量的比較,只有預設值和新值相等時,才跳出循環
var5就是須要更新的變量,var1和var2是預設值和新值

死鎖

講了那麼多無鎖的操做,咱們來看一下一個死鎖的現象
兩個線程互相佔着對方想獲得的鎖,就會出現死鎖情況

public class DeadLock extends Thread{
    protected String suo;
    public static String zuo = new String();
    public static String you = new String();

    public DeadLock(String suo){
        this.suo=suo;
    }

    @Override
    public void run(){
        if (suo==zuo){
            synchronized (zuo){
                System.out.println("拿到了左,正在拿右......");
                synchronized (you){
                    System.out.println("拿到了右,成功了");
                }
            }
        }
        if (suo==you){
            synchronized (you){
                System.out.println("拿到了右,正在拿左......");
                synchronized (zuo){
                    System.out.println("拿到了zuo,成功了");
                }
            }
        }
    }

    public static void main(String[] args) throws InterruptedException {
        for (int i=0;i<10000;i++){
            DeadLock t1 = new DeadLock(zuo);
            DeadLock t2 = new DeadLock(you);
            t1.start();t2.start();
        }

        Thread.sleep(50000);
    }
}

如圖:

clipboard.png
出現了兩個線程的死鎖現象,因此說去鎖不只能提高性能,也能防止死鎖的產生

今天就先到這裏,你們能夠看看這些內容的拓展
記得點關注看更新,謝謝閱讀

相關文章
相關標籤/搜索