摘要:前言本篇主要講解如何去優化鎖機制或者克服多線程因為鎖可導致性能下降的問題線程變量有這樣一個場景,前面是一大桶水,個人去喝水,為了保證線程安全,我們要在杯子上加鎖導致大家輪著排隊喝水,因為加了鎖的杯子是同步的,只能有一個人拿著這個唯一的杯子喝
前言
本篇主要講解如何去優化鎖機制
或者克服多線程因為鎖可導致性能下降的問題
有這樣一個場景,前面是一大桶水,10個人去喝水,為了保證線程安全,我們要在杯子上加鎖
導致大家輪著排隊喝水,因為加了鎖的杯子是同步的,只能有一個人拿著這個唯一的杯子喝水
這樣子大家都喝完一杯水需要很長的時間
如果我們給每個人分發一個杯子呢?是不是每人喝到水的時間縮小到了十分之一
多線程并發也是一個道理
在每個Thread中都有自己的數據存放空間(ThreadLocalMap)
而ThreadLocal就是在當前線程的存放空間中存放數據
下面這個例子,在每個線程中存放一個arraylist,而不是大家去公用一個arraylist
public class ThreadLocalTest { public static ThreadLocalthreadLocal = new ThreadLocal (); 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回收
這時有一個值得考慮的問題
我們使用線程池來開發的時候,線程池中的線程并不會關閉,它只是處于空閑狀態
也就是說,我們如果把過大的數據存儲在當前線程的ThreadLocalMap中,線程不斷的調用,被空閑...
最后會導致內存溢出
解決方法是當不需要這些數據時
使用ThreadLocal.remove()方法將變量給移除
還有一種脫離鎖的機制,那就是CAS
CAS帶著三個變量,分別是:
V更新變量:需要返回的變量
E預期值:原來的值
N新值,傳進來的新變量
只有當預期值和新值相等時,才會把V=N,如果不相等,說明該操作會讓數據無法同步
根據上面的解釋,大概就能知道CAS其實也是在保護數據的同步性
當多個線程進行CAS操作時,可想只有一個線程能成功更新,之后其它線程的E和V會不地進行斷比較
所以CAS的同步鎖的實現是一樣的
CAS操作的并發包在Atomic包中,atomic實現了很多類型
不管是AtomicInteger還是AtomicReference,都有相同點,請觀察它們的源碼:
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); } }
如圖:
出現了兩個線程的死鎖現象,所以說去鎖不僅能提升性能,也能防止死鎖的產生
今天就先到這里,大家可以看看這些內容的拓展
記得點關注看更新,謝謝閱讀
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/70676.html
摘要:今天就先到這里,大家可以看看這些內容的拓展記得點關注看更新,謝謝閱讀 前言 這是一個長篇博客,希望大家關注我并且一起學習java高并發廢話不多說,直接開始 并行和并發 并行:多個線程同時處理多個任務并發:多個線程處理同個任務,不一定要同時 下面用圖來描述并行和并發的區別:(實現和虛線表示兩個不同的線程) showImg(https://segmentfault.com/img/bVYT...
摘要:可以用代替可以用代替定義的對象的值是不可變的今天就先到這里,大家可以看看這些內容的拓展記得點關注看更新,謝謝閱讀 前言 java高并發第二篇講的是java線程的基礎依舊不多說廢話 線程和進程 進程是操作系統運行的基礎,是一個程序運行的實體,windows上打開任務管理器就能看到進程線程是輕量級的進程,是程序執行的最小單位,是在進程這個容器下進行的 線程基本操作 新建一個線程類有兩種方式...
摘要:前言今天講的多線程的同步控制直接進入正題重入鎖重入鎖可以完全代替,它需要類來實現下面用一個簡單的例子來實現重入鎖以上代碼打印出來的是,可以說明也實現了線程同步它相比更加靈活,因為重入鎖實現了用戶自己加鎖,自己釋放鎖記得一定要釋放,不然其他線 前言 今天講的多線程的同步控制直接進入正題 ReentrantLock重入鎖 重入鎖可以完全代替synchronized,它需要java.util...
摘要:前言這篇主要來講解多線程中一個非常經典的設計模式包括它的基礎到拓展希望大家能夠有所收獲生產者消費者模式簡述此設計模式中主要分兩類線程生產者線程和消費者線程生產者提供數據和任務消費者處理數據和任務該模式的核心就是數據和任務的交互點共享內存緩 前言 這篇主要來講解多線程中一個非常經典的設計模式包括它的基礎到拓展希望大家能夠有所收獲 生產者-消費者模式簡述 此設計模式中主要分兩類線程:生產者...
閱讀 1967·2021-11-24 10:45
閱讀 1459·2021-11-18 13:15
閱讀 4542·2021-09-22 15:47
閱讀 3917·2021-09-09 11:36
閱讀 2012·2019-08-30 15:44
閱讀 3092·2019-08-29 13:05
閱讀 2502·2019-08-29 12:54
閱讀 1994·2019-08-26 13:47