摘要:線程需要避免竟態(tài),死鎖以及很多其他共享狀態(tài)的并發(fā)性問題。用戶線程在前臺,守護線程在后臺運行,為其他前臺線程提供服務。當所有前臺線程都退出時,守護線程就會退出。線程阻塞等待獲取某個對象鎖的訪問權限。
1、多線程介紹 多線程優(yōu)點
資源利用率好
程序設計簡單
服務器響應更快
多線程缺點設計更復雜
上下文切換的開銷
增加資源消耗
線程需要內存維護本地的堆棧,同時需要操作系統(tǒng)資源管理線程。
并發(fā)系統(tǒng)可以有多種并發(fā)模型,不同的并發(fā)模型在處理任務時,線程間的協(xié)作和交互的方式也不同。
并行工作者委托者將任務分配到不同的現(xiàn)場去執(zhí)行,每個工作者完成整個任務。工作者們并行運作在不同的線程上,甚至可能在不同的CPU上。如圖所示:
優(yōu)點:很容易理解和使用。
缺點:
共享狀態(tài)會很復雜
共享的工作者經(jīng)常需要訪問一些共享數(shù)據(jù),無論是內存中的或者共享的數(shù)據(jù)庫中的。
在并行工作者模型中,線程需要以某種方式存取共享數(shù)據(jù),以確保某個線程的修改能夠對其他線程可見。線程需要避免竟態(tài),死鎖以及很多其他共享狀態(tài)的并發(fā)性問題。
無狀態(tài)的工作者
共享狀態(tài)能夠被系統(tǒng)中得其他線程修改。所以工作者在每次需要的時候必須重讀狀態(tài),以確保每次都能訪問到最新的副本,不管共享狀態(tài)是保存在內存中的還是在外部數(shù)據(jù)庫中。工作者無法在內部保存這個狀態(tài)(但是每次需要的時候可以重讀)稱為無狀態(tài)的。
每次都重讀需要的數(shù)據(jù),將會導致速度變慢,特別是狀態(tài)保存在外部數(shù)據(jù)庫中的時候。
任務順序是不確定的
作業(yè)執(zhí)行順序是不確定的。無法保證哪個作業(yè)最先或者最后被執(zhí)行。
類似于工廠中生產(chǎn)線上的工人們那樣組織工作者。每個工作者只負責作業(yè)中的部分工作。當完成了自己的這部分工作時工作者會將作業(yè)轉發(fā)給下一個工作者。每個工作者在自己的線程中運行,并且不會和其他工作者共享狀態(tài)。有時也被成為無共享并行模型。
通常使用非阻塞的IO來設計使用流水線并發(fā)模型的系統(tǒng)。非阻塞IO就是,一旦某個工作者開始一個IO操作的時候(比如讀取文件或從網(wǎng)絡連接中讀取數(shù)據(jù)),這個工作者不會一直等待IO操作的結束。IO操作速度很慢,所以等待IO操作結束很浪費CPU時間。此時CPU可以做一些其他事情。當IO操作完成的時候,IO操作的結果(比如讀出的數(shù)據(jù)或者數(shù)據(jù)寫完的狀態(tài))被傳遞給下一個工作者。
在實際過程中,可能會是這樣:
也可能是這樣:
當然還會有更復雜的設計,……
缺點: 代碼編寫復雜,追蹤某個作業(yè)到底被什么代碼執(zhí)行難度較大。
優(yōu)點:
無需共享的狀態(tài)
工作者之間無需共享狀態(tài),無需考慮所有因并發(fā)訪問共享對象而產(chǎn)生的并發(fā)性問題,基本上是一個單線程的實現(xiàn)。
有狀態(tài)的工作者
當工作者知道了沒有其他線程可以修改它們的數(shù)據(jù),工作者可以變成有狀態(tài)的。對于有狀態(tài),是指,可以在內存中保存它們需要操作的數(shù)據(jù),只需在最后將更改寫回到外部存儲系統(tǒng)。因此,有狀態(tài)的工作者通常比無狀態(tài)的工作者具有更高的性能。
較好的硬件整合(Hardware Conformity)
當能確定代碼只在單線程模式下執(zhí)行的時候,通常能夠創(chuàng)建更優(yōu)化的數(shù)據(jù)結構和算法。單線程有狀態(tài)的工作者能夠在內存中緩存數(shù)據(jù),訪問緩存的數(shù)據(jù)變得更快。
合理的作業(yè)順序
基于流水線并發(fā)模型實現(xiàn)的并發(fā)系統(tǒng),在某種程度上是有可能保證作業(yè)的順序的。作業(yè)的有序性使得它更容易地推出系統(tǒng)在某個特定時間點的狀態(tài)。更進一步,你可以將所有到達的作業(yè)寫入到日志中去。一旦這個系統(tǒng)的某一部分掛掉了,該日志就可以用來重頭開始重建系統(tǒng)當時的狀態(tài)。按照特定的順序將作業(yè)寫入日志,并按這個順序作為有保障的作業(yè)順序。
Actors在Actor模型中每個工作者被稱為actor。Actor之間可以直接異步地發(fā)送和處理消息。Actor可以被用來實現(xiàn)一個或多個像前文描述的那樣的作業(yè)處理流水線。下圖給出了Actor模型:
工作者之間不直接進行通信。相反,它們在不同的通道中發(fā)布自己的消息(事件)。其他工作者們可以在這些通道上監(jiān)聽消息,發(fā)送者無需知道誰在監(jiān)聽。下圖給出了Channel模型:
channel模型對于來說似乎更加靈活。一個工作者無需知道誰在后面的流水線上處理作業(yè)。只需知道作業(yè)(或消息等)需要轉發(fā)給哪個通道。通道上的監(jiān)聽者可以隨意訂閱或者取消訂閱,并不會影響向這個通道發(fā)送消息的工作者。這使得工作者之間具有松散的耦合。
3、實現(xiàn)多線程方式多線程實現(xiàn)方法有兩種:
繼承Thread類
public class MyThread extends Thread { public void run(){ System.out.println("MyThread running"); } } //調用 MyThread myThread = new MyThread(); myTread.start();
實現(xiàn)Runnble接口
public class MyRunnable implements Runnable { public void run(){ System.out.println("MyRunnable running"); } } //調用 Thread thread = new Thread(new MyRunnable()); thread.start();
實現(xiàn)Runnble接口比Thread類的優(yōu)勢:
可以避免Java單繼承帶來的局限
增強程序健壯性,能夠被多個線程共享,代碼和數(shù)據(jù)是獨立的
適合多個相同程序代碼的線程區(qū)處理同一資源
Thread中,start和run的區(qū)別:run是在當前線程運行,start是開辟新的線程運行!所以一般情況下使用的是start!
執(zhí)行完run()方法后,或在run()方法中return,線程便自然消亡。
當一個線程運行時,另一個線程可以調用對應的 Thread 對象的 interrupt()方法來中斷它,該方法只是在目標線程中設置一個標志,表示它已經(jīng)被中斷,并立即返回。這里需要注意的是,如果只是單純的調用 interrupt()方法,線程并沒有實際被中斷,會繼續(xù)往下執(zhí)行。
sleep()方法的實現(xiàn)檢查到休眠線程被中斷,它會相當友好地終止線程,并拋出 InterruptedException 異常。
public class SleepInterrupt extends Object implements Runnable{ public void run(){ try{ System.out.println("in run() - about to sleep for 20 seconds"); Thread.sleep(20000); System.out.println("in run() - woke up"); }catch(InterruptedException e){ System.out.println("in run() - interrupted while sleeping"); //處理完中斷異常后,返回到run()方法人口, //如果沒有return,線程不會實際被中斷,它會繼續(xù)打印下面的信息 return; } System.out.println("in run() - leaving normally"); } public static void main(String[] args) { SleepInterrupt si = new SleepInterrupt(); Thread t = new Thread(si); t.start(); //主線程休眠2秒,從而確保剛才啟動的線程有機會執(zhí)行一段時間 try { Thread.sleep(2000); }catch(InterruptedException e){ e.printStackTrace(); } System.out.println("in main() - interrupting other thread"); //中斷線程t t.interrupt(); System.out.println("in main() - leaving"); } }
如果將 catch 塊中的 return 語句注釋掉,則線程在拋出異常后,會繼續(xù)往下執(zhí)行,而不會被中斷,從而會打印出leaving normally信息。
待決中斷另外一種情況,如果線程在調用 sleep()方法前被中斷,那么該中斷稱為待決中斷,它會在剛調用 sleep()方法時,立即拋出 InterruptedException 異常。
public class PendingInterrupt extends Object { public static void main(String[] args){ //如果輸入了參數(shù),則在mian線程中中斷當前線程(亦即main線程) if( args.length > 0 ){ Thread.currentThread().interrupt(); } //獲取當前時間 long startTime = System.currentTimeMillis(); try{ Thread.sleep(2000); System.out.println("was NOT interrupted"); }catch(InterruptedException x){ System.out.println("was interrupted"); } //計算中間代碼執(zhí)行的時間 System.out.println("elapsedTime=" + ( System.currentTimeMillis() - startTime)); } }
這種模式下,main 線程中斷它自身。除了將中斷標志(它是 Thread 的內部標志)設置為 true 外,沒有其他任何影響。線程被中斷了,但 main 線程仍然運行,main 線程繼續(xù)監(jiān)視實時時鐘,并進入 try 塊,一旦調用 sleep()方法,它就會注意到待決中斷的存在,并拋出 InterruptException。
中斷狀態(tài)判斷
isInterrupted()方法判斷是否中斷
Thread.interrupted()方法判斷中斷狀態(tài)
join & yieldjoin 方法用線程對象調用,如果在一個線程 A 中調用另一個線程 B 的 join 方法,線程 A 將會等待線程 B 執(zhí)行完畢后再執(zhí)行。
yield 可以直接用 Thread 類調用,yield 讓出 CPU 執(zhí)行權給同等級的線程,如果沒有相同級別的線程在等待 CPU 的執(zhí)行權,則該線程繼續(xù)執(zhí)行。
守護線程Java有兩類線程:UserThread(用戶線程)、Daemon Thread(守護線程)。
用戶線程在前臺,守護線程在后臺運行,為其他前臺線程提供服務。當所有前臺線程都退出時,守護線程就會退出。如果有前臺線程仍然存活,守護線程就不會退出。
守護線程并非只有虛擬機內部提供,用戶可以使用Thread.setDaemon(true)方法設置為當前線程為守護線程。
setDaemon(true)必須在調用的線程的start()方法之前設置,否則會拋出異常。
在守護線程中產(chǎn)生的新線程也是守護線程
線程阻塞4、線程安全線程在以下四種狀態(tài)下會產(chǎn)生阻塞:
執(zhí)行Thread.sleep()
當線程遇見wait()語句,它會一直阻塞到接到通知notify()
線程阻塞與不同的I/O的方式有多種。例:InputStream的read方法,一直阻塞到從流中讀取一個字節(jié)的數(shù)據(jù)為知。
線程阻塞等待獲取某個對象鎖的訪問權限。
競態(tài)條件 & 臨界區(qū)定義:當多個線程訪問某個類時,這個類始終都能表現(xiàn)出正確的行為,那么這個類就是線程安全的!
當兩個線程競爭同一資源時,如果對資源的訪問順序敏感,就稱存在競態(tài)條件。
導致競態(tài)條件發(fā)生的代碼區(qū)稱作:臨界區(qū)。
下例中add()方法就是一個臨界區(qū),它會產(chǎn)生競態(tài)條件。在臨界區(qū)中使用適當?shù)耐骄涂梢员苊飧倯B(tài)條件。
public class Counter { protected long count = 0; public void add(long value){ this.count = this.count + value; } }數(shù)據(jù)安全
線程逃逸規(guī)則:如果一個資源的創(chuàng)建,使用,銷毀都在同一個線程內完成,且永遠不會脫離該線程的控制,則該資源的使用就是線程安全的。
屬性 | 描述 | 是否線程安全 |
---|---|---|
局部變量 | 在棧中,不會被線程共享 | 線程安全 |
局部對象 | 引用所指的對象都存在共享堆中,對象不會被其它方法獲得,也不會被非局部變量引用到 | 線程安全 |
對象成員 | 多個線程執(zhí)行讀操作,或者每個線程的對象都相互獨立 | 線程安全 |
局部對象 | 對象會被其它方法獲得,或者被全局變量引用到 | 線程非安全 |
對象成員 | 存儲在堆上。若多個線程同時更新同一個對象的同一個成員 | 線程非安全 |
當多個線程同時訪問同一個資源,并且其中的一個或者多個線程對這個資源進行了寫操作,才會產(chǎn)生競態(tài)條件。多個線程同時讀同一個資源不會產(chǎn)生競態(tài)條件。
我們可以通過創(chuàng)建不可變的共享對象來保證對象在線程間共享時不會被修改,從而實現(xiàn)線程安全,如下所示:
public class ImmutableValue{ private int value = 0; public ImmutableValue(int value){ this.value = value; } public int getValue(){ return this.value; } }
如果非要對ImmutableValue進行操作的話,可以創(chuàng)建新的實例進行隔離:
public class ImmutableValue{ private int value = 0; public ImmutableValue(int value){ this.value = value; } public int getValue(){ return this.value; } //創(chuàng)建一個新的實例 public ImmutableValue add(int valueToAdd){ return new ImmutableValue(this.value + valueToAdd); } }
ImmutableValue可以看做是線程安全的,但是如果別的類引用了ImmutableValue,就不能保證線程安全了。如下所示:
public void Calculator{ private ImmutableValue currentValue = null; public ImmutableValue getValue(){ return currentValue; } public void setValue(ImmutableValue newValue){ this.currentValue = newValue; } public void add(int newValue){ this.currentValue = this.currentValue.add(newValue); } }
即使Calculator類內部使用了一個不可變對象,但Calculator類本身還是可變的,因此Calculator類不是線程安全的。換句話說:ImmutableValue類是線程安全的,但使用它的類不是。
5、同步(synchronized)當多個線程訪問某個狀態(tài)變量,并且有線程執(zhí)行寫入操作時,必須采用同步機制來協(xié)同這些線程對變量的訪問。
Java的主要同步機制有:
synchronized關鍵字
volatile類型變量
顯示鎖
原子變量
無論是同步方法,還是同步塊都是只針對同一個對象的多線程而言的,只有同一個對象產(chǎn)生的多線程,才會考慮到同步方法或者是同步塊。
實例方法Java實例方法同步是同步在對象上。這樣,每個方法同步都同步在方法所屬的實例。只有一個線程能夠在實例方法同步塊中運行。如果有多個實例存在,那么一個線程一次可以在一個實例同步塊中執(zhí)行操作。一個實例一個線程。
public synchronized void add(int value){ this.count += value; }靜態(tài)方法同步
靜態(tài)方法的同步是指同步在該方法所在的類對象上。因為在Java虛擬機中一個類只能對應一個類對象,所以同時只允許一個線程執(zhí)行同一個類中的靜態(tài)同步方法。
對于不同類中的靜態(tài)同步方法,一個線程可以執(zhí)行每個類中的靜態(tài)同步方法而無需等待。不管類中的那個靜態(tài)同步方法是否被調用,一個類只能由一個線程同時執(zhí)行。
public static synchronized void add(int value){ count += value; }實例方法中的同步塊
有時你不需要同步整個方法,而是同步方法中的一部分。
public void add(int value){ synchronized(this){ this.count += value; } }
示例使用Java同步塊構造器來標記一塊代碼是同步的。該代碼在執(zhí)行時和同步方法一樣。在上例中,使用了“this”,即為調用add方法的實例本身。在同步構造器中用括號括起來的對象叫做監(jiān)視器對象。
靜態(tài)方法中的同步塊和上面類似,下面是兩個靜態(tài)方法同步的例子。這些方法同步在該方法所屬的類對象上。
public class MyClass { public static synchronized void log1(String msg1, String msg2){ log.writeln(msg1); log.writeln(msg2); } public static void log2(String msg1, String msg2){ synchronized(MyClass.class){ log.writeln(msg1); log.writeln(msg2); } } }
這兩個方法不允許同時被線程訪問。
如果第二個同步塊不是同步在MyClass.class這個對象上。那么這兩個方法可以同時被線程訪問。
線程通信的目標是使線程間能夠互相發(fā)送信號。另一方面,線程通信使線程能夠等待其他線程的信號。
通過共享對象通信線程間發(fā)送信號的一個簡單方式是在共享對象的變量里設置信號值。
public class MySignal{ protected boolean hasDataToProcess = false; public synchronized boolean hasDataToProcess(){ return this.hasDataToProcess; } public synchronized void setHasDataToProcess(boolean hasData){ this.hasDataToProcess = hasData; } }
線程A在一個同步塊里設置boolean型成員變量hasDataToProcess為true,線程B也在同步塊里讀取hasDataToProcess這個成員變量。
線程A和B必須獲得指向一個MySignal共享實例的引用,以便進行通信。如果它們持有的引用指向不同的MySingal實例,那么彼此將不能檢測到對方的信號。
線程B運行在一個循環(huán)里,等待線程A的一個可執(zhí)行的信號。
protected MySignal sharedSignal = ... ... while(!sharedSignal.hasDataToProcess()){ //do nothing... busy waiting }wait(),notify()和notifyAll()
除非忙等待的時間特別短,否則會浪費CPU資源。合理的做法:讓等待線程進入睡眠或者非運行狀態(tài),直到它接收到它等待的信號。
java.lang.Object 類定義了三個方法,wait()、notify()和notifyAll()來實現(xiàn)這個等待機制。
一個線程一旦調用了任意對象的wait()方法,就會變?yōu)榉沁\行狀態(tài),直到另一個線程調用了同一個對象的notify()方法。
為了調用wait()或者notify(),線程必須先獲得那個對象的鎖。也就是說,線程必須在同步塊里調用wait()或者notify()。
在wait()/notify()機制中,不要使用全局對象,字符串常量等。應該使用對應唯一的對象
public class MonitorObject{ } public class MyWaitNotify{ MonitorObject myMonitorObject = new MonitorObject(); public void doWait(){ synchronized(myMonitorObject){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } } public void doNotify(){ synchronized(myMonitorObject){ myMonitorObject.notify(); } } }
不管是等待線程還是喚醒線程都在同步塊里調用wait()和notify()。這是強制性的!一個線程如果沒有持有對象鎖,將不能調用wait(),notify()或者notifyAll()。否則,會拋出IllegalMonitorStateException異常。
一旦線程調用了wait()方法,它就釋放了所持有的監(jiān)視器對象上的鎖。這將允許其他線程也可以調用wait()或者notify()。
被喚醒的線程必須重新獲得監(jiān)視器對象的鎖,才可以退出wait()的方法調用,因為wait方法調用運行在同步塊里面。如果多個線程被notifyAll()喚醒,那么在同一時刻將只有一個線程可以退出wait()方法,因為每個線程在退出wait()前必須獲得監(jiān)視器對象的鎖。
丟失信號notify()和notifyAll()方法不會保存調用它們的方法,如果方法被調用時,沒有線程處于等待狀態(tài)。通知信號過后便丟棄了。因此,如果一個線程先于被通知線程調用wait()前調用了notify(),等待的線程將錯過這個信號。在某些情況下,這可能使線程錯過了喚醒信號,永遠在等待不再醒來。
為了避免丟失信號,必須把它們保存在信號類里。在MyWaitNotify的例子中,通知信號應被存儲在MyWaitNotify實例的一個成員變量里。
public class MyWaitNotify2{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ if(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }
在上述例子中,doNotify()方法在調用notify()前把wasSignalled變量設為true。同時,留意doWait()方法在調用wait()前會檢查wasSignalled變量。
為了避免信號丟失,用一個變量來保存是否被通知過。在notify前,設置自己已經(jīng)被通知過。在wait后,設置自己沒有被通知過,需要等待通知。。
假喚醒線程有可能在沒有調用過notify()和notifyAll()的情況下醒來。這就是所謂的假喚醒(spurious wakeups)。等待線程即使沒有收到正確的信號,也能夠執(zhí)行后續(xù)的操作。
為了防止假喚醒,保存信號的成員變量將在一個while循環(huán)里接受檢查,而不是在if表達式里。這樣的一個while循環(huán)叫做自旋鎖。
public class MyWaitNotify3{ MonitorObject myMonitorObject = new MonitorObject(); boolean wasSignalled = false; public void doWait(){ synchronized(myMonitorObject){ while(!wasSignalled){ try{ myMonitorObject.wait(); } catch(InterruptedException e){...} } //clear signal and continue running. wasSignalled = false; } } public void doNotify(){ synchronized(myMonitorObject){ wasSignalled = true; myMonitorObject.notify(); } } }
如果等待線程沒有收到信號就喚醒,wasSignalled變量將變?yōu)閒alse,while循環(huán)會再執(zhí)行一次,促使醒來的線程回到等待狀態(tài)。
7、TheadLocal目前的JVM實現(xiàn)自旋會消耗CPU,如果長時間不調用doNotify方法,doWait方法會一直自旋,CPU會消耗太大。
ThreadLocal類創(chuàng)建的變量只被同一個線程進行讀和寫操作。因此,盡管有兩個線程同時執(zhí)行一段相同的代碼,而且這段代碼又有一個指向同一個ThreadLocal變量的引用,但是這兩個線程依然不能看到彼此的ThreadLocal變量域。
//創(chuàng)建一個ThreadLocal變量:每個線程僅需要實例化一次即可。 //每個線程只能看到私有的ThreadLocal實例,不同的線程在給ThreadLocal對象設置不同的值,也不能看到彼此的修改。 private ThreadLocal myThreadLocal = new ThreadLocal(); //設置、獲取數(shù)據(jù) myThreadLocal.set("A thread local value"); String threadLocalValue = (String) myThreadLocal.get(); //創(chuàng)建泛型對象 private ThreadLocal myThreadLocal1 = new ThreadLocal(); myThreadLocal1.set("Hello ThreadLocal"); String threadLocalValues = myThreadLocal.get();
InheritableThreadLocal類是ThreadLocal的子類。為了解決ThreadLocal實例內部每個線程都只能看到自己的私有值,所以InheritableThreadLocal允許一個線程創(chuàng)建的所有子線程訪問其父線程的值。
引用
1、并發(fā)編程網(wǎng)-Java并發(fā)性和多線程
2、蘭亭風雨專欄
文章版權歸作者所有,未經(jīng)允許請勿轉載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉載請注明本文地址:http://systransis.cn/yun/66891.html
摘要:目錄介紹問題匯總具體問題好消息博客筆記大匯總年月到至今,包括基礎及深入知識點,技術博客,學習筆記等等,還包括平時開發(fā)中遇到的匯總,當然也在工作之余收集了大量的面試題,長期更新維護并且修正,持續(xù)完善開源的文件是格式的同時也開源了生活博客,從年 目錄介紹 00.Java問題匯總 01.具體問題 好消息 博客筆記大匯總【16年3月到至今】,包括Java基礎及深入知識點,Android技...
摘要:我的學習筆記匯總標簽筆記分為兩大部分和筆記內容主要是對一些基礎特性和編程細節(jié)進行總結整理,適合了解基礎語法,想進一步深入學習的人如果覺得不錯,請給,這也是對我的鼓勵,有什么意見歡迎留言反饋目錄基礎鞏固筆記反射基礎鞏固筆記泛型基礎鞏 我的java&javaweb學習筆記(匯總) 標簽: java [TOC] 筆記分為兩大部分:javase和javaweb javase javawe...
摘要:對象頭的另外一部分是類型指針,即對象指向它的類元數(shù)據(jù)的指針,虛擬機通過這個指針來確定這個對象是哪個類的實例。并不是所有的虛擬機實現(xiàn)都必須在對象數(shù)據(jù)上保留類型指針,換句話說,查找對象的元數(shù)據(jù)信息并不一定要經(jīng)過對象本身,這點將在節(jié)討論。 目錄介紹 1.關于int和Integer的問題區(qū)別分析 2.Integer的值緩存的原理 2.1 Java 5 中引入緩存特性 2.2 Intege...
摘要:如問到是否使用某框架,實際是是問該框架的使用場景,有什么特點,和同類可框架對比一系列的問題。這兩個方向的區(qū)分點在于工作方向的側重點不同。 [TOC] 這是一份來自嗶哩嗶哩的Java面試Java面試 32個核心必考點完全解析(完) 課程預習 1.1 課程內容分為三個模塊 基礎模塊: 技術崗位與面試 計算機基礎 JVM原理 多線程 設計模式 數(shù)據(jù)結構與算法 應用模塊: 常用工具集 ...
摘要:分區(qū)函數(shù)返回一個布爾值,這意味著得到的分組的鍵類型是,于是它最多可以分為兩組是一組,是一組。當遍歷到流中第個元素時,這個函數(shù)執(zhí)行時會有兩個參數(shù)保存歸約結果的累加器已收集了流中的前個項目,還有第個元素本身。 一、收集器簡介 把列表中的交易按貨幣分組: Map transactionsByCurrencies = transactions.stream().collect(groupi...
摘要:前言是一個開源的壓力測試工具,常用于應用壓力測試,本文針使用其對接口進行并發(fā)性能測試,做筆記以備忘。 前言 Jmetter是一個開源的壓力測試工具,常用于Web應用壓力測試,本文針使用其對api接口進行并發(fā)性能測試,做筆記以備忘。 目錄 一、下載和安裝 1. Jmetter下載 2.下載并安...
閱讀 1664·2019-08-30 13:04
閱讀 2217·2019-08-30 12:59
閱讀 1777·2019-08-29 18:34
閱讀 1874·2019-08-29 17:31
閱讀 1266·2019-08-29 15:42
閱讀 3545·2019-08-29 15:37
閱讀 2866·2019-08-29 13:45
閱讀 2780·2019-08-26 13:57