多線程如何解決for循環效率的問題

多線程解決for循環效率問題

在for裡面,如果執行一次for裡面的內容所需時間比較長,可以使用線程池來提高for循環的效率

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主線程執行的。

采用先把要執行的“耗時”內容放到一個線程的執行主體(run方法)裡面,再用線程池執行該線程,可大大減少for循環的耗時。

但這種情況不適合for次數較大的情形,因為每循環一次,就開辟一個線程,開銷較大。

註意這種不叫高並發,隻是相當於原來由一個工人幹的活現在由多個工人協作完成一樣。

Java 多個線程交替循環執行

有些時候面試官經常會問,兩個線程怎麼交替執行呀,如果是三個線程,又怎麼交替執行呀,這種問題一般人還真不一定能回答上來。多線程這塊如果理解的不好,學起來是很吃力的,更別說面試瞭。

下面我們就來剖析一下怎麼實現多個線程順序輸出。

兩個線程循環交替打印

//首先我們來看一種比較簡單的方式
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) {
  //創建兩個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狀態
   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 所以此線程執行  執行完之後 喚醒現成2,由於此時count已經進行瞭++,所有while成立,c1進入等待狀態,其他兩個也一樣
      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的監視器方法,與Lock配合可以實現等待/通知模式,但是這兩者在使用方式以及功能特性上還是有差別的。Object和Condition接口的一些對比。摘自《Java並發編程的藝術》

Condition接口常用方法

condition可以通俗的理解為條件隊列。當一個線程在調用瞭await方法以後,直到線程等待的某個條件為真的時候才會被喚醒。這種方式為線程提供瞭更加簡單的等待/通知模式。Condition必須要配合鎖一起使用,因為對共享狀態變量的訪問發生在多線程環境下。一個Condition的實例必須與一個Lock綁定,因此Condition一般都是作為Lock的內部實現。

await() :造成當前線程在接到信號或被中斷之前一直處於等待狀態。

await(long time, TimeUnit unit) :造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處於等待狀態。

awaitNanos(long nanosTimeout) :造成當前線程在接到信號、被中斷或到達指定等待時間之前一直處於等待狀態。返回值表示剩餘時間,如果在nanosTimesout之前喚醒,那麼返回值 = nanosTimeout – 消耗時間,如果返回值 <= 0 ,則可以認定它已經超時瞭。

awaitUninterruptibly() :造成當前線程在接到信號之前一直處於等待狀態。【註意:該方法對中斷不敏感】。

awaitUntil(Date deadline) :造成當前線程在接到信號、被中斷或到達指定最後期限之前一直處於等待狀態。如果沒有到指定時間就被通知,則返回true,否則表示到瞭指定時間,返回返回false。

signal() :喚醒一個等待線程。該線程從等待方法返回前必須獲得與Condition相關的鎖。

signal()All :喚醒所有等待線程。能夠從等待方法返回的線程必須獲得與Condition相關的鎖。

Semaphore介紹

Semaphore 是 synchronized 的加強版,作用是控制線程的並發數量。就這一點而言,單純的synchronized 關鍵字是實現不瞭的。他可以保證某一個資源在一段區間內有多少給線程可以去訪問。

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

比如說通過這個構造方法可以創建一個是否公平的Semaphore類。

以上為個人經驗,希望能給大傢一個參考,也希望大傢多多支持WalkonNet。

推薦閱讀: