張孝祥_Java多線程與併發庫高級應用03

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)           設置爲給定值。

 String

toString()           返回當前值的字符串表示形式。

 boolean

weakCompareAndSet(int expect, int update)           如果當前值 == 預期值,則以原子方式將該設置爲給定的更新值。

                            |----AtomicIntegerArray

構造方法摘要

AtomicIntegerArray(int length)           創建給定長度的新 AtomicIntegerArray。

AtomicIntegerArray(int[] array)           創建與給定數組具有相同長度的新 AtomicIntegerArray,並從給定數組複製其所有元素。

方法摘要

 int

addAndGet(int i, int delta)           以原子方式將給定值與索引 i 的元素相加。

 boolean

compareAndSet(int i, int expect, int update)           如果當前值 == 預期值,則以原子方式將位置 i 的元素設置爲給定的更新值。

 int

decrementAndGet(int i)           以原子方式將索引 i 的元素減 1。

 int

get(int i)           獲取位置 i 的當前值。

 int

getAndAdd(int i, int delta)           以原子方式將給定值與索引 i 的元素相加。

 int

getAndDecrement(int i)           以原子方式將索引 i 的元素減 1。

 int

getAndIncrement(int i)           以原子方式將索引 i 的元素加 1。

 int

getAndSet(int i, int newValue)           將位置 i 的元素以原子方式設置爲給定值,並返回舊值。

 int

incrementAndGet(int i)           以原子方式將索引 i 的元素加 1。

 void

lazySet(int i, int newValue)           最後將位置 i 的元素設置爲給定值。

 int

length()           返回該數組的長度。

 void

set(int i, int newValue)           將位置 i 的元素設置爲給定值。

 String

toString()           返回數組當前值的字符串表示形式。

 boolean

weakCompareAndSet(int i, int expect, int update)           如果當前值 == 預期值,則以原子方式將位置 i 的元素設置爲給定的更新值。

 

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.CallableFuture的應用:獲取一個線程的運行結果

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)           試圖取消對此任務的執行。

 V

get()           如有必要,等待計算完成,然後獲取其結果。

 V

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方法摘要

 Future<V>

poll()           獲取並移除表示下一個已完成任務的 Future,如果不存在這樣的任務,則返回 null。

 Future<V>

poll(long timeout, TimeUnit unit)          獲取並移除表示下一個已完成任務的 Future,如果目前不存在這樣的任務,則將等待指定的時間(如果有必要)。

 Future<V>

submit(Callable<V> task)           提交要執行的值返回任務,並返回表示掛起的任務結果的 Future。

 Future<V>

submit(Runnable task, V result)           提交要執行的 Runnable 任務,並返回一個表示任務完成的 Future,可以提取或輪詢此任務。

 Future<V>

take()           獲取並移除表示下一個已完成任務的 Future,如果目前不存在這樣的任務,則等待。

ExecutorCompletionService構造方法摘要

ExecutorCompletionService(Executor executor)
          使用爲執行基本任務而提供的執行程序創建一個 ExecutorCompletionService,並將 LinkedBlockingQueue 作爲完成隊列。

 

ExecutorCompletionService(Executor executor, BlockingQueue<Future<V>> completionQueue)
          使用爲執行基本任務而提供的執行程序創建一個 ExecutorCompletionService,並將所提供的隊列作爲其完成隊列。

 

示例:

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());

}


發佈了19 篇原創文章 · 獲贊 2 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章