多線程編程,有時希望每個線程的數據相互隔離互不影響,有時卻希望線程間能共享數據,並保持同步。本篇體驗多線程共享和不共享數據。
□ 多線程不共享數據
對於多線程,CLR到底是怎樣為它們分配內存棧空間呢?是"一個蘿卜一個坑",每個線程都有自己的棧空間;還是"大樹底下好乘涼",所有的線程共享同一個棧空間?
我們讓2個線程執行相同的靜態方法,用到相同的變量,通過打印變量來求證多線程棧空間的分配情況。
class Program{static void Main(string[] args){new Thread(SayHi).Start();SayHi();}static void SayHi(){for (int i = 0; i < 5; i++){Console.WriteLine("我是線程#" + Thread.CurrentThread.ManagedThreadId + "這是我的第" +i+ "次說hello");}}}
雖然2個線程交叉執行,但都說了4次的hello。說明CLR為2個線程都分配了棧空間,變量i在各自的棧空間中相互不受影響。
□ 多線程共享數據
線程間是需要相互合作的,多個線程如何共享數據呢?
※ 共享實例變量
讓2個線程執行同一個實例方法,看看是否可以共享對象實例的公共字段。
using System;using System.Threading;namespace ConsoleApplication1{class Program{public int whatever;static void Main(string[] args){Program p = new Program();new Thread(p.PrintVariable).Start();p.PrintVariable();}void PrintVariable(){whatever++;Console.WriteLine("線程#" + Thread.CurrentThread.ManagedThreadId + " 執行後whatever變量為:" +whatever);}}}
可見,2個線程共享了Program實例的公共字段。
另外,關於線程間共享數據,一個繞不開的話題是:如何避免數據不同步?使用lock語句塊可以解決這個問題,在同一時刻只允許有一個線程進入方法內部。這樣的做法也叫”線程安全”。
class Program{public int whatever;//static readonly編譯期變量 在聲明時賦值static readonly object locker = new object();static void Main(string[] args){Program p = new Program();new Thread(p.PrintVariable).Start();p.PrintVariable();}void PrintVariable(){lock (locker){whatever++;Console.WriteLine("線程#" + Thread.CurrentThread.ManagedThreadId + " 執行後whatever變量為:" + whatever);}}}
當一個線程在lock語句塊執行,另一個線程等待,等待的這個線程在那刻不消耗CPU。
※ 共享靜態字段
class Program{static int whatever;//static readonly編譯期變量 在聲明時賦值static readonly object locker = new object();static void Main(string[] args){new Thread(PrintVariable).Start();PrintVariable();}static void PrintVariable(){lock (locker){whatever++;Console.WriteLine("線程#" + Thread.CurrentThread.ManagedThreadId + " 執行後whatever變量為:" + whatever);}}}
總結:
○ CLR會給每個線程分配內存棧空間,棧中的變量互不影響
○ 多線程可以共享對象實例的公共成員和類的靜態字段
○ 線程間共享數據需要考慮"線程安全",使用lock語句塊可保證"線程安全"
線程系列包括:
public class ThreadPoolManager {
private static ThreadPoolManager instance = null;
private List<Upload> taskQueue = Collections.synchronizedList(new LinkedList<Upload>());//任務隊列
private WorkThread[] workQueue ; //工作線程(真正執行任務的線程)
private static int worker_num = 6; //工作線程數量(默認工作線程數量是6)
private static int worker_count = 0;
private ThreadPoolManager(){
this(6);
}
private ThreadPoolManager(int num){
worker_num = num;
workQueue = new WorkThread[worker_num];
for(int i=0;i<worker_num;i++){
workQueue[i] = new WorkThread(i);
}
}
public static synchronized ThreadPoolManager getInstance(){
if(instance==null)
instance = new ThreadPoolManager();
return instance;
}
public void addTask(Upload task){
//對任務隊列的操作要上鎖
synchronized (taskQueue) {
if(task!=null){
taskQueue.add(task);
taskQueue.notifyAll();
System.out.println("task id "+task.getInfo() + " submit!");
}
}
}
public void BatchAddTask(Upload[] tasks){
//對任務隊列的修改操作要上鎖
synchronized (taskQueue) {
for(Upload e:tasks){
if(e!=null){
taskQueue.add(e);
taskQueue.notifyAll();
System.out.println("t......余下全文>>
共享數據不難,只需要在創建線程對象的時候將你需要共享的數據對象在啟動線程前(通常是構造線程對象的時候)傳入即可。但是必須注意:
1. 共享的數據必須是線程安全的,因為涉及兩個線程的並發訪問。
2. 共享的數據內部的其它對象引用也必須是線程安全的,假如你需要訪問它們。
貼個代碼:
Object shareObject;// init object you need.
var t1 = new System.Threading.Thread(arg =>
{// arg is shareObject
});
var t2 = new System.Threading.Thread(arg =>
{// arg is shareObject
});
t1.Start(shareObject);
t2.Start(shareObject);