concurrent源碼
1. 各種同步控制工具的使用
1.1 ReentrantLock
ReentrantLock感覺上是synchronized的增強版,synchronized的特點是使用簡單,一切交給JVM去處理,但是功能上是比較薄弱的。在JDK1.5之前,ReentrantLock的性能要好於synchronized,由於對JVM進行了優化,現在的JDK版本中,兩者性能是不相上下的。如果是簡單的實現,不要刻意去使用ReentrantLock。
相比於synchronized,ReentrantLock在功能上更加豐富,它具有可重入、可中斷、可限時、公平鎖等特點。
首先我們通過一個例子來說明ReentrantLock最初步的用法:
package test;
import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock = new ReentrantLock(); public static int i = 0;
@Override public void run() { for (int j = 0; j < 10000000; j++)
{ lock.lock(); try
{
i++;
} finally
{ lock.unlock();
}
}
}
public static void main(String[] args) throws InterruptedException {
Test test = new Test();
Thread t1 = new Thread(test);
Thread t2 = new Thread(test);
t1.start();
t2.start();
t1.join();
t2.join();
System.out.println(i);
}
}
有兩個線程都對i進行++操作,為了保證線程安全,使用了ReentrantLock,從用法上可以看出,與synchronized相比,ReentrantLock就稍微復雜一點。因為必須在finally中進行解鎖操作,如果不在finally解鎖,有可能代碼出現異常鎖沒被釋放,而synchronized是由JVM來釋放鎖。
那麼ReentrantLock到底有哪些優秀的特點呢?
1.1.1 可重入
單線程可以重復進入,但要重復退出
lock.lock();
lock.lock();try{
i++;
}
finally{
lock.unlock();
lock.unlock();
}
由於ReentrantLock是重入鎖,所以可以反復得到相同的一把鎖,它有一個與鎖相關的獲取計數器,如果擁有鎖的某個線程再次得到鎖,那麼獲取計數器就加1,然後鎖需要被釋放兩次才能獲得真正釋放(重入鎖)。這模仿了synchronized的語義;如果線程進入由線程已經擁有的監控器保護的 synchronized 塊,就允許線程繼續進行,當線程退出第二個(或者後續)synchronized塊的時候,不釋放鎖,只有線程退出它進入的監控器保護的第一個synchronized塊時,才釋放鎖。
public class Child extends Father implements Runnable{ final static Child child = new Child();//為了保證鎖唯一
public static void main(String[] args) { for (int i = 0; i < 50; i++) { new Thread(child).start();
}
}
public synchronized void doSomething() {
System.out.println("1child.doSomething()");
doAnotherThing(); // 調用自己類中其他的synchronized方法
}
private synchronized void doAnotherThing() { super.doSomething(); // 調用父類的synchronized方法
System.out.println("3child.doAnotherThing()");
}
@Override
public void run() {
child.doSomething();
}
}class Father { public synchronized void doSomething() {
System.out.println("2father.doSomething()");
}
}
我們可以看到一個線程進入不同的synchronized方法,是不會釋放之前得到的鎖的。所以輸出還是順序輸出。所以synchronized也是重入鎖
輸出:
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
1child.doSomething()
2father.doSomething()
3child.doAnotherThing()
...
1.1.2.可中斷
與synchronized不同的是,ReentrantLock對中斷是有響應的。中斷相關知識查看[高並發Java 二] 多線程基礎
普通的lock.lock()是不能響應中斷的,lock.lockInterruptibly()能夠響應中斷。
我們模擬出一個死鎖現場,然後用中斷來處理死鎖
package test;import java.lang.management.ManagementFactory;import java.lang.management.ThreadInfo;import java.lang.management.ThreadMXBean;import java.util.concurrent.locks.ReentrantLock;public class Test implements Runnable{ public static ReentrantLock lock1 = new ReentrantLock(); public static ReentrantLock lock2 = new ReentrantLock(); int lock; public Test(int lock)
{ this.lock = lock;
} @Override
public void run()
{ try
{ if (lock == 1)
{
lock1.lockInterruptibly(); try
{
Thread.sleep(500);
} catch (Exception e)
{ // TODO: handle exception
}
lock2.lockInterruptibly();
} else
{
lock2.lockInterruptibly(); try
{
Thread.sleep(500);
} catch (Exception e)
{ // TODO: handle exception
}
lock1.lockInterruptibly();
}
} catch (Exception e)
{ // TODO: handle exception
} finally
{ if (lock1.isHeldByCurrentThread())
{
lock1.unlock();
} if (lock2.isHeldByCurrentThread())
{
lock2.unlock();
}
System.out.println(Thread.currentThread().getId() + ":線程退出");
}
} public static void main(String[] args) throws InterruptedException {
Test t1 = new Test(1);
Test t2 = new Test(2);
Thread thread1 = new Thread(t1);
Thread thread2 = new Thread(t2);
thread1.start();
thread2.start();
Thread.sleep(1000); //DeadlockChecker.check();
} static class DeadlockChecker
{ private final static ThreadMXBean mbean = ManagementFactory
.getThreadMXBean(); final static Runnable deadlockChecker = new Runnable()
{ @Override
public void run()
{ // TODO Auto-generated method stub
while (true)
{ long[] deadlockedThreadIds = mbean.findDeadlockedThreads(); if (deadlockedThreadIds != null)
{
ThreadInfo[] threadInfos = mbean.getThreadInfo(deadlockedThreadIds); for (Thread t : Thread.getAllStackTraces().keySet())
{ for (int i = 0; i < threadInfos.length; i++)
{ if(t.getId() == threadInfos[i].getThreadId())
{
t.interrupt();
}
}
}
} try
{
Thread.sleep(5000);
} catch (Exception e)
{ // TODO: handle exception
}
}
}
};
public static void check()
{
Thread t = new Thread(deadlockChecker);
t.setDaemon(true);
t.start();
}
}
}
上述代碼有可能會發生死鎖,線程1得到lock1,線程2得到lock2,然後彼此又想獲得對方的鎖。
我們用jstack查看運行上述代碼後的情況
下面舉個例子:
package test;import java.util.concurrent.CyclicBarrier;public class Test implements Runnable{ private String soldier; private final CyclicBarrier cyclic; public Test(String soldier, CyclicBarrier cyclic)
{ this.soldier = soldier; this.cyclic = cyclic;
} @Override
public void run()
{ try
{ //等待所有士兵到齊
cyclic.await();
dowork(); //等待所有士兵完成工作
cyclic.await();
} catch (Exception e)
{ // TODO Auto-generated catch block
e.printStackTrace();
}
} private void dowork()
{ // TODO Auto-generated method stub
try
{
Thread.sleep(3000);
} catch (Exception e)
{ // TODO: handle exception
}
System.out.println(soldier + ": done");
} public static class BarrierRun implements Runnable
{ boolean flag; int n; public BarrierRun(boolean flag, int n)
{ super(); this.flag = flag; this.n = n;
} @Override
public void run()
{ if (flag)
{
System.out.println(n + "個任務完成");
} else
{
System.out.println(n + "個集合完成");
flag = true;
}
}
} public static void main(String[] args)
{ final int n = 10;
Thread[] threads = new Thread[n]; boolean flag = false;
CyclicBarrier barrier = new CyclicBarrier(n, new BarrierRun(flag, n));
System.out.println("集合"); for (int i = 0; i < n; i++)
{
System.out.println(i + "報道");
threads[i] = new Thread(new Test("士兵" + i, barrier));
threads[i].start();
}
}
}
列印結果:
集合
士兵5: done士兵7: done士兵8: done士兵3: done士兵4: done士兵1: done士兵6: done士兵2: done士兵0: done士兵9: done10個任務完成
1.7 LockSupport
提供線程阻塞原語
和suspend類似
LockSupport.park();
LockSupport.unpark(t1);
與suspend相比不容易引起線程凍結
LockSupport的思想呢,和Semaphore有點相似,內部有一個許可,park的時候拿掉這個許可,unpark的時候申請這個許可。所以如果unpark在park之前,是不會發生線程凍結的。
下面的代碼是[高並發Java 二] 多線程基礎中suspend示例代碼,在使用suspend時會發生死鎖。
而使用LockSupport則不會發生死鎖。
另外
park()能夠響應中斷,但不拋出異常。中斷響應的結果是,park()函數的返回,可以從Thread.interrupted()得到中斷標志。
在JDK當中有大量地方使用到了park,當然LockSupport的實現也是使用unsafe.park()來實現的。
public static void park() { unsafe.park(false, 0L);
}
1.8 ReentrantLock 的實現
下面來介紹下ReentrantLock的實現,ReentrantLock的實現主要由3部分組成:
CAS狀態
等待隊列
park()
- /**
- * The synchronization state.
- */
- private volatile int state;
- final void lock() { if (compareAndSetState(0, 1))
- setExclusiveOwnerThread(Thread.currentThread()); else
- acquire(1);
- }
- public final void acquire(int arg) { if (!tryAcquire(arg) &&
- acquireQueued(addWaiter(Node.EXCLUSIVE), arg))
- selfInterrupt();
- }
- private final boolean parkAndCheckInterrupt() {
- LockSupport.park(this); return Thread.interrupted();
- }
- public static Map m=Collections.synchronizedMap(new HashMap());
ReentrantLock的父類中會有一個state變數來表示同步的狀態
通過CAS操作來設置state來獲取鎖,如果設置成了1,則將鎖的持有者給當前線程
如果拿鎖不成功,則會做一個申請
首先,再去申請下試試看tryAcquire,因為此時可能另一個線程已經釋放了鎖。
如果還是沒有申請到鎖,就addWaiter,意思是把自己加到等待隊列中去
其間還會有多次嘗試去申請鎖,如果還是申請不到,就會被掛起
同理,如果在unlock操作中,就是釋放了鎖,然後unpark,這里就不具體講了。
2. 並發容器及典型源碼分析
2.1ConcurrentHashMap
我們知道HashMap不是一個線程安全的容器,最簡單的方式使HashMap變成線程安全就是使用Collections.synchronizedMap,它是對HashMap的一個包裝
同理對於List,Set也提供了相似方法。
但是這種方式只適合於並發量比較小的情況。
我們來看下synchronizedMap的實現
它會將HashMap包裝在裡面,然後將HashMap的每個操作都加上synchronized。
由於每個方法都是獲取同一把鎖(mutex),這就意味著,put和remove等操作是互斥的,大大減少了並發量。
下面來看下ConcurrentHashMap是如何實現的
在ConcurrentHashMap內部有一個Segment段,它將大的HashMap切分成若干個段(小的HashMap),然後讓數據在每一段上Hash,這樣多個線程在不同段上的Hash操作一定是線程安全的,所以只需要同步同一個段上的線程就可以了,這樣實現了鎖的分離,大大增加了並發量。
在使用ConcurrentHashMap.size時會比較麻煩,因為它要統計每個段的數據和,在這個時候,要把每一個段都加上鎖,然後再做數據統計。這個就是把鎖分離後的小小弊端,但是size方法應該是不會被高頻率調用的方法。
在實現上,不使用synchronized和lock.lock而是盡量使用trylock,同時在HashMap的實現上,也做了一點優化。這里就不提了。
2.2BlockingQueue
BlockingQueue不是一個高性能的容器。但是它是一個非常好的共享數據的容器。是典型的生產者和消費者的實現。
『貳』 java新手學習要多久
在編程的世界裡,Java一直是企業級別開發最喜歡選擇的編程語言,從java目前的就業形勢來看還是相當不錯的,由於手機安卓開發的需要,許多人也開始轉戰Java的學習,java技術更新的很快,而且在現在中國的培訓機構里java學員都是批量生產的,那麼競爭壓力也是很大的。所以許多人在學好java的基礎後,轉戰到火熱的安卓開發。成都IT培訓優就業小編今天來分析一下java各種大致學習路線。
首先總結一下做java最後大致是兩個方向:web企業級開發(JavaWeb
J2EE)和手機android開發。這兩個方向的先決條件是J2SE,J2SE並不是那麼簡單,只有當你逐漸學的深入了你才體會到java的精髓。
1.HTML+CSS+DIV,前端會用到,作為研發人員至少應該會簡單寫些頁面出來,這是許多編程語言都需要了解的基礎。
2.JAVASCRIPT,想要使用一些特殊的效果就需要這個了,但是你並不需要很精通,當你需要用到JS的地方可以用Jquery代理,Jquery很好學,很有用的JS框架,看似復雜華麗的效果他都能輕松完成。
3.Ajax,前端非同步交互技術,值得去研究
4.JSP,Servlet,Javabean,發展至今已經不提倡在JSP里嵌入JAVA代碼,所以僅僅是作為視圖層使用,嵌入些如JSTL和Struts2標簽庫的標簽成為動態頁面。Servlet是應該重點學習的,包括如過濾器、監聽器等,這些都是當下Struts2或者未來WEB框架的底層基礎和原理。
5.Strut2+Hibernate(or JPA)+Spring,當下十分流行的開發框架。其實是很容易上手的,但關鍵是需要懂得原理。
6.Web Service技術,它能使得運行在不同機器上的不同應用無須藉助附加的、專門的第三方軟體或硬體, 就可相互交換數據或集成。依據Web
Service規范實施的應用之間, 無論它們所使用的語言、 平台或內部協議是什麼, 都可以相互交換數據。
用一個月時間把《Java2參考大全》或者《Java核心技術1(基礎篇)》拿下來,然後再用1個月時間把《Java核心技術2(高級篇)》拿下來,最後再把《Java編程思想(第四版)》弄熟(理念性比較強,主要是將編程思想的),然後強迫自己去多做項目,慢慢累計自己的代碼量,這樣差不多1年時間,估計你就能掌握java編程了,也就代表你能上班了。
『叄』 並發編程解惑之線程
主要內容:
進程是資源分配的最小單位,每個進程都有獨立的代碼和數據空間,一個進程包含 1 到 n 個線程。線程是 CPU 調度的最小單位,每個線程有獨立的運行棧和程序計數器,線程切換開銷小。
Java 程序總是從主類的 main 方法開始執行,main 方法就是 Java 程序默認的主線程,而在 main 方法中再創建的線程就是其他線程。在 Java 中,每次程序啟動至少啟動 2 個線程。一個是 main 線程,一個是垃圾收集線程。每次使用 Java 命令啟動一個 Java 程序,就相當於啟動一個 JVM 實例,而每個 JVM 實例就是在操作系統中啟動的一個進程。
多線程可以通過繼承或實現介面的方式創建。
Thread 類是 JDK 中定義的用於控制線程對象的類,該類中封裝了線程執行體 run() 方法。需要強調的一點是,線程執行先後與創建順序無關。
通過 Runnable 方式創建線程相比通過繼承 Thread 類創建線程的優勢是避免了單繼承的局限性。若一個 boy 類繼承了 person 類,boy 類就無法通過繼承 Thread 類的方式來實現多線程。
使用 Runnable 介面創建線程的過程:先是創建對象實例 MyRunnable,然後將對象 My Runnable 作為 Thread 構造方法的入參,來構造出線程。對於 new Thread(Runnable target) 創建的使用同一入參目標對象的線程,可以共享該入參目標對象 MyRunnable 的成員變數和方法,但 run() 方法中的局部變數相互獨立,互不幹擾。
上面代碼是 new 了三個不同的 My Runnable 對象,如果只想使用同一個對象,可以只 new 一個 MyRunnable 對象給三個 new Thread 使用。
實現 Runnable 介面比繼承 Thread 類所具有的優勢:
線程有新建、可運行、阻塞、等待、定時等待、死亡 6 種狀態。一個具有生命的線程,總是處於這 6 種狀態之一。 每個線程可以獨立於其他線程運行,也可和其他線程協同運行。線程被創建後,調用 start() 方法啟動線程,該線程便從新建態進入就緒狀態。
NEW 狀態(新建狀態) 實例化一個線程之後,並且這個線程沒有開始執行,這個時候的狀態就是 NEW 狀態:
RUNNABLE 狀態(就緒狀敬春則態):
阻塞狀態有 3 種:
如果一個線程調用了一個對象的 wait 方法, 那麼這個線程就會處於等待狀態(waiting 狀態)直到另外一個線程調用這個對象的 notify 或者 notifyAll 方法後才會解除這個狀態。
run() 里的代碼執行完畢後,線程進入終結狀態(TERMINATED 狀態)。
線程狀態有 6 種:新建、可運行、阻塞、等待、定時等待、死亡。
我們看下 join 方法的使用:
運行結果:
我們來看下 yield 方法的使用:
運行結果:
線程與線程之間是無法直接通信的,A 線程無法直接通知 B 線程,Java 中線程之間交換信息是通過共享的內存來實現的,控制共享資源的讀寫的訪問,使得多個線程輪流執行對共享數據的操作,線程之間通信是通過對共享資源上鎖或釋放鎖來實現的。線程排隊輪流執行共享資源,這稱為線程的同步。
Java 提供了很多同森歷步操作(也就是線程間的通信方式),同步可使用 synchronized 關鍵字、Object 類的 wait/notifyAll 方法、ReentrantLock 鎖、無鎖同步 CAS 等方式來實現。
ReentrantLock 是 JDK 內置的一個鎖對象,用於線程同步(線程通信),需要用戶手動釋放鎖。
運行結果:
這表明同一時間段只能有 1 個線程執行 work 方法,因為 work 方法里的代碼需要獲取到鎖才能執行,這就實現了多個線程間的通信,線程 0 獲取鎖,先執行,線程 1 等待,線程 0 釋放鎖,線程 1 繼續執行。
synchronized 是一亮棚種語法級別的同步方式,稱為內置鎖。該鎖會在代碼執行完畢後由 JVM 釋放。
輸出結果跟 ReentrantLock 一樣。
Java 中的 Object 類默認是所有類的父類,該類擁有 wait、 notify、notifyAll 方法,其他對象會自動繼承 Object 類,可調用 Object 類的這些方法實現線程間的通信。
除了可以通過鎖的方式來實現通信,還可通過無鎖的方式來實現,無鎖同 CAS(Compare-and-Swap,比較和交換)的實現,需要有 3 個操作數:內存地址 V,舊的預期值 A,即將要更新的目標值 B,當且僅當內存地址 V 的值與預期值 A 相等時,將內存地址 V 的值修改為目標值 B,否則就什麼都不做。
我們通過計算器的案例來演示無鎖同步 CAS 的實現方式,非線程安全的計數方式如下:
線程安全的計數方式如下:
運行結果:
線程安全累加的結果才是正確的,非線程安全會出現少計算值的情況。JDK 1.5 開始,並發包里提供了原子操作的類,AtomicBoolean 用原子方式更新的 boolean 值,AtomicInteger 用原子方式更新 int 值,AtomicLong 用原子方式更新 long 值。 AtomicInteger 和 AtomicLong 還提供了用原子方式將當前值自增 1 或自減 1 的方法,在多線程程序中,諸如 ++i 或 i++ 等運算不具有原子性,是不安全的線程操作之一。 通常我們使用 synchronized 將該操作變成一個原子操作,但 JVM 為此種操作提供了原子操作的同步類 Atomic,使用 AtomicInteger 做自增運算的性能是 ReentantLock 的好幾倍。
上面我們都是使用底層的方式實現線程間的通信的,但在實際的開發中,我們應該盡量遠離底層結構,使用封裝好的 API,例如 J.U.C 包(java.util.concurrent,又稱並發包)下的工具類 CountDownLath、CyclicBarrier、Semaphore,來實現線程通信,協調線程執行。
CountDownLatch 能夠實現線程之間的等待,CountDownLatch 用於某一個線程等待若干個其他線程執行完任務之後,它才開始執行。
CountDownLatch 類只提供了一個構造器:
CountDownLatch 類中常用的 3 個方法:
運行結果:
CyclicBarrier 字面意思循環柵欄,通過它可以讓一組線程等待至某個狀態之後再全部同時執行。當所有等待線程都被釋放以後,CyclicBarrier 可以被重復使用,所以有循環之意。
相比 CountDownLatch,CyclicBarrier 可以被循環使用,而且如果遇到線程中斷等情況時,可以利用 reset() 方法,重置計數器,CyclicBarrier 會比 CountDownLatch 更加靈活。
CyclicBarrier 提供 2 個構造器:
上面的方法中,參數 parties 指讓多少個線程或者任務等待至 barrier 狀態;參數 barrierAction 為當這些線程都達到 barrier 狀態時會執行的內容。
CyclicBarrier 中最重要的方法 await 方法,它有 2 個重載版本。下面方法用來掛起當前線程,直至所有線程都到達 barrier 狀態再同時執行後續任務。
而下面的方法則是讓這些線程等待至一定的時間,如果還有線程沒有到達 barrier 狀態就直接讓到達 barrier 的線程執行任務。
運行結果:
CyclicBarrier 用於一組線程互相等待至某個狀態,然後這一組線程再同時執行,CountDownLatch 是不能重用的,而 CyclicBarrier 可以重用。
Semaphore 類是一個計數信號量,它可以設定一個閾值,多個線程競爭獲取許可信號,執行完任務後歸還,超過閾值後,線程申請許可信號時將會被阻塞。Semaphore 可以用來 構建對象池,資源池,比如資料庫連接池。
假如在伺服器上運行著若干個客戶端請求的線程。這些線程需要連接到同一資料庫,但任一時刻只能獲得一定數目的資料庫連接。要怎樣才能夠有效地將這些固定數目的資料庫連接分配給大量的線程呢?
給方法加同步鎖,保證同一時刻只能有一個線程去調用此方法,其他所有線程排隊等待,但若有 10 個資料庫連接,也只有一個能被使用,效率太低。另外一種方法,使用信號量,讓信號量許可與資料庫可用連接數為相同數量,10 個資料庫連接都能被使用,大大提高性能。
上面三個工具類是 J.U.C 包的核心類,J.U.C 包的全景圖就比較復雜了:
J.U.C 包(java.util.concurrent)中的高層類(Lock、同步器、阻塞隊列、Executor、並發容器)依賴基礎類(AQS、非阻塞數據結構、原子變數類),而基礎類是通過 CAS 和 volatile 來實現的。我們盡量使用頂層的類,避免使用基礎類 CAS 和 volatile 來協調線程的執行。J.U.C 包其他的內容,在其他的篇章會有相應的講解。
Future 是一種非同步執行的設計模式,類似 ajax 非同步請求,不需要同步等待返回結果,可繼續執行代碼。使 Runnable(無返回值不支持上報異常)或 Callable(有返回值支持上報異常)均可開啟線程執行任務。但是如果需要非同步獲取線程的返回結果,就需要通過 Future 來實現了。
Future 是位於 java.util.concurrent 包下的一個介面,Future 介面封裝了取消任務,獲取任務結果的方法。
在 Java 中,一般是通過繼承 Thread 類或者實現 Runnable 介面來創建多線程, Runnable 介面不能返回結果,JDK 1.5 之後,Java 提供了 Callable 介面來封裝子任務,Callable 介面可以獲取返回結果。我們使用線程池提交 Callable 介面任務,將返回 Future 介面添加進 ArrayList 數組,最後遍歷 FutureList,實現非同步獲取返回值。
運行結果:
上面就是非同步線程執行的調用過程,實際開發中用得更多的是使用現成的非同步框架來實現非同步編程,如 RxJava,有興趣的可以繼續去了解,通常非同步框架都是結合遠程 HTTP 調用 Retrofit 框架來使用的,兩者結合起來用,可以避免調用遠程介面時,花費過多的時間在等待介面返回上。
線程封閉是通過本地線程 ThreadLocal 來實現的,ThreadLocal 是線程局部變數(local vari able),它為每個線程都提供一個變數值的副本,每個線程對該變數副本的修改相互不影響。
在 JVM 虛擬機中,堆內存用於存儲共享的數據(實例對象),也就是主內存。Thread Local .set()、ThreadLocal.get() 方法直接在本地內存(工作內存)中寫和讀共享變數的副本,而不需要同步數據,不用像 synchronized 那樣保證數據可見性,修改主內存數據後還要同步更新到工作內存。
Myabatis、hibernate 是通過 threadlocal 來存儲 session 的,每一個線程都維護著一個 session,對線程獨享的資源操作很方便,也避免了線程阻塞。
ThreadLocal 類位於 Thread 線程類內部,我們分析下它的源碼:
ThreadLocal 和 Synchonized 都用於解決多線程並發訪問的問題,訪問多線程共享的資源時,Synchronized 同步機制採用了以時間換空間的方式,提供一份變數讓多個線程排隊訪問,而 ThreadLocal 採用了以空間換時間的方式,提供每個線程一個變數,實現數據隔離。
ThreadLocal 可用於資料庫連接 Connection 對象的隔離,使得每個請求線程都可以復用連接而又相互不影響。
在 Java 裡面,存在強引用、弱引用、軟引用、虛引用。我們主要來了解下強引用和弱引用:
上面 a、b 對實例 A、B 都是強引用
而上面這種情況就不一樣了,即使 b 被置為 null,但是 c 仍然持有對 C 對象實例的引用,而間接的保持著對 b 的強引用,所以 GC 不會回收分配給 b 的空間,導致 b 無法回收也沒有被使用,造成了內存泄漏。這時可以通過 c = null; 來使得 c 被回收,但也可以通過弱引用來達到同樣目的:
從源碼中可以看出 Entry 里的 key 對 ThreadLocal 實例是弱引用:
Entry 里的 key 對 ThreadLocal 實例是弱引用,將 key 值置為 null,堆中的 ThreadLocal 實例是可以被垃圾收集器(GC)回收的。但是 value 卻存在一條從 Current Thread 過來的強引用鏈,只有當當前線程 Current Thread 銷毀時,value 才能被回收。在 threadLocal 被設為 null 以及線程結束之前,Entry 的鍵值對都不會被回收,出現內存泄漏。為了避免泄漏,在 ThreadLocalMap 中的 set/get Entry 方法里,會對 key 為 null 的情況進行判斷,如果為 null 的話,就會對 value 置為 null。也可以通過 ThreadLocal 的 remove 方法(類似加鎖和解鎖,最後 remove 一下,解鎖對象的引用)直接清除,釋放內存空間。
總結來說,利用 ThreadLocal 來訪問共享數據時,JVM 通過設置 ThreadLocalMap 的 Key 為弱引用,來避免內存泄露,同時通過調用 remove、get、set 方法的時候,回收弱引用(Key 為 null 的 Entry)。當使用 static ThreadLocal 的時候(如上面的 Spring 多數據源),static 變數在類未載入的時候,它就已經載入,當線程結束的時候,static 變數不一定會被回收,比起普通成員變數使用的時候才載入,static 的生命周期變長了,若沒有及時回收,容易產生內存泄漏。
使用線程池,可以重用存在的線程,減少對象創建、消亡的開銷,可控制最大並發線程數,避免資源競爭過度,還能實現線程定時執行、單線程執行、固定線程數執行等功能。
Java 把線程的調用封裝成了一個 Executor 介面,Executor 介面中定義了一個 execute 方法,用來提交線程的執行。Executor 介面的子介面是 ExecutorService,負責管理線程的執行。通過 Executors 類的靜態方法可以初始化
ExecutorService 線程池。Executors 類的靜態方法可創建不同類型的線程池:
但是,不建議使用 Executors 去創建線程池,而是通過 ThreadPoolExecutor 的方式,明確給出線程池的參數去創建,規避資源耗盡的風險。
如果使用 Executors 去創建線程池:
最佳的實踐是通過 ThreadPoolExecutor 手動地去創建線程池,選取合適的隊列存儲任務,並指定線程池線程大小。通過線程池實現類 ThreadPoolExecutor 可構造出線程池的,構造函數有下面幾個重要的參數:
參數 1:corePoolSize
線程池核心線程數。
參數 2:workQueue
阻塞隊列,用於保存執行任務的線程,有 4 種阻塞隊列可選:
參數 3:maximunPoolSize
線程池最大線程數。如果阻塞隊列滿了(有界的阻塞隊列),來了一個新的任務,若線程池當前線程數小於最大線程數,則創建新的線程執行任務,否則交給飽和策略處理。如果是無界隊列就不存在這種情況,任務都在無界隊列里存儲著。
參數 4:RejectedExecutionHandler
拒絕策略,當隊列滿了,而且線程達到了最大線程數後,對新任務採取的處理策略。
有 4 種策略可選:
最後,還可以自定義處理策略。
參數 5:ThreadFactory
創建線程的工廠。
參數 6:keeyAliveTime
線程沒有任務執行時最多保持多久時間終止。當線程池中的線程數大於 corePoolSize 時,線程池中所有線程中的某一個線程的空閑時間若達到 keepAliveTime,則會終止,直到線程池中的線程數不超過 corePoolSize。但如果調用了 allowCoreThread TimeOut(boolean value) 方法,線程池中的線程數就算不超過 corePoolSize,keepAlive Time 參數也會起作用,直到線程池中的線程數量變為 0。
參數 7:TimeUnit
配合第 6 個參數使用,表示存活時間的時間單位最佳的實踐是通過 ThreadPoolExecutor 手動地去創建線程池,選取合適的隊列存儲任務,並指定線程池線程大小。
運行結果:
線程池創建線程時,會將線程封裝成工作線程 Worker,Worker 在執行完任務後,還會不斷的去獲取隊列里的任務來執行。Worker 的加鎖解鎖機制是繼承 AQS 實現的。
我們來看下 Worker 線程的運行過程:
總結來說,如果當前運行的線程數小於 corePoolSize 線程數,則獲取全局鎖,然後創建新的線程來執行任務如果運行的線程數大於等於 corePoolSize 線程數,則將任務加入阻塞隊列 BlockingQueue 如果阻塞隊列已滿,無法將任務加入 BlockingQueue,則獲取全局所,再創建新的線程來執行任務
如果新創建線程後使得線程數超過了 maximumPoolSize 線程數,則調用 Rejected ExecutionHandler.rejectedExecution() 方法根據對應的拒絕策略處理任務。
CPU 密集型任務,線程執行任務佔用 CPU 時間會比較長,應該配置相對少的線程數,避免過度爭搶資源,可配置 N 個 CPU+1 個線程的線程池;但 IO 密集型任務則由於需要等待 IO 操作,線程經常處於等待狀態,應該配置相對多的線程如 2*N 個 CPU 個線程,A 線程阻塞後,B 線程能馬上執行,線程多競爭激烈,能飽和的執行任務。線程提交 SQL 後等待資料庫返回結果時間較長的情況,CPU 空閑會較多,線程數應設置大些,讓更多線程爭取 CPU 的調度。
『肆』 實戰Java高並發程序設計讀後感10篇_讀後感_名著讀後感
《實戰Java高並發程序設計》是一本由葛一鳴 / 郭超著作,電子工業出版社出版的平裝圖書,本書定價:69.00元,頁數:352,文章吧我精心整理的一些讀者的讀後感,希望對大家能有幫助。
《實戰Java高並發程序設計》讀後感(一):是本入門書籍
這是一本Java並發基礎以及conCurrent包的類的簡介,雖然書名是實戰,但是例子挺多都是屬於helloWorld級別的,所以是比較適合入門。同時也會夾著一些對源碼和數據機構的分析,也會有Java8帶來一些新特性(比如函數式編程等)的講解,所以還可以吧。
還有一點就是這本書的圖會比較新穎和奇葩,例如下面這樣的:
有時候會覺得挺形象的,更多的時候是不想吐槽(有些字一下子還看不出來是什麼字o(╯□╰)o),還是希望用專業的畫圖軟體畫吧.
再去找一個並發的書籍看看加強~並發真的需要好好學學
《實戰Java高並發程序設計》讀後感(二):全面了解Java並發編程的好書
1.這本書的章節編排是比較清晰的,而且是由淺入深、由理論到實戰,閱讀的時候感覺特別流暢;
2.如果你翻過這本書,你一定會對書中的插圖印象「深刻」,很難想像現代出版的書里的插圖是這種質量;
3.關於Java並發的知識可以說是介紹得比較全面了,當前全面的話可能就沒法真正的深入,比如ConcurrentHashMap基本上並發編程中最常用最經典的設計,但是書上介紹的非常少;同樣的,Java 8中引入的CompletableFuture也是一個很重要的工具但是介咐頃紹的篇幅也非常有限;
4.對於Akka這一塊,說實話我讀了兩遍,還是沒有看得很懂,通過閱讀官方的文檔才比較清晰,感覺這一塊寫得有點混亂,條理不夠清晰;
5.最終我還是給這本書四星,因為讀後自己確實對並發這一塊有了比較完整的認識,讀後結合《深入理解Java虛擬機(第2版)》,從項目中的代碼找到並相關部分並予以改進,梳理並發重點的知識(顯式鎖控制、並發容器、並發流),收獲還是挺多的。
《實戰Java高並發程序設計》讀後感(三):Java並發編程和高並發解決方案視頻課程
Java並發編程和高並發解決方案視頻課程
網盤地址://pan./s/19tUBliZIYy2HQ0LiVfCw-A 密碼: d9fb
備用地址(騰訊微雲)://share.weiyun/5grRNnM 密碼:e324w9
學會高並發處理思路與手段,讓跳槽面試從容不迫,並發與高並發是面試的重要考察點,常問面試問題與答案都在這里了!
無論面試還是實際開發,幾乎都會涉及並發相關知識及高並發相關場景處理,如果你想系統的學習一下並發編程
並了解一下實際的高並發場景及應對方案,那這門課就是為你准備的。
第1章 課程准備衡好陸
第2章 並發基礎
第3章 項目准備
第4章 線程安全性
第5章 安全發布對象
第6章 線程安全策略
第7章 J.U.C之AQS
第8章 J.U.C組件拓展
第9章 線程調度-線程池
第10章 多線程並發拓展
第11章 高並發之擴容思路
第12章 高並發之緩存思路
第13章 高並發之消息隊列思路
第14章 高並發之應用拆分思路
第15章 高並發之應用限流思路
第16章 高並發之服務降級與服務熔斷思路
第17章 高並發之資料庫切庫分庫分表思路
第18章 高並發之高可用手段介紹
第19章 課程總結
《實戰Java高並發程序設計》讀後感(四):實戰Java高並發程序設計書評
說實話,在當當上找了很多有關java並發編程的書籍,最後也不知道是什麼原因買了這本書(好像是這本書有優惠。。。)買來之後看了,簡直後悔得不行!
這本書的作者是葛一鳴和郭超,出版社是電子工業出版社!
首先,這本書的作者的寫作態度值得懷疑,書中的配圖完全是手畫的,而且畫的質量實在不敢恭維,寫過的論文都應該知道,畫圖應該襪爛用Visio。實在納悶那麼多專業的圖,作者為何不用Visio或其他專業畫圖軟體,選擇用手畫是幾個意思,關鍵畫的質量還不咋地,歪七八糟的,看都看不清楚!作為一個專業的出版社,編輯也能審核通過,我是佩服出版社的編輯的審稿能力!
其次,關於java並發這塊,這本書連入門書籍都算不上,只能算是科普下,書中涉及到的並發知識,比如java內存模型,volatile,鎖等,作者都沒有深入原理的講,基本上都是簡單帶過,遠不如網路來的講得好,其中講volatile的一段:「和原子性問題一樣,我們只要簡單地使用volatile來聲明ready變數,告訴java虛擬機,這個變數可能會在不同的線程中修改,這樣就可以順利的解決這個問題了。」 不知道大家覺得怎麼樣,反正我看完想說臟話,作者你就是這么講解技術的嗎?用volatile來告訴虛擬機變數會在不同線程中修改??我只能說呵呵
還有一點,其他豆瓣網友也提到過,很多內容以及代碼都是從其他博客、文章過來,作者原創的有價值的東西,幾乎為零。
書中還有其他一些低級錯誤,就不一一指出了,最後,如果有幸能夠被作者看到此評論,真心希望作者好好反思下,請你為寫出的書負責,確保產出原創的有意義的內容,不是隨意幾段文字和代碼,整理下就能出書的,請你對得起買書的讀者!
『伍』 小白都看懂了,Python 中的線程和進程精講,建議收藏
目錄
眾所周知,CPU是計算機的核心,它承擔了所有的計算任務。而操作系統是計算機的管理者,是一個大管家,它負責任務的調度,資源的分配和管理,統領整個計算機硬體。應用程序是具有某種功能的程序,程序運行與操作系統之上
在很早的時候計算機並沒有線程這個概念,但是隨著時代的發展,只用進程來處理程序出現很多的不足。如當一個進程堵塞時,整個程序會停止在堵塞處,並且如果頻繁的切換進程,會浪費系統資源。所以線程出現了
線程是能擁有資源和獨立運行的最小單位,也是程序執行的最小單位。一個進程可以擁有多個線程,而且屬於同一個進程的多個線程間會共享該進行的資源
① 200 多本 Python 電子書(和經典的書籍)應該有
② Python標准庫資料(最全中文版)
③ 項目源碼(四五十個有趣且可靠的練手項目及源碼)
④ Python基礎入門、爬蟲、網路開發、大數據分析方面的視頻(適合小白學習)
⑤ Python學習路線圖(告別不入流的學習)
私信我01即可獲取大量Python學習資源
進程時一個具有一定功能的程序在一個數據集上的一次動態執行過程。進程由程序,數據集合和進程式控制制塊三部分組成。程序用於描述進程要完成的功能,是控制進程執行的指令集;數據集合是程序在執行時需要的數據和工作區;程序控制塊(PCB)包含程序的描述信息和控制信息,是進程存在的唯一標志
在Python中,通過兩個標准庫 thread 和 Threading 提供對線程的支持, threading 對 thread 進行了封裝。 threading 模塊中提供了 Thread , Lock , RLOCK , Condition 等組件
在Python中線程和進程的使用就是通過 Thread 這個類。這個類在我們的 thread 和 threading 模塊中。我們一般通過 threading 導入
默認情況下,只要在解釋器中,如果沒有報錯,則說明線程可用
守護模式:
現在我們程序代碼中,有多個線程, 並且在這個幾個線程中都會去 操作同一部分內容,那麼如何實現這些數據的共享呢?
這時,可以使用 threading庫裡面的鎖對象 Lock 去保護
Lock 對象的acquire方法 是申請鎖
每個線程在操作共享數據對象之前,都應該申請獲取操作權,也就是調用該共享數據對象對應的鎖對象的acquire方法,如果線程A 執行了 acquire() 方法,別的線程B 已經申請到了這個鎖, 並且還沒有釋放,那麼 線程A的代碼就在此處 等待 線程B 釋放鎖,不去執行後面的代碼。
直到線程B 執行了鎖的 release 方法釋放了這個鎖, 線程A 才可以獲取這個鎖,就可以執行下面的代碼了
如:
到在使用多線程時,如果數據出現和自己預期不符的問題,就可以考慮是否是共享的數據被調用覆蓋的問題
使用 threading 庫裡面的鎖對象 Lock 去保護
Python中的多進程是通過multiprocessing包來實現的,和多線程的threading.Thread差不多,它可以利用multiprocessing.Process對象來創建一個進程對象。這個進程對象的方法和線程對象的方法差不多也有start(), run(), join()等方法,其中有一個方法不同Thread線程對象中的守護線程方法是setDeamon,而Process進程對象的守護進程是通過設置daemon屬性來完成的
守護模式:
其使用方法和線程的那個 Lock 使用方法類似
Manager的作用是提供多進程共享的全局變數,Manager()方法會返回一個對象,該對象控制著一個服務進程,該進程中保存的對象運行其他進程使用代理進行操作
語法:
線程池的基類是 concurrent.futures 模塊中的 Executor , Executor 提供了兩個子類,即 ThreadPoolExecutor 和 ProcessPoolExecutor ,其中 ThreadPoolExecutor 用於創建線程池,而 ProcessPoolExecutor 用於創建進程池
如果使用線程池/進程池來管理並發編程,那麼只要將相應的 task 函數提交給線程池/進程池,剩下的事情就由線程池/進程池來搞定
Exectuor 提供了如下常用方法:
程序將 task 函數提交(submit)給線程池後,submit 方法會返回一個 Future 對象,Future 類主要用於獲取線程任務函數的返回值。由於線程任務會在新線程中以非同步方式執行,因此,線程執行的函數相當於一個「將來完成」的任務,所以 Python 使用 Future 來代表
Future 提供了如下方法:
使用線程池來執行線程任務的步驟如下:
最佳線程數目 = ((線程等待時間+線程CPU時間)/線程CPU時間 )* CPU數目
也可以低於 CPU 核心數
使用線程池來執行線程任務的步驟如下:
關於進程的開啟代碼一定要放在 if __name__ == '__main__': 代碼之下,不能放到函數中或其他地方
開啟進程的技巧
開啟進程的數量最好低於最大 CPU 核心數