本篇主要講解如何去優化鎖機制
或者克服多線程由於鎖可致使性能降低的問題安全
有這樣一個場景,前面是一大桶水,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帶着三個變量,分別是:
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); } }
如圖:
出現了兩個線程的死鎖現象,因此說去鎖不只能提高性能,也能防止死鎖的產生
今天就先到這裏,你們能夠看看這些內容的拓展
記得點關注看更新,謝謝閱讀