05. 線程範圍內共享變量的概念與作用
線程範圍內共享數據圖解:
代碼演示:
class ThreadScopeShareData
{
三個模塊共享數據,主線程模塊和AB模塊
privatestatic int data = 0; 準備共享的數據
存放各個線程對應的數據
private Map<Thread, Integer>threadData = new HashMap<Thread, Integer>();
publicstatic void main(String[] args)
{ 創建兩個線程
for (int i=0;i<2; i++)
{
new Thread(
new Runnable()
{
public void run()
{現在當前線程中修改一下數據,給出修改信息
data = new Random().nextInt();
SOP(Thread.currentThread().getName()+將數據改爲+data);
將線程信息和對應數據存儲起來
threadData.put(Thread.currentThread(),data);
使用兩個不同的模塊操作這個數據,看結果
new A().get();
new B().get();
}
}
).start();
}
}
staticclass A
{
public void get()
{
data =threadData.get(Thread.currentThread());
SOP(A+Thread.currentThread().getName()+拿到的數據+data);
}
}
staticclass B
{
public void get()
{
data =threadData.get(Thread.currentThread());
SOP(B+Thread.currentThread().getName()+拿到的數據+data);
}
}
}
結果並沒與實現線程間的數據同步,兩個線程使用的是同一個線程的數據。要解決這個問題,可以將每個線程用到的數據與對應的線程號存放到一個map集合中,使用數據時從這個集合中根據線程號獲取對應線程的數據。代碼實現:上面紅色部分
程序中存在的問題:獲取的數據與設置的數據不同步
Thread-1共享數據設置爲:-997057737
Thread-1--A 模塊數據:-997057737
Thread-0共享數據設置爲:11858818
Thread-0--A 模塊數據:11858818
Thread-0--B 模塊數據:-997057737
Thread-1--B 模塊數據:-997057737
最好將Runnable中設置數據的方法也寫在對應的模塊中,與獲取數據模塊互斥,以保證數據同步
06.ThreadLocal類及應用技巧
多個模塊在同一個線程中運行時要共享同一份數據,實現線程範圍內的數據共享可以用上一節中所用的方法。
JDK1.5提供了ThreadLocal類來方便實現線程範圍內的數據共享,它的作用就相當於上一節中的Map。
每個線程調用全局ThreadLocal對象的set方法,就相當於往其內部的map集合中增加一條記錄,key就是各自的線程,value就是各自的set方法傳進去的值。
在線程結束時可以調用ThreadLocal.clear()方法用來更快釋放內存,也可以不調用,因爲線程結束後也可以自動釋放相關的ThreadLocal變量。
一個ThreadLocal對象只能記錄一個線程內部的一個共享變量,需要記錄多個共享數據,可以創建多個ThreadLocal對象,或者將這些數據進行封裝,將封裝後的數據對象存入ThreadLocal對象中。
將數據對象封裝成單例,同時提供線程範圍內的共享數據的設置和獲取方法,提供已經封裝好了的線程範圍內的對象實例,使用時只需獲取實例對象即可實現數據的線程範圍內的共享,因爲該對象已經是當前線程範圍內的對象了。下邊給出張老師的優雅代碼:
package cn.itheima;
import java.util.Random;
public classThreadLocalShareDataDemo
{ /**06.ThreadLocal類及應用技巧
* 將線程範圍內共享數據進行封裝,封裝到一個單獨的數據類中,提供設置獲取方法
* 將該類單例化,提供獲取實例對象的方法,獲取到的實例對象是已經封裝好的當前線程範圍內的對象
*/
public static voidmain(String[] args)
{
for (inti=0; i<2; i++)
{
newThread(
newRunnable()
{
public voidrun()
{
intdata = new Random().nextInt(889);
System.out.println(Thread.currentThread().getName()+"產生數據:"+data);
MyDatamyData = MyData.getInstance();
myData.setAge(data);
myData.setName("Name:"+data);
newA().get();
newB().get();
}
}).start();
}
}
static class A
{ //可以直接使用獲取到的線程範圍內的對象實例調用相應方法
Stringname = MyData.getInstance().getName();
int age =MyData.getInstance().getAge();
public voidget()
{
System.out.println(Thread.currentThread().getName()+"--AA name:"+name+"...age:"+age);
}
}
static class B
{
//可以直接使用獲取到的線程範圍內的對象實例調用相應方法
Stringname = MyData.getInstance().getName();
int age =MyData.getInstance().getAge();
public voidget()
{
System.out.println(Thread.currentThread().getName()+"--BB name:"+name+"...age:"+age);
}
}
static classMyData
{
privateString name;
private int age;
publicString getName()
{
return name;
}
public voidsetName(String name)
{
this.name =name;
}
public intgetAge()
{
return age;
}
public voidsetAge(int age)
{
this.age =age;
}
//單例
privateMyData() {};
//提供獲取實例方法
public staticMyData getInstance()
{
//從當前線程範圍內數據集中獲取實例對象
MyDatainstance = threadLocal.get();
if(instance==null)
{
instance= new MyData();
threadLocal.set(instance);
}
returninstance;
}
//將實例對象存入當前線程範圍內數據集中
staticThreadLocal<MyData> threadLocal = newThreadLocal<MyData>();
}
}
08.java5原子性操作類的應用
Java5的線程併發庫
java.util.concurrent在併發編程中很常用的實用工具類。
|----locks爲鎖和等待條件提供一個框架的接口和類,
它不同於內置同步和監視器
|----atomic類的小工具包,支持在單個變量上解除鎖的線程安全編程。
可以對基本類型、數組中的基本類型、類中的基本類型等進行操作
|----AtomicInteger
構造方法摘要 |
|
AtomicInteger() 創建具有初始值 0 的新 AtomicInteger。 |
|
AtomicInteger(int initialValue) 創建具有給定初始值的新 AtomicInteger。 |
|
方法摘要 |
|
int |
addAndGet(int delta) 以原子方式將給定值與當前值相加。 |
boolean |
compareAndSet(int expect, int update) 如果當前值 == 預期值,則以原子方式將該值設置爲給定的更新值。 |
int |
decrementAndGet() 以原子方式將當前值減 1。 |
double |
doubleValue() 以 double 形式返回指定的數值。 |
float |
floatValue() 以 float 形式返回指定的數值。 |
int |
get() 獲取當前值。 |
int |
getAndAdd(int delta) 以原子方式將給定值與當前值相加。 |
int |
getAndDecrement() 以原子方式將當前值減 1。 |
int |
getAndIncrement() 以原子方式將當前值加 1。 |
int |
getAndSet(int newValue) 以原子方式設置爲給定值,並返回舊值。 |
int |
incrementAndGet() 以原子方式將當前值加 1。 |
int |
intValue() 以 int 形式返回指定的數值。 |
void |
lazySet(int newValue) 最後設置爲給定值。 |
long |
longValue() 以 long 形式返回指定的數值。 |
void |
set(int newValue) 設置爲給定值。 |
toString() 返回當前值的字符串表示形式。 |
|
boolean |
weakCompareAndSet(int expect, int update) 如果當前值 == 預期值,則以原子方式將該設置爲給定的更新值。 |
|----AtomicIntegerArray
構造方法摘要 |
|
|
|
|
|
方法摘要 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
09.java5線程併發庫的應用
如果沒有線程池,需要在run方法中不停判斷,還有沒有任務需要執行
線程池的通俗比喻:接待客戶,爲每個客戶都安排一個工作人員,接待完成後該工作人員就廢掉。服務器每收到一個客戶請求就爲其分配一個線程提供服務,服務結束後銷燬線程,不斷創建、銷燬線程,影響性能。
線程池:先創建多個線程放在線程池中,當有任務需要執行時,從線程池中找一個空閒線程執行任務,任務完成後,並不銷燬線程,而是返回線程池,等待新的任務安排。
線程池編程中,任務是提交給整個線程池的,並不是提交給某個具體的線程,而是由線程池從中挑選一個空閒線程來運行任務。一個線程同時只能執行一個任務,可以同時向一個線程池提交多個任務。
線程池創建方法:
a、創建一個擁有固定線程數的線程池
ExecutorServicethreadPool = Executors.newFixedThreadPool(3);
b、創建一個緩存線程池 線程池中的線程數根據任務多少自動增刪動態變化
ExecutorServicethreadPool = Executors.newCacheThreadPool();
c、創建一個只有一個線程的線程池 與單線程一樣 但好處是保證池子裏有一個線程,
當線程意外死亡,會自動產生一個替補線程,始終有一個線程存活
ExecutorServicethreadPool = Executors.newSingleThreadExector();
往線程池中添加任務
threadPool.executor(Runnable)
關閉線程池:
threadPool.shutdown() 線程全部空閒,沒有任務就關閉線程池
threadPool.shutdownNow() 不管任務有沒有做完,都關掉
用線程池啓動定時器:
a、創建調度線程池,提交任務 延遲指定時間後執行任務
Executors.newScheduledThreadPool(線程數).schedule(Runnable, 延遲時間,時間單位);
b、創建調度線程池,提交任務, 延遲指定時間執行任務後,間隔指定時間循環執行
Executors.newScheduledThreadPool(線程數). scheduleAtFixedRate (Runnable,延遲時間,
間隔時間,時間單位);
所有的 schedule 方法都接受相對延遲和週期作爲參數,而不是絕對的時間或日期。將以
Date
所表示的絕對時間轉換成要求的形式很容易。例如,要安排在某個以後的
Date 運行,可以使用:schedule(task,date.getTime() - System.currentTimeMillis(),TimeUnit.MILLISECONDS)。
10.Callable與Future的應用:獲取一個線程的運行結果
public interface Callable<V>
返回結果並且可能拋出異常的任務。實現者定義了一個不帶任何參數的叫做call 的方法。 Callable 接口類似於 Runnable,兩者都是爲那些其實例可能被另一個線程執行的類設計的。但是Runnable 不會返回結果,並且無法拋出經過檢查的異常。
只有一個方法V call
()
計算結果,如果無法計算結果,則拋出一個Exception異常。
使用方法:
ExecutorServicethreadPool = Executors.newSingleThreadExccutor();
如果不需要返回結果,就用execute方法 調用submit方法返回一個Future對象
Future<T> future = threadPool.submit(new Callable<T>(){//接收一個Callable接口的實例對象
覆蓋Callable接口中的call方法,拋出異常
publicTcall() throws Exception
{
ruturnT
}
});
獲取Future接收的結果
future.get();會拋出異常
future.get()沒有拿到結果就會一直等待
Future取得的結果類型和Callable返回的結果類型必須一致,通過泛型實現。Callable要通過ExecutorService的submit方法提交,返回的Future對象可以取消任務。
public interface Future<V>
Future 表示異步計算的結果。它提供了檢查計算是否完成的方法,以等待計算的完成,並獲取計算的結果。計算完成後只能使用get 方法來獲取結果,如有必要,計算完成前可以阻塞此方法。取消則由cancel 方法來執行。還提供了其他方法,以確定任務是正常完成還是被取消了。一旦計算完成,就不能再取消計算。如果爲了可取消性而使用Future 但又不提供可用的結果,則可以聲明Future<?> 形式類型、並返回 null 作爲底層任務的結果。
方法摘要 |
|
boolean |
cancel(boolean mayInterruptIfRunning) 試圖取消對此任務的執行。 |
get() 如有必要,等待計算完成,然後獲取其結果。 |
|
get(long timeout, TimeUnit unit) 如有必要,最多等待爲使計算完成所給定的時間之後,獲取其結果(如果結果可用)。 |
|
boolean |
isCancelled() 如果在任務正常完成前將其取消,則返回 true。 |
boolean |
isDone() 如果任務已完成,則返回 true。 |
public interface CompletionService<V>
CompletionService用於提交一組Callable任務,其take方法返回一個已完成的Callable任務對應的Future對象。好比同時種幾塊麥子等待收割,收割時哪塊先熟先收哪塊。
將生產新的異步任務與使用已完成任務的結果分離開來的服務。生產者submit 執行的任務。使用者 take 已完成的任務,並按照完成這些任務的順序處理它們的結果。例如,CompletionService 可以用來管理異步 IO ,執行讀操作的任務作爲程序或系統的一部分提交,然後,當完成讀操作時,會在程序的不同部分執行其他操作,執行操作的順序可能與所請求的順序不同。
通常,CompletionService 依賴於一個單獨的 Executor 來實際執行任務,在這種情況下,CompletionService 只管理一個內部完成隊列。ExecutorCompletionService 類提供了此方法的一個實現。
CompletionService方法摘要 |
||
poll() 獲取並移除表示下一個已完成任務的 Future,如果不存在這樣的任務,則返回 null。 |
||
poll(long timeout, TimeUnit unit) 獲取並移除表示下一個已完成任務的 Future,如果目前不存在這樣的任務,則將等待指定的時間(如果有必要)。 |
||
submit(Runnable task, V result) 提交要執行的 Runnable 任務,並返回一個表示任務完成的 Future,可以提取或輪詢此任務。 |
||
take() 獲取並移除表示下一個已完成任務的 Future,如果目前不存在這樣的任務,則等待。 |
||
ExecutorCompletionService(Executor executor)
|
|
|
ExecutorCompletionService(Executor executor,
BlockingQueue<Future<V>> completionQueue)
|
|
示例:
ExecutorService threadPool = Executors.newFixedThreadPool(10); //創建線程池,傳遞給coms
用threadPool執行任務,執行的任務返回結果都是整數
CompletionService<Integer> coms = newExecutorCompletionService<Integer>(threadPool);
提交10個任務 種麥子
for (int i=0; i<10; i++)
{
finalint num = i+1;
coms.submit(newCallable<Integer>(){
public Integercall() 覆蓋call方法
{匿名內部類使用外部變量要用final修飾
SOP(任務+num);
Thread.sleep(newRandom().nextInt(6)*1000);
return num;
}
});
}
等待收穫 割麥子
for (int i=0; i<10; i++)
{ take獲取第一個Future對象,用get獲取結果
SOP(coms.take().get());
}