天天看點

java:多線程基礎之Runnable、Callable與Thread

java:多線程基礎之Runnable、Callable與Thread

java中的多線程入門 Runnable與Thread基本使用

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

一、Runnable入門示例

java:多線程基礎之Runnable、Callable與Thread
java:多線程基礎之Runnable、Callable與Thread
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 }      

View Code

代碼很簡單,每個線程依次輸出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的子類

java:多線程基礎之Runnable、Callable與Thread
java:多線程基礎之Runnable、Callable與Thread
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 }      

 然後這樣使用:

java:多線程基礎之Runnable、Callable與Thread
java:多線程基礎之Runnable、Callable與Thread
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類改變這種局面:

java:多線程基礎之Runnable、Callable與Thread
java:多線程基礎之Runnable、Callable與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方法,可以讓多個線程并行處理,運作結果如下:

從輸出結果上看,二個線程已經在并行處理了。

四、通過線上搶購示例了解資源共享

雙十一剛過,每到這個時候,通常是狼多肉少,下面的OrderRunnable類模拟這種搶購情況,假設産品數隻有10個,搶購的客戶卻有100個

java:多線程基礎之Runnable、Callable與Thread
java:多線程基礎之Runnable、Callable與Thread
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 }      

現在想用二個線程來處理:

java:多線程基礎之Runnable、Callable與Thread
java:多線程基礎之Runnable、Callable與Thread
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!

顯然,這個結果不正确,隻有10個産品,卻生成了20個訂單!

正确的做法,讓多個Thread共同使用一個Runnable:

java:多線程基礎之Runnable、Callable與Thread
java:多線程基礎之Runnable、Callable與Thread
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 }      

五、ThreadPoolExecutor

如果有大量線程,建議使用線程池管理,下面是ThreadPoolExecutor的示例用法:

java:多線程基礎之Runnable、Callable與Thread
java:多線程基礎之Runnable、Callable與Thread
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上有一篇舊貼子,寫得很好,推薦大家去看看,特别是下面這張圖:

java:多線程基礎之Runnable、Callable與Thread

六、ThreadPoolTaskExecutor

終于輪到我大Spring出場了,Spring架構提供了org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor類,可以用注入的形式生成線程池

java:多線程基礎之Runnable、Callable與Thread
java:多線程基礎之Runnable、Callable與Thread
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>      

配置好以後,就可以直接使用了

java:多線程基礎之Runnable、Callable與Thread
java:多線程基礎之Runnable、Callable與Thread
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,正如字面意義一樣,該線程啟用後,馬上開始,但是處理結果将在"未來"某一時刻,才真正需要,在此之前,其它線程可以繼續處理自己的事情

java:多線程基礎之Runnable、Callable與Thread
java:多線程基礎之Runnable、Callable與Thread
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其實已經在背景處理了。

作者:菩提樹下的楊過