多線程如何解決for循環(huán)效率的問題
多線程解決for循環(huán)效率問題
在for里面,如果執(zhí)行一次for里面的內容所需時間比較長,可以使用線程池來提高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,給方法傳參,參數最終也可以進入的線程里;
運行結果:

由打印結果可知:m1方法是用到了多線程的,多線程此時被線程池管理;而m2方法始終是main主線程執(zhí)行的。
采用先把要執(zhí)行的“耗時”內容放到一個線程的執(zhí)行主體(run方法)里面,再用線程池執(zhí)行該線程,可大大減少for循環(huán)的耗時。
但這種情況不適合for次數較大的情形,因為每循環(huán)一次,就開辟一個線程,開銷較大。
注意這種不叫高并發(fā),只是相當于原來由一個工人干的活現在由多個工人協作完成一樣。
Java 多個線程交替循環(huán)執(zhí)行
有些時候面試官經常會問,兩個線程怎么交替執(zhí)行呀,如果是三個線程,又怎么交替執(zhí)行呀,這種問題一般人還真不一定能回答上來。多線程這塊如果理解的不好,學起來是很吃力的,更別說面試了。
下面我們就來剖析一下怎么實現多個線程順序輸出。
兩個線程循環(huá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();
// 打印偶數
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 {
// 打印奇數
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來實現的:
public class ThreadCq4 {
//利用信號量來限制
private static Semaphore s1 = new Semaphore(1);
private static Semaphore s2 = new Semaphore(1);
public static void main(String[] args) {
try {
//首先調用s2為 acquire狀態(tài)
s1.acquire();
// s2.acquire(); 調用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í)行完之后 喚醒現成2,由于此時count已經進行了++,所有while成立,c1進入等待狀態(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();
}
}
三個線程的也可以寫三種,這里寫一種就行了,寫法和上面兩個線程的都一樣。大家可以自己試一下。
Condition介紹
我們在沒有學習Lock之前,使用的最多的同步方式應該是synchronized關鍵字來實現同步方式了。配合Object的wait()、notify()系列方法可以實現等待/通知模式。Condition接口也提供了類似Object的監(jiān)視器方法,與Lock配合可以實現等待/通知模式,但是這兩者在使用方式以及功能特性上還是有差別的。Object和Condition接口的一些對比。摘自《Java并發(fā)編程的藝術》

Condition接口常用方法
condition可以通俗的理解為條件隊列。當一個線程在調用了await方法以后,直到線程等待的某個條件為真的時候才會被喚醒。這種方式為線程提供了更加簡單的等待/通知模式。Condition必須要配合鎖一起使用,因為對共享狀態(tài)變量的訪問發(fā)生在多線程環(huán)境下。一個Condition的實例必須與一個Lock綁定,因此Condition一般都是作為Lock的內部實現。
await() :造成當前線程在接到信號或被中斷之前一直處于等待狀態(tài)。
await(long time, TimeUnit unit) :造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處于等待狀態(tài)。
awaitNanos(long nanosTimeout) :造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處于等待狀態(tài)。返回值表示剩余時間,如果在nanosTimesout之前喚醒,那么返回值 = nanosTimeout - 消耗時間,如果返回值 <= 0 ,則可以認定它已經超時了。
awaitUninterruptibly() :造成當前線程在接到信號之前一直處于等待狀態(tài)?!咀⒁猓涸摲椒▽χ袛嗖幻舾小俊?/p>
awaitUntil(Date deadline) :造成當前線程在接到信號、被中斷或到達指定最后期限之前一直處于等待狀態(tài)。如果沒有到指定時間就被通知,則返回true,否則表示到了指定時間,返回返回false。
signal() :喚醒一個等待線程。該線程從等待方法返回前必須獲得與Condition相關的鎖。
signal()All :喚醒所有等待線程。能夠從等待方法返回的線程必須獲得與Condition相關的鎖。
Semaphore介紹
Semaphore 是 synchronized 的加強版,作用是控制線程的并發(fā)數量。就這一點而言,單純的synchronized 關鍵字是實現不了的。他可以保證某一個資源在一段區(qū)間內有多少給線程可以去訪問。


從源碼我們可以看出來,它new了一個靜態(tài)內部類,繼承Sync接口。他同時也提供了一些構造方法

比如說通過這個構造方法可以創(chuàng)建一個是否公平的Semaphore類。
以上為個人經驗,希望能給大家一個參考,也希望大家多多支持腳本之家。
相關文章
springBoot啟動時讓方法自動執(zhí)行的幾種實現方式
這篇文章主要介紹了springBoot啟動時讓方法自動執(zhí)行的幾種實現方式,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友們下面隨著小編來一起學習學習吧2021-03-03
Java用單向環(huán)形鏈表來解決約瑟夫環(huán)Josepfu問題
如果把單鏈表的最后一個節(jié)點的指針指向鏈表頭部,而不是指向NULL,那么就構成了一個單向循環(huán)鏈表,通俗講就是把尾節(jié)點的下一跳指向頭結點2021-10-10

