摘要:然而,這兩個(gè)方法都只是讀取對(duì)象狀態(tài),如果只是讀取操作,就可以允許線程并行,這樣讀取效率將會(huì)提高。分配線程執(zhí)行子任務(wù)執(zhí)行子任務(wù)獲得子任務(wù)進(jìn)行完成的結(jié)果
Lock
Lock接口主要操作類是ReentrantLock,可以起到synchronized的作用,另外也提供額外的功能。
用Lock重寫上一篇中的死鎖例子
import java.util.concurrent.locks.Lock; import java.util.concurrent.locks.ReentrantLock; public class Resource { Lock lock=new ReentrantLock(); int num=0; void doSome(){ } public void deal(Resource res){ while(true){ boolean mylock=this.lock.tryLock();//嘗試獲得當(dāng)前Resource的鎖定 boolean resLock=res.lock.tryLock();//嘗試獲得傳入的Resource的鎖定 try{ if(mylock&&resLock){ res.doSome(); System.out.println(res+":"+this.num); break;//退出循環(huán) } }finally{ if(mylock) this.lock.unlock(); if(resLock) res.lock.unlock(); } } } }
重寫后不會(huì)出現(xiàn)死鎖的原因在于,當(dāng)無法同時(shí)獲得兩個(gè)鎖定時(shí),干脆釋放已獲得的鎖定。
上面代碼使用當(dāng)前Resource的Lock的tryLock()方法嘗試獲得鎖定,以及傳入Resource的Lock的tryLock()方法嘗試獲得鎖定。只有當(dāng)可以獲得兩個(gè)Resource的鎖定,才能執(zhí)行res.doSome().最后無論什么情況,都要finally解除鎖定。
ReadWriteLock接口定義了讀取鎖定和寫入鎖定的行為??梢允褂?b>readLock(),writeLock()方法返回Lock操作對(duì)象。
ReentrantReadWriteLock是ReadWriteLock接口的主要操作類.
ReentrantReadWriteLock.readLock操作Lock接口,調(diào)用其lock()方法時(shí),若沒有任何ReentrantReadWriteLock.writeLock調(diào)用過lock()方法,也就是沒有任何寫入鎖定時(shí),才可以取得讀取鎖定。
下面用ReadWriteLock試著寫一個(gè)ArrayList
import java.util.Arrays; import java.util.concurrent.locks.ReadWriteLock; import java.util.concurrent.locks.ReentrantReadWriteLock; public class MyArrayList{ private ReadWriteLock lock=new ReentrantReadWriteLock(); private Object[] list; private int next=0; public MyArrayList(){ list=new Object[16]; } public void add(T obj){ try{ lock.writeLock().lock();//獲取寫入鎖定 if(next==list.length) list=Arrays.copyOf(list, list.length*2); list[next++]=obj; }finally{ lock.writeLock().unlock();//解除寫入鎖定 } } @SuppressWarnings("unchecked") public T get(int index){ try{ lock.readLock().lock();//獲取讀取鎖定 return (T) list[index]; }finally{ lock.readLock().unlock();//解除讀取鎖定 } } public int size(){ try{ lock.readLock().lock(); return next; }finally{ lock.readLock().unlock(); } } }
重寫后的效果是
若有線程調(diào)用add()方法進(jìn)行寫入操作,先獲得寫入鎖定。這時(shí)如果有其他線程準(zhǔn)備獲得寫入鎖定或讀取鎖定,都必須等待前面的寫入鎖定解除。
若有線程調(diào)用get()方法進(jìn)行讀取操作,先獲得讀取鎖定。這時(shí)如果有其他線程準(zhǔn)備獲得讀取鎖定,則可以獲得;但如果是準(zhǔn)備獲得寫入鎖定,仍然要等待所有讀取鎖定解除。
使用ReadWriteLock的好處在于,如果有兩個(gè)線程都想調(diào)用get()和size()方法,由于鎖定的關(guān)系,其中一個(gè)線程只能等到另一個(gè)線程解除鎖定。然而,這兩個(gè)方法都只是讀取對(duì)象狀態(tài),如果只是讀取操作,就可以允許線程并行,這樣讀取效率將會(huì)提高。
ConditionCondition接口用來搭配Lock,最基本的用法就是達(dá)到Object的wait(),notify(),notifyAll()方法的作用。
下面用wait(),notify(),notifyAll()實(shí)現(xiàn)生產(chǎn)者與消費(fèi)者.
店員從生產(chǎn)者獲得生產(chǎn)出的商品,消費(fèi)者從店員取走商品
若生產(chǎn)者生產(chǎn)速度較快,店員那可能有很多商品,店員會(huì)叫生產(chǎn)者停一下。過一段時(shí)間,店員那商品不多了,再通知生產(chǎn)者繼續(xù)生產(chǎn)
若消費(fèi)者取走速度過快,店員那可能沒有商品可供取走,店員會(huì)叫消費(fèi)者停一下。過一段時(shí)間,店員那有商品了,再通知消費(fèi)者過來取
這里假定店員那最多只能放一件商品
public class Producer implements Runnable{ private Clerk clerk; public Producer(Clerk clerk){ this.clerk=clerk; } @Override public void run() { for(int i=0;i<10;i++){ try { Thread.sleep((int)Math.random()*3000); } catch (InterruptedException e) { } clerk.setProduct(i); } } }
public class Consumer implements Runnable{ private Clerk clerk; public Consumer(Clerk clerk){ this.clerk=clerk; } @Override public void run() { for(int i=0;i<10;i++){ try { Thread.sleep((int)Math.random()*3000); } catch (InterruptedException e) { } clerk.getProduct(); } } }
public class Clerk extends Thread{ private int product=-1;//沒有商品 public synchronized void setProduct(int product){ while(this.product!=-1){ try { wait();//店員那有商品,生產(chǎn)者停一下 } catch (InterruptedException e) { } } this.product=product; System.out.println("生產(chǎn)者生產(chǎn)商品"+this.product); notify();//通知等待集合(喚醒的可能是消費(fèi)者,也可能是生產(chǎn)者) } public synchronized int getProduct(){ while(this.product==-1){ try { wait();//店員沒有商品,消費(fèi)者停一下 } catch (InterruptedException e) { } } int p=this.product; System.out.println("消費(fèi)者消費(fèi)商品"+this.product); this.product=-1;//商品已經(jīng)被取走 notify(); return p; } public static void main(String[] args){ Clerk clerk=new Clerk(); new Thread(new Producer(clerk)).start(); new Thread(new Consumer(clerk)).start(); } }
生產(chǎn)者生產(chǎn)商品0 消費(fèi)者消費(fèi)商品0 生產(chǎn)者生產(chǎn)商品1 消費(fèi)者消費(fèi)商品1 生產(chǎn)者生產(chǎn)商品2 消費(fèi)者消費(fèi)商品2 生產(chǎn)者生產(chǎn)商品3 消費(fèi)者消費(fèi)商品3 生產(chǎn)者生產(chǎn)商品4 消費(fèi)者消費(fèi)商品4 生產(chǎn)者生產(chǎn)商品5 消費(fèi)者消費(fèi)商品5 生產(chǎn)者生產(chǎn)商品6 消費(fèi)者消費(fèi)商品6 生產(chǎn)者生產(chǎn)商品7 消費(fèi)者消費(fèi)商品7 生產(chǎn)者生產(chǎn)商品8 消費(fèi)者消費(fèi)商品8 生產(chǎn)者生產(chǎn)商品9 消費(fèi)者消費(fèi)商品9
現(xiàn)在用Condition接口重寫
public class Clerk { private int product=-1;//沒有商品 Lock lock=new ReentrantLock(); private Condition condition=lock.newCondition(); public void setProduct(int product){ try{ lock.lock(); while(this.product!=-1){ try { condition.await();//店員那有商品,生產(chǎn)者停一下 } catch (InterruptedException e) { } } this.product=product; System.out.println("生產(chǎn)者生產(chǎn)商品"+this.product); condition.signal();//通知等待集合(喚醒的可能是消費(fèi)者,也可能是生產(chǎn)者) }finally{ lock.unlock(); } } public int getProduct(){ try{ lock.lock(); while(this.product==-1){ try { condition.await();//店員沒有商品,消費(fèi)者停一下 } catch (InterruptedException e) { } } int p=this.product; System.out.println("消費(fèi)者消費(fèi)商品"+this.product); this.product=-1;//商品已經(jīng)被取走 condition.signal(); return p; }finally{ lock.unlock(); } } public static void main(String[] args){ Clerk clerk=new Clerk(); new Thread(new Producer(clerk)).start(); new Thread(new Consumer(clerk)).start(); } }
注意在多個(gè)生產(chǎn)者,消費(fèi)者線程的情況下,等待集合中兩者都會(huì)有,而condition.signal()從等待集合中喚醒的具體對(duì)象是不確定的。有可能消費(fèi)者取走商品后,喚醒的還是消費(fèi)者,這時(shí),消費(fèi)者又會(huì)執(zhí)行while循環(huán),進(jìn)入等待集合。
事實(shí)上,一個(gè)Condition對(duì)象可以表示一個(gè)等待集合。這樣上面例子,可以有兩個(gè)等待集合,一個(gè)給消費(fèi)者用,一個(gè)給生產(chǎn)者用。生產(chǎn)者只會(huì)通知消費(fèi)者的等待集合,消費(fèi)者也只會(huì)通知生產(chǎn)者的等待集合。這樣效率會(huì)高些。
public class Clerk { ... private Condition producerCondition=lock.newCondition();//生產(chǎn)者的等待集合 private Condition consumerCondition=lock.newCondition();//消費(fèi)者的等待集合 public void setProduct(int product){ try{ lock.lock(); while(this.product!=-1){ try { producerCondition.await();//店員那有商品,生產(chǎn)者停一下 } catch (InterruptedException e) { } } this.product=product; System.out.println("生產(chǎn)者生產(chǎn)商品"+this.product); consumerCondition.signal();//喚醒消費(fèi)者等待集合 }finally{ lock.unlock(); } } public int getProduct(){ try{ lock.lock(); while(this.product==-1){ try { consumerCondition.await();//店員沒有商品,消費(fèi)者停一下 } catch (InterruptedException e) { } } int p=this.product; System.out.println("消費(fèi)者消費(fèi)商品"+this.product); this.product=-1;//商品已經(jīng)被取走 producerCondition.signal();//喚醒生產(chǎn)者等待集合 return p; }finally{ lock.unlock(); } } ... }Executor
定義Executor接口的目的是將Runnable的指定與如何執(zhí)行分離。它只定義了一個(gè)execute()方法。
public class Page{ private Executor executor; public Page(Executor executor){ this.executor=executor; } ... public void method1(){ ... executor.execute(new Runnable(){ @Override public void run(){ ... } }); ... }
}
public class DirectExecutor implements Executor{ public void execute(Runnable r){ r.run(); } }
調(diào)用
new Page(new DirectExecutor()).method1();
Executor api
像線程池這類服務(wù),實(shí)際上是定義在Executor接口的子接口ExecutorService中。通用的ExecutorService由抽象類AbstractExecutorService操作,如果需要線程池功能,可以使用其子類ThreadPoolExecutor.
重寫上面executor例子
ExecutorService executorService=Executors.newCachedThreadPool(); new Page(executorService).method1(); executorService.shutdown();//在指定執(zhí)行的Runnable都完成后,將ExecutorService關(guān)閉Future與Callable
ExecutorService還定義了submit(),invokeAll(),invokeAny()等方法,這些方法出現(xiàn)在java.util.concurrent.Future,java.util.concurrent.Callable接口
Future定義的行為就是讓你在將來取得結(jié)果。你可以將想執(zhí)行的工作交給Future,Future會(huì)使用另一個(gè)線程處理,你可以先做別的事情。過些時(shí)候,再調(diào)用Future的get()獲得結(jié)果。
如果結(jié)果已經(jīng)產(chǎn)生,get()會(huì)直接返回,否則會(huì)進(jìn)入阻塞狀態(tài)直到結(jié)果返回。get()的另一種重載方法可以指定等待結(jié)果的時(shí)間,若指定時(shí)間內(nèi)結(jié)果還沒產(chǎn)生,則拋出TimeoutException異常。也可以使用Future的isDone()方法看結(jié)果是否產(chǎn)生。
Future經(jīng)常與Callable一起使用,Callable的作用與Runnable相似,都是用來定義執(zhí)行的流程。
Runnable的run()方法無返回值,也無法拋出異常
Callable的call()方法可以有返回值,也可以拋出異常
FutureTask是Future的操作類,創(chuàng)建時(shí)可傳入Callable對(duì)象指定執(zhí)行的流程
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; public class FutureTaskDemo { public static int fib(int n){ return n<=1?n:fib(n-1)+fib(n-2); } public static void main(String[] args){ FutureTasktask=new FutureTask ( new Callable (){ @Override public Integer call() throws Exception { return fib(30); } } ); new Thread(task).start(); try { Thread.sleep(3000); System.out.println(task.get()); } catch (InterruptedException|ExecutionException e) { } } }
FutureTask構(gòu)造類
FutureTask實(shí)現(xiàn)RunnableFuture接口,RunnableFuture接口繼承Runnable,Future接口。所以可以new Thread(task).
ExecutorService的submit()方法也可以接受Callable對(duì)象,調(diào)用后返回Future對(duì)象。
ExecutorService service=Executors.newCachedThreadPool(); Futurefuture=service.submit(new Callable (){ @Override public Integer call() throws Exception { return fib(30); } });
如果有多個(gè)Callable,可以先將它們收集到Collection中,然后調(diào)用ExecutorService的invokeAll()方法,返回List
如果有多個(gè)Callable,要求其中只要有一個(gè)執(zhí)行完成就行了,則可以先將它們收集到Collection中,然后調(diào)用ExecutorService的invokeAny()方法
ScheduledThreadPoolExecutorScheduledThreadPoolExecutor用來進(jìn)行工作排程,其中的schedule()方法用來排定Runnable或Callable實(shí)例延遲多久執(zhí)行一次,并返回Future子接口ScheduledFuture的實(shí)例。
import java.util.Date; import java.util.concurrent.Executors; import java.util.concurrent.ScheduledExecutorService; import java.util.concurrent.TimeUnit; public class ScheduledExecution { public static void main(String[] args){ ScheduledExecutorService service=Executors.newSingleThreadScheduledExecutor(); service.scheduleWithFixedDelay(new Runnable(){ public void run(){ System.out.println(new Date()); try { Thread.sleep(2000);//假設(shè)工作會(huì)執(zhí)行2s } catch (InterruptedException e) { } } }, 2000, 1000, TimeUnit.MILLISECONDS); } }
Sat Oct 24 17:11:59 CST 2015 Sat Oct 24 17:12:02 CST 2015 Sat Oct 24 17:12:05 CST 2015 Sat Oct 24 17:12:08 CST 2015 Sat Oct 24 17:12:11 CST 2015
可以看到,輸出兩兩間相差3s.
scheduleWithFixedDelay()方法參數(shù)
如果把方法換成scheduleAtFixedRate()
Sat Oct 24 17:28:28 CST 2015 Sat Oct 24 17:28:30 CST 2015 Sat Oct 24 17:28:32 CST 2015 Sat Oct 24 17:28:34 CST 2015
每次排定的執(zhí)行周期是1s,但是工作執(zhí)行的時(shí)間是2s,會(huì)超過排定的執(zhí)行周期,所以輸出兩兩間相差2s。
ForkJoinPoolFuture的另一個(gè)操作類ForkJoinTask,與ExecutorService的另一個(gè)操作類ForkJoinPool有關(guān),它們都是jdk7新增的api,用來解決分而治之的問題。
ForkJoinTask操作Future接口,可以在未來獲得耗時(shí)工作的執(zhí)行結(jié)果
ForkJoinPool管理ForkJoinTask,調(diào)用fork()方法,可以讓另一個(gè)線程執(zhí)行ForkJoinTask
如果要獲得ForkJoinTask的執(zhí)行結(jié)果,可以調(diào)用join()方法。如果執(zhí)行結(jié)果還沒產(chǎn)生,會(huì)阻塞直至有執(zhí)行結(jié)果返回
使用ForkJoinTask的子類RecursiveTask,它是個(gè)抽象類,使用時(shí)必須繼承它,并操作compute()方法。
import java.util.concurrent.ForkJoinPool; import java.util.concurrent.RecursiveTask; public class FibDemo extends RecursiveTask{ final int n; FibDemo(int n){ this.n=n; } public static int fib(int n){ return n<=1?n:fib(n-1)+fib(n-2); } @Override protected Integer compute() { if(n<=10){ return fib(n); } FibDemo f1=new FibDemo(n-1); f1.fork();//ForkJoinPool分配線程執(zhí)行子任務(wù) FibDemo f2=new FibDemo(n-2); return f2.compute()+f1.join();//執(zhí)行f2子任務(wù)+獲得f1子任務(wù)進(jìn)行完成的結(jié)果 } public static void main(String[] args){ FibDemo fib=new FibDemo(40); ForkJoinPool pool=new ForkJoinPool(); System.out.println(pool.invoke(fib)); } }
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/64701.html
摘要:如果把注釋去掉,則在所以非線程都結(jié)束時(shí),自動(dòng)終止。默認(rèn)所有從線程產(chǎn)生的線程也是線程。停止線程線程完成方法后,就進(jìn)入狀態(tài)。被標(biāo)示為的區(qū)塊會(huì)被監(jiān)控,任何線程要執(zhí)行區(qū)塊都必須先獲得指定的對(duì)象鎖定。 Tread和Runnable 定義線程 實(shí)現(xiàn)Runnable接口,重寫run()方法 繼承Thread類,重寫run()方法 啟動(dòng)線程 Runnable tortoise=ne...
摘要:一讓廣播明星黯然失色要建立頁面,需要?jiǎng)?chuàng)建用超文本標(biāo)記語言,編寫的文件,把它們放在一個(gè)服務(wù)器上二服務(wù)器能做什么服務(wù)器在互聯(lián)網(wǎng)上有一份全天候的工作。一、Web讓廣播明星黯然失色 要建立Web頁面,需要?jiǎng)?chuàng)建用超文本標(biāo)記語言(HyperText Markup Language,HTML)編寫的文件,把它們放在一個(gè)Web服務(wù)器上二、Web服務(wù)器能做什么? Web服務(wù)器在互聯(lián)網(wǎng)上有一份全天候的工...
摘要:包括元素的高度上下內(nèi)邊距上下邊框值,如果元素的的值為那么該值為。該值為元素的包含元素。最后,所有這些偏移量都是只讀的,而且每次訪問他們都需要重新計(jì)算。為了避免重復(fù)計(jì)算,可以將計(jì)算的值保存起來,以提高性能。 offsetHeight 包括元素的高度、上下內(nèi)邊距、上下邊框值,如果元素的style.display的值為none,那么該值為0。offsetWidth 包括元素的寬度、左...
摘要:如果需要收集參數(shù)化類型對(duì)象,只有使用警告這節(jié)討論,向參數(shù)可變的方法傳遞一個(gè)泛型類型的實(shí)例。異常不能拋出或捕獲泛型類的實(shí)例實(shí)際上,泛型類擴(kuò)展也是不合法的。 Object:所有類的超類 java中每個(gè)類都是由它擴(kuò)展而來,但是并不需要這樣寫:class Employee extends Object.如果沒有明確指出超類,Object類就被認(rèn)為是這個(gè)的超類??梢允褂肙bject類型的變量引用...
摘要:關(guān)鍵字作用調(diào)用超類方法調(diào)用超類構(gòu)造器關(guān)鍵字作用引用隱式參數(shù)如調(diào)用該類的其他構(gòu)造器在覆蓋一個(gè)方法時(shí),子類方法可見性不能低于超類方法阻止繼承類和方法目的確保它們不會(huì)在子類中改變語義。但是如果將一個(gè)類聲明為后面可以改變類變量的值了。 數(shù)據(jù)類型 整型 int 存儲(chǔ)要求:4byte 取值范圍:-2147483648 -- 2147483647(超過20億) short 存儲(chǔ)要求:2byte 取...
閱讀 2297·2021-11-25 09:43
閱讀 3188·2021-10-14 09:42
閱讀 3522·2021-10-12 10:12
閱讀 1602·2021-09-07 10:17
閱讀 1939·2019-08-30 15:54
閱讀 3213·2019-08-30 15:54
閱讀 1586·2019-08-30 15:53
閱讀 1956·2019-08-29 11:21