java.lang包下有二個非常有用的東西:Runnable接口與Thread類,Thread實現了Runnable接口(可以認為Thread是Runnable的子類),利用它們可以實現最基本的多線程開發。
一、Runnable入門示例

1 public class RunnableDemo1 { 2 3 public static void main(String[] args) { 4 new Runnable() { 5 public void run() { 6 for (int i = 0; i < 5; i++) { 7 try { 8 Thread.sleep(100); 9 } catch (InterruptedException e) { 10 e.printStackTrace(); 11 } 12 System.out.println("r1 -> i=" + i); 13 } 14 15 } 16 }.run(); 17 18 new Runnable() { 19 public void run() { 20 for (int i = 0; i < 5; i++) { 21 try { 22 Thread.sleep(100); 23 } catch (InterruptedException e) { 24 e.printStackTrace(); 25 } 26 System.out.println("r2 -> i=" + i); 27 } 28 } 29 }.run(); 30 31 } 32 33 }
代碼很簡單,每個線程依次輸出0-4這5個數字,運行結果:
r1 -> i=0
r1 -> i=1
r1 -> i=2
r1 -> i=3
r1 -> i=4
r2 -> i=0
r2 -> i=1
r2 -> i=2
r2 -> i=3
r2 -> i=4
二、向Runnable傳遞參數
實際應用中,線程開始處理前,通常會有一些初始參數,如果要傳入參數,可以參考下面的方法,先定義一個Runnable的子類

1 package com.cnblogs.yjmyzz; 2 3 public class MyRunnable implements Runnable{ 4 5 private String name; 6 private int max; 7 8 public MyRunnable(String name,int max){ 9 this.name = name; 10 this.max = max; 11 } 12 13 public void run() { 14 for (int i = 1; i <= max; i++) { 15 try { 16 Thread.sleep(5); 17 System.out.println(name + ".i=" + i); 18 } catch (InterruptedException e) { 19 e.printStackTrace(); 20 } 21 } 22 } 23 24 }
然后這樣使用:

1 package com.cnblogs.yjmyzz; 2 3 public class RunnableDemo2 { 4 5 public static void main(String[] args) { 6 7 new MyRunnable("A", 5).run(); 8 9 new MyRunnable("B", 5).run(); 10 } 11 12 }
運行結果:
A.i=1
A.i=2
A.i=3
A.i=4
A.i=5
B.i=1
B.i=2
B.i=3
B.i=4
B.i=5
三、利用Thread並行處理
剛才的二個例子,相當大家也發現了問題,雖然是有二個線程,但是始終是按順序執行的,上一個線程處理完成前,下一個線程無法開始,這其實跟同步處理沒啥二樣,可以通過Thread類改變這種局面:

1 public class RunnableDemo3 { 2 3 public static void main(String[] args) { 4 5 Runnable r1 = new MyRunnable("A", 5); 6 Runnable r2 = new MyRunnable("B", 5); 7 8 Thread t1 = new Thread(r1); 9 Thread t2 = new Thread(r2); 10 11 t1.start(); 12 t2.start(); 13 14 } 15 16 }
Thread通過start方法,可以讓多個線程並行處理,運行結果如下:
B.i=1
A.i=1
B.i=2
A.i=2
B.i=3
A.i=3
B.i=4
A.i=4
B.i=5
A.i=5
從輸出結果上看,二個線程已經在並行處理了。
四、通過在線搶購示例理解資源共享
雙十一剛過,每到這個時候,通常是狼多肉少,下面的OrderRunnable類模擬這種搶購情況,假設產品數只有10個,搶購的客戶卻有100個

1 package com.cnblogs.yjmyzz; 2 3 public class OrderRunnable implements Runnable{ 4 5 String taskName; 6 7 public OrderRunnable(String taskName){ 8 this.taskName=taskName; 9 } 10 11 private int productNum = 10; 12 13 private int customerNum = 100; 14 15 public void run() { 16 17 for (int i = 0; i < customerNum; i++) { 18 if (productNum > 0) { 19 try { 20 Thread.sleep(50); 21 } catch (InterruptedException e) { 22 e.printStackTrace(); 23 } 24 System.out.println(taskName + " -> order success!"); 25 productNum -= 1; 26 } 27 } 28 29 } 30 31 }
現在想用二個線程來處理:

1 package com.cnblogs.yjmyzz; 2 3 public class RunnableDemo4 { 4 5 public static void main(String[] args) { 6 7 Runnable r1 = new OrderRunnable("A"); 8 Runnable r2 = new OrderRunnable("B"); 9 10 new Thread(r1).start(); 11 new Thread(r2).start(); 12 13 } 14 15 }
運行結果:
A -> order success!
B -> order success!
B -> order success!
A -> order success!
B -> order success!
A -> order success!
A -> order success!
B -> order success!
B -> order success!
A -> order success!
B -> order success!
A -> order success!
A -> order success!
B -> order success!
A -> order success!
B -> order success!
A -> order success!
B -> order success!
A -> order success!
B -> order success!
顯然,這個結果不正確,只有10個產品,卻生成了20個訂單!
正確的做法,讓多個Thread共同使用一個Runnable:

1 package com.cnblogs.yjmyzz; 2 3 public class RunnableDemo5 { 4 5 public static void main(String[] args) { 6 7 Runnable r1 = new OrderRunnable("A"); 8 9 new Thread(r1).start(); 10 new Thread(r1).start(); 11 12 } 13 14 }
A -> order success!
A -> order success!
A -> order success!
A -> order success!
A -> order success!
A -> order success!
A -> order success!
A -> order success!
A -> order success!
A -> order success!
A -> order success!
五、ThreadPoolExecutor
如果有大量線程,建議使用線程池管理,下面是ThreadPoolExecutor的示例用法:

1 package com.cnblogs.yjmyzz; 2 3 import java.util.concurrent.ArrayBlockingQueue; 4 import java.util.concurrent.ThreadPoolExecutor; 5 import java.util.concurrent.TimeUnit; 6 7 public class RunnableDemo7 { 8 9 public static void main(String[] args) { 10 11 ThreadPoolExecutor threadPool = new ThreadPoolExecutor(2, 10, 1, 12 TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(3)); 13 14 for (int i = 0; i < 6; i++) { 15 threadPool.execute(new MyRunnable("R"+i, 5)); 16 } 17 18 } 19 20 }
運行結果:
R5.i=1
R0.i=1
R1.i=1
R5.i=2
R1.i=2
R0.i=2
R5.i=3
R1.i=3
R0.i=3
R5.i=4
R1.i=4
R0.i=4
R5.i=5
R0.i=5
R1.i=5
R2.i=1
R3.i=1
R4.i=1
R2.i=2
R3.i=2
R4.i=2
R2.i=3
R3.i=3
R4.i=3
R2.i=4
R4.i=4
R3.i=4
R2.i=5
R4.i=5
R3.i=5
agapple在ITeye上有一篇舊貼子,寫得很好,推薦大家去看看,特別是下面這張圖:
還有這篇 http://jiaguwen123.iteye.com/blog/1017636,也值得參考
六、ThreadPoolTaskExecutor
終於輪到我大Spring出場了,Spring框架提供了org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor類,可以用注入的形式生成線程池

1 <?xml version="1.0" encoding="UTF-8"?> 2 <beans xmlns="http://www.springframework.org/schema/beans" 3 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:aop="http://www.springframework.org/schema/aop" 4 xmlns:tx="http://www.springframework.org/schema/tx" xmlns:jdbc="http://www.springframework.org/schema/jdbc" 5 xmlns:context="http://www.springframework.org/schema/context" 6 xsi:schemaLocation=" 7 http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd 8 http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd 9 http://www.springframework.org/schema/jdbc http://www.springframework.org/schema/jdbc/spring-jdbc-3.0.xsd 10 http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd 11 http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd" 12 default-autowire="byName"> 13 14 <bean id="threadPoolTaskExecutor" 15 class="org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor"> 16 <property name="corePoolSize" value="2" /> 17 <property name="maxPoolSize" value="10" /> 18 <property name="queueCapacity" value="1000" /> 19 <property name="keepAliveSeconds" value="15" /> 20 <property name="rejectedExecutionHandler"> 21 <bean class="java.util.concurrent.ThreadPoolExecutor$CallerRunsPolicy" /> 22 </property> 23 </bean> 24 25 </beans>
配置好以后,就可以直接使用了

1 package com.cnblogs.yjmyzz; 2 3 import org.springframework.context.ApplicationContext; 4 import org.springframework.context.support.ClassPathXmlApplicationContext; 5 import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 6 7 public class RunnableDemo8 { 8 9 @SuppressWarnings("resource") 10 public static void main(String[] args) { 11 12 ApplicationContext applicationContext = new ClassPathXmlApplicationContext( 13 "spring.xml"); 14 ThreadPoolTaskExecutor taskExecutor = applicationContext.getBean( 15 "threadPoolTaskExecutor", ThreadPoolTaskExecutor.class); 16 17 for (int i = 0; i < 6; i++) { 18 taskExecutor.execute(new MyRunnable("R" + i, 5)); 19 } 20 21 } 22 23 }
七、FutureTask<T>
如果某些線程的處理非常耗時,不希望它阻塞其它線程,可以考慮使用FutureTask,正如字面意義一樣,該線程啟用后,馬上開始,但是處理結果將在"未來"某一時刻,才真正需要,在此之前,其它線程可以繼續處理自己的事情

1 package com.cnblogs.yjmyzz; 2 3 import java.util.concurrent.Callable; 4 import java.util.concurrent.ExecutionException; 5 import java.util.concurrent.FutureTask; 6 7 public class RunnableDemo9 { 8 9 public static void main(String[] args) throws InterruptedException, 10 ExecutionException { 11 12 FutureTask<String> task = new FutureTask<String>( 13 new Callable<String>() { 14 public String call() throws InterruptedException { 15 System.out.println("FutureTask開始處理..."); 16 Thread.sleep(1000); 17 return "hello world"; 18 } 19 }); 20 System.out.println("FutureTask准備開始..."); 21 new Thread(task).start(); 22 System.out.println("其它處理開始..."); 23 Thread.sleep(1000); 24 System.out.println("其它處理完成..."); 25 System.out.println("FutureTask處理結果:" + task.get()); 26 System.out.println("全部處理完成"); 27 } 28 29 }
二個注意點:
a) FutureTask使用Callable接口取得返回值,因為結果可能並不需要立刻返回,而是等到未來真正需要的時候,而Runnable並不提供返回值
b) FutureTask通過Thread的start()調用后,馬上就開始處理,但並不阻塞后面的線程,在真正需要處理結果的時候,調用get()方法,這時如果FutureTask本身的處理尚未完成,才會阻塞,等待處理完成
剛才的運行結果:
FutureTask准備開始...
FutureTask開始處理...
其它處理開始...
其它處理完成...
FutureTask處理結果:hello world
全部處理完成
可以看到,“其它處理”並未被FutureTask阻塞,但FutureTask其實已經在后台處理了。