您好,登錄后才能下訂單哦!
本篇內(nèi)容介紹了“多線程for循環(huán)效率的問題怎么解決”的有關(guān)知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領(lǐng)大家學(xué)習(xí)一下如何處理這些情況吧!希望大家仔細(xì)閱讀,能夠?qū)W有所成!
在for里面,如果執(zhí)行一次for里面的內(nèi)容所需時間比較長,可以使用線程池來提高for循環(huán)的效率
public class TreadFor { private static final int loopNum = 1*10; public static void main(String args[]) throws InterruptedException { TreadFor TestThreadPool = new TreadFor(); long bt = System.currentTimeMillis(); List<String> list = new ArrayList<>(); list.add("0"); list.add("1"); list.add("2"); list.add("3"); list.add("4"); list.add("5"); list.add("6"); list.add("7"); list.add("8"); list.add("9"); TestThreadPool.m1(list); long et2 = System.currentTimeMillis(); System.out.println("[1]耗時:"+(et2 - bt)+ "ms"); Thread thread = new Thread(); long at = System.currentTimeMillis(); TestThreadPool.m2(); long et3 = System.currentTimeMillis(); System.out.println("[2]耗時:"+(et3 - at)+ "ms"); } public void m1( List<String> list) { ExecutorService pool = Executors.newCachedThreadPool(); for (int i = 0; i < list.size(); i++) { String str = list.get(i); System.out.println(list.get(i)); Runnable run = new Runnable() { public void run() { try { new Thread().sleep(1000); //模擬耗時操作 System.out.println("[1]" + Thread.currentThread().getName()+"----"+str); } catch (Exception e) { } } }; pool.execute(run); } System.out.println("[1] done!"); pool.shutdown(); } public void m2() { AtomicInteger connectionIds = new AtomicInteger(0); for (int index = 0; index < loopNum; index++) { try { new Thread().sleep(1000); //模擬耗時操作 System.out.println("[2]" + Thread.currentThread().getName()); } catch (Exception e) { e.printStackTrace(); } } System.out.println("[2] done!"); } }
其中遍歷list,給方法傳參,參數(shù)最終也可以進(jìn)入的線程里;
運行結(jié)果:
由打印結(jié)果可知:m1方法是用到了多線程的,多線程此時被線程池管理;而m2方法始終是main主線程執(zhí)行的。
采用先把要執(zhí)行的“耗時”內(nèi)容放到一個線程的執(zhí)行主體(run方法)里面,再用線程池執(zhí)行該線程,可大大減少for循環(huán)的耗時。
但這種情況不適合for次數(shù)較大的情形,因為每循環(huán)一次,就開辟一個線程,開銷較大。
注意這種不叫高并發(fā),只是相當(dāng)于原來由一個工人干的活現(xiàn)在由多個工人協(xié)作完成一樣。
有些時候面試官經(jīng)常會問,兩個線程怎么交替執(zhí)行呀,如果是三個線程,又怎么交替執(zhí)行呀,這種問題一般人還真不一定能回答上來。多線程這塊如果理解的不好,學(xué)起來是很吃力的,更別說面試了。
下面我們就來剖析一下怎么實現(xiàn)多個線程順序輸出。
//首先我們來看一種比較簡單的方式 public class ThreadCq { public static void main(String[] args) { Stack<Integer> stack = new Stack<>(); for(int i=1;i<100;i++) { stack.add(i); } Draw draw = new Draw(stack); new Thread(draw).start(); new Thread(draw).start(); } } class Draw implements Runnable{ private Stack<Integer> stack; public Draw(Stack<Integer> stack) { this.stack = stack; } @Override public void run() { while(!stack.isEmpty()) { synchronized (this) { notify(); System.out.println(Thread.currentThread().getName()+"---"+stack.pop()); try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } } }
這種方式是用Condition對象來完成的:
public class ThreadCq3 { //聲明一個鎖 static ReentrantLock lock = new ReentrantLock(); public static void main(String[] args) { //創(chuàng)建兩個Condition對象 Condition c1 = lock.newCondition(); Condition c2 = lock.newCondition(); Stack<Integer> stack = new Stack<>(); for (int i = 0; i <= 100; i++) { stack.add(i); } new Thread(() -> { try { Thread.sleep(500); } catch (InterruptedException e1) { e1.printStackTrace(); } while (true) { lock.lock(); // 打印偶數(shù) try { if (stack.peek() % 2 != 0) { c1.await(); } System.out.println(Thread.currentThread().getName() + "-----" + stack.pop()); c2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }).start(); new Thread(() -> { while (true) { try { Thread.sleep(500); } catch (InterruptedException e1) { e1.printStackTrace(); } lock.lock(); try { // 打印奇數(shù) if (stack.peek() % 2 != 1) { c2.await(); } System.out.println(Thread.currentThread().getName() + "-----" + stack.pop()); c1.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }).start(); } }
這種方式是通過Semaphore來實現(xiàn)的:
public class ThreadCq4 { //利用信號量來限制 private static Semaphore s1 = new Semaphore(1); private static Semaphore s2 = new Semaphore(1); public static void main(String[] args) { try { //首先調(diào)用s2為 acquire狀態(tài) s1.acquire(); // s2.acquire(); 調(diào)用s1或者s2先占有一個 } catch (InterruptedException e1) { e1.printStackTrace(); } new Thread(()->{ while(true) { try { s1.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("A"); s2.release(); } }).start(); new Thread(()->{ while(true) { try { s2.acquire(); } catch (InterruptedException e) { e.printStackTrace(); } try { Thread.sleep(500); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println("B"); s1.release(); } }).start(); } }
上面就是三種比較常用的,最常用的要屬第一種和第二種。
上面我們看了兩個線程依次輸出的實例,這里我們來看看三個線程如何做呢。
public class LockCond { private static int count = 0; private static Lock lock = new ReentrantLock(); public static void main(String[] args) { Condition c1 = lock.newCondition(); Condition c2 = lock.newCondition(); Condition c3 = lock.newCondition(); new Thread(()->{ while(true) { lock.lock(); try { while(count %3 != 0) { //剛開始count為0 0%3=0 所以此線程執(zhí)行 執(zhí)行完之后 喚醒現(xiàn)成2,由于此時count已經(jīng)進(jìn)行了++,所有while成立,c1進(jìn)入等待狀態(tài),其他兩個也一樣 c1.await(); } System.out.println(Thread.currentThread().getName()+"========:A"); count++; //喚醒線程2 c2.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }) .start(); new Thread(()->{ while(true) { lock.lock(); try { while(count %3 != 1) { c2.await(); } System.out.println(Thread.currentThread().getName()+"========:B"); count++; //喚醒線程3 c3.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }) .start(); new Thread(()->{ while(true) { lock.lock(); try { while(count %3 != 2) { c3.await(); } System.out.println(Thread.currentThread().getName()+"========:C"); count++; //喚醒線程1 c1.signal(); } catch (InterruptedException e) { e.printStackTrace(); } finally { lock.unlock(); } } }) .start(); } }
三個線程的也可以寫三種,這里寫一種就行了,寫法和上面兩個線程的都一樣。大家可以自己試一下。
我們在沒有學(xué)習(xí)Lock之前,使用的最多的同步方式應(yīng)該是synchronized關(guān)鍵字來實現(xiàn)同步方式了。配合Object的wait()、notify()系列方法可以實現(xiàn)等待/通知模式。Condition接口也提供了類似Object的監(jiān)視器方法,與Lock配合可以實現(xiàn)等待/通知模式,但是這兩者在使用方式以及功能特性上還是有差別的。Object和Condition接口的一些對比。摘自《Java并發(fā)編程的藝術(shù)》
Condition接口常用方法
condition可以通俗的理解為條件隊列。當(dāng)一個線程在調(diào)用了await方法以后,直到線程等待的某個條件為真的時候才會被喚醒。這種方式為線程提供了更加簡單的等待/通知模式。Condition必須要配合鎖一起使用,因為對共享狀態(tài)變量的訪問發(fā)生在多線程環(huán)境下。一個Condition的實例必須與一個Lock綁定,因此Condition一般都是作為Lock的內(nèi)部實現(xiàn)。
await()
:造成當(dāng)前線程在接到信號或被中斷之前一直處于等待狀態(tài)。
await(long time, TimeUnit unit)
:造成當(dāng)前線程在接到信號、被中斷或到達(dá)指定等待時間之前一直處于等待狀態(tài)。
awaitNanos(long nanosTimeout)
:造成當(dāng)前線程在接到信號、被中斷或到達(dá)指定等待時間之前一直處于等待狀態(tài)。返回值表示剩余時間,如果在nanosTimesout之前喚醒,那么返回值 = nanosTimeout - 消耗時間,如果返回值 <= 0 ,則可以認(rèn)定它已經(jīng)超時了。
awaitUninterruptibly()
:造成當(dāng)前線程在接到信號之前一直處于等待狀態(tài)?!咀⒁猓涸摲椒▽χ袛嗖幻舾小?。
awaitUntil(Date deadline)
:造成當(dāng)前線程在接到信號、被中斷或到達(dá)指定最后期限之前一直處于等待狀態(tài)。如果沒有到指定時間就被通知,則返回true,否則表示到了指定時間,返回返回false。
signal()
:喚醒一個等待線程。該線程從等待方法返回前必須獲得與Condition相關(guān)的鎖。
signal()All
:喚醒所有等待線程。能夠從等待方法返回的線程必須獲得與Condition相關(guān)的鎖。
Semaphore 是 synchronized 的加強版,作用是控制線程的并發(fā)數(shù)量。就這一點而言,單純的synchronized 關(guān)鍵字是實現(xiàn)不了的。他可以保證某一個資源在一段區(qū)間內(nèi)有多少給線程可以去訪問。
從源碼我們可以看出來,它new了一個靜態(tài)內(nèi)部類,繼承Sync接口。他同時也提供了一些構(gòu)造方法
比如說通過這個構(gòu)造方法可以創(chuàng)建一個是否公平的Semaphore類。
“多線程for循環(huán)效率的問題怎么解決”的內(nèi)容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業(yè)相關(guān)的知識可以關(guān)注億速云網(wǎng)站,小編將為大家輸出更多高質(zhì)量的實用文章!
免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點不代表本網(wǎng)站立場,如果涉及侵權(quán)請聯(lián)系站長郵箱:is@yisu.com進(jìn)行舉報,并提供相關(guān)證據(jù),一經(jīng)查實,將立刻刪除涉嫌侵權(quán)內(nèi)容。