『壹』 快速學習jav的方法有哪些
作者:Yifen Hao
鏈接:https://www.hu.com/question/57483039/answer/153055031
來源:知乎
著作權歸作者所有。商業轉載請聯系作者獲得授權,非商業轉載請註明出處。
1. 做完的東西記得寫筆記。
比如在Spring中配置了redis集群,如果下次配置,還要去網上搜,效率低,不如把那部分代碼寫到筆記里。
今天在idea中用插件把代碼部署到docker里,也是一邊谷歌,一邊記筆記。
又比如一些常用的命令,總是會記不起來,用到的時候去搜谷歌,效率真的很低。不如記錄下來。
我自己用的notepad++記錄筆記,一直開著,需要的時候用快捷鍵直接切換到前台,從來不會卡,從來不擔心內容丟失。馬上就能記下來。記得時候不用太擔心格式,畢竟文字最重要。記錄了之後,後面整理好,用markdown格式寫到雲筆記里。
2.堅持學習新東西
程序員只有一種死法,土死的。
Spring是個好東西,但是配置太繁瑣了,如果自己想重新搞一個應用,或者做一些功能小demo,先要弄一大堆配置,等弄完配置,心都冷了。Spring Boot作為新技術,大大簡化了配置,啟動一個web應用都不用寫配置。我想驗證一個功能,點幾下滑鼠就能啟動了。
學習java8,jdk8引入了lambda表達式,大大減少了繁雜代碼,添加函數式方法,對集合的操作大大簡化。新的time包由joda time作者所寫,比之前的date,calender好用太多。
好用的東西還有guava庫。
3.學習其他語言和編程範式
python,kotlin,多了解一下編程範式。java寫多了,人真的變笨了。
4.多看優秀書籍
Java並發編程實戰,Effective Java,重構,演算法,HTTP權威指南等等
5.多看源碼
我覺得自己有個優點,也算是個缺點,我接觸到一個東西,我就特別想知道,它原理是什麼,怎麼實現的,忍不住點進去源碼看看。
前不久把java並發的工具類看了。從UNSAFE開始,到LockSupport和原子工具類,到AQS實現,然後基於AQS的Lock,CountdownLatch,Semaphore,然後是基於Lock的阻塞隊列實現等。
Java的容器代碼,我基本也看過很多。
之前也看了Zookeeper的源碼。
Spring的源碼結構也基本了解了。
『貳』 Lock的await/singal 和 Object的wait/notify 的區別
在使用Lock之前,我們都使用Object 的wait和notify實現同步的。舉例來說,一個procer和consumer,consumer發現沒有東西了,等待,proer生成東西了,喚醒。
線程consumer 線程procer
synchronize(obj){
obj.wait();//沒東西了,等待
} synchronize(obj){
obj.notify();//有東西了,喚醒
}
有了lock後,世道變了,現在是:
lock.lock();
condition.await();
lock.unlock(); lock.lock();
condition.signal();
lock.unlock();
為了突出區別,省略了若干細節。區別有三點:
1. lock不再用synchronize把同步代碼包裝起來;
2. 阻塞需要另外一個對象condition;
3. 同步和喚醒的對象是condition而不是lock,對應的方法是await和signal,而不是wait和notify。
為什麼需要使用condition呢?簡單一句話,lock更靈活。以前的方式只能有一個等待隊列,在實際應用時可能需要多個,比如讀和寫。為了這個靈活性,lock將同步互斥控制和等待隊列分離開來,互斥保證在某個時刻只有一個線程訪問臨界區(lock自己完成),等待隊列負責保存被阻塞的線程(condition完成)。
通過查看ReentrantLock的源代碼發現,condition其實是等待隊列的一個管理者,condition確保阻塞的對象按順序被喚醒。
在Lock的實現中,LockSupport被用來實現線程狀態的改變,後續將更進一步研究LockSupport的實現機制。
『叄』 java里是怎麼通過condition介面是獲取監視器方法的
ReentrantLock和condition是配合著使用的,就像wait和notify一樣,提供一種多線程間通信機制。
ReentrantLock 的lock方法有兩種實現:公平鎖與非公平鎖
看newCondition的源碼實現:
final ConditionObject newCondition() {
return new ConditionObject();}
其實就是只實例化一個個conditionObject對象綁定到lock罷了。也就是拿到了監視器,再深入到conditionObject這個裡面實現看看await方法:
public final void await() throws InterruptedException {
if (Thread.interrupted())
throw new InterruptedException(); Node node = addConditionWaiter(); int savedState = fullyRelease(node); int interruptMode = 0; while (!isOnSyncQueue(node)) {
LockSupport.park(this); if ((interruptMode = checkInterruptWhileWaiting(node)) != 0)
break; }
if (acquireQueued(node, savedState) && interruptMode != THROW_IE)
interruptMode = REINTERRUPT; if (node.nextWaiter != null) // clean up if cancelled unlinkCancelledWaiters(); if (interruptMode != 0)
reportInterruptAfterWait(interruptMode);}
大概就是將當前線程加入等待隊列,其中做一些邏輯判斷,再來看看喚醒的方法:singal和singalAll:
public final void signalAll() {
if (!isHeldExclusively())
throw new IllegalMonitorStateException();
Node first = firstWaiter;
if (first != null)
doSignalAll(first);
}
其實就是將等待隊列裡面的線程依次喚醒罷了,doSingalAll:
private void doSignalAll(Node first) {
lastWaiter = firstWaiter = null;
do {
Node next = first.nextWaiter;
first.nextWaiter = null;
transferForSignal(first);
first = next;
} while (first != null);
}
transferForSignal將線程轉移到syncQueue重新排隊,這里主要用到CAS(lock free)演算法改變狀態:
final boolean transferForSignal(Node node) {
/*
* If cannot change waitStatus, the node has been cancelled.
*/
if (!compareAndSetWaitStatus(node, Node.CONDITION, 0))
return false;
/*
* Splice onto queue and try to set waitStatus of predecessor to
* indicate that thread is (probably) waiting. If cancelled or
* attempt to set waitStatus fails, wake up to resync (in which
* case the waitStatus can be transiently and harmlessly wrong).
*/
Node p = enq(node);
int ws = p.waitStatus;
if (ws > 0 || !compareAndSetWaitStatus(p, ws, Node.SIGNAL))
LockSupport.unpark(node.thread);
return true;
}
篇幅有限,沒有詳細描述...反正多看看源碼吧,結合著實例分析
『肆』 Lock的await/singal 和 Object的wait/notify 的區別
Lock的await/singal 和 Object的wait/notify 的區別
在使用Lock之前,我們都使用Object 的wait和notify實現同步的。舉例來說,一個procer和consumer,consumer發現沒有東西了,等待,proer生成東西了,喚醒。
線程consumer 線程procer
synchronize(obj){
obj.wait();//沒東西了,等待
} synchronize(obj){
obj.notify();//有東西了,喚醒
}
有了lock後,世道變了,現在是:
lock.lock();
condition.await();
lock.unlock(); lock.lock();
condition.signal();
lock.unlock();
為了突出區別,省略了若干細節。區別有三點:
1. lock不再用synchronize把同步代碼包裝起來;
2. 阻塞需要另外一個對象condition;
3. 同步和喚醒的對象是condition而不是lock,對應的方法是await和signal,而不是wait和notify。
為
什麼需要使用condition呢?簡單一句話,lock更靈活。以前的方式只能有一個等待隊列,在實際應用時可能需要多個,比如讀和寫。為了這個靈活
性,lock將同步互斥控制和等待隊列分離開來,互斥保證在某個時刻只有一個線程訪問臨界區(lock自己完成),等待隊列負責保存被阻塞的線程
(condition完成)。
通過查看ReentrantLock的源代碼發現,condition其實是等待隊列的一個管理者,condition確保阻塞的對象按順序被喚醒。
在Lock的實現中,LockSupport被用來實現線程狀態的改變,後續將更進一步研究LockSupport的實現機制。
『伍』 java並發包源碼怎麼讀
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()
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不是一個高性能的容器。但是它是一個非常好的共享數據的容器。是典型的生產者和消費者的實現。
『陸』 線程池中空閑的線程處於什麼狀態
一:阻塞狀態,線程並沒有銷毀,也沒有得到CPU時間片執行;
源碼追蹤:
for (;;) {
...
workQueue.take();
...
}
public E take()...{
...
while (count.get() == 0) { / /這里就是任務隊列中的消息數量
notEmpty.await();
}
...
}
public final void await()...{
...
LockSupport.park(this);
...
}
繼續往下:
public static void park(Object blocker) {
Thread t = Thread.currentThread();
setBlocker(t, blocker);
U.park(false, 0L);
setBlocker(t, null);
}
private static final sun.misc.Unsafe U = sun.misc.Unsafe.getUnsafe();
//線程調用該方法,線程將一直阻塞直到超時,或者是中斷條件出現。
public native void park(boolean isAbsolute, long time);
上面就是java11線程池中阻塞的源碼追蹤;
二.對比object的wait()方法:
@FastNative
public final native void wait(long timeout, int nanos) throws InterruptedException;
還有Thread的sleep() 方法:
@FastNative
private static native void sleep(Object lock, long millis, int nanos)throws...;
可見,線程池中使用的阻塞方式並不是Object中的wait(),也不是Thread.sleep() ;
這3個方法最終實現都是通過c&c++實現的native方法.
三.在<<Java虛擬機(第二版)>>中,對線程狀態有以下介紹:
12.4.3狀態轉換
Java語言定義了5種線程狀態,在任意一個時間點,一個線程只能有且只有其中的一種
狀態,這5種狀態分別如下。
1)新建(New):創建後尚未啟動的線程處於這種狀態。
2)運行(Runable):Runable包括了操作系統線程狀態中的Running和Ready,也就是處於此
狀態的線程有可能正在執行,也有可能正在等待著CPU為它分配執行時間。
3)無限期等待(Waiting):處於這種狀態的線程不會被分配CPU執行時間,它們要等待被
其他線程顯式地喚醒。以下方法會讓線程陷入無限期的等待狀態:
●沒有設置Timeout參數的Object.wait()方法。
●沒有設置Timeout參數的Thread.join()方法。
●LockSupport.park()方法。
4)限期等待(Timed Waiting):處於這種狀態的線程也不會被分配CPU執行時間,不過無
須等待被其他線程顯式地喚醒,在一定時間之後它們會由系統自動喚醒。以下方法會讓線程
進入限期等待狀態:
●Thread.sleep()方法。
●設置了Timeout參數的Object.wait()方法。
●設置了Timeout參數的Thread.join()方法。
●LockSupport.parkNanos()方法。
●LockSupport.parkUntil()方法。
5)阻塞(Blocked):線程被阻塞了,「阻塞狀態」與「等待狀態」的區別是:「阻塞狀態」在等
待著獲取到一個排他鎖,這個事件將在另外一個線程放棄這個鎖的時候發生;而「等待狀
態」則是在等待一段時間,或者喚醒動作的發生。在程序等待進入同步區域的時候,線程將
進入這種狀態。
結束(Terminated):已終止線程的線程狀態,線程已經結束執行。