摘要:目標線程由運行狀態轉換為就緒狀態,也就是讓出執行權限,讓其他線程得以優先執行,但其他線程能否優先執行時未知的。函數的官方解釋是意思是使調用該函數的線程讓出執行時間給其他已就緒狀態的線程。
線程允許在同一個進程中同時存在多個程序控制流,即通過線程可以實現同時處理多個任務的功能。線程會共享進程范圍內的資源,例如內存句柄和文件句柄,但每個線程都有各自的程序計數器、棧以及局部變量。
多線程的實現 實現方式對于Java的多線程來說,我們學習的一般都是Thread和Runnable,通過我們使用如下代碼啟動一個新的線程:
private void startewThread() { new Thread(){ @Override public void run() { // 耗時任務 } }.start(); } 或者 private void startewThread1(){ new Thread(new Runnable() { @Override public void run() { // 耗時任務 } }).start(); }
第一種是覆寫了Thread類中的run方法執行任務;第二種是實現Runnable接口中的run方法執行任務。
那么Thread和Runnable是什么關系呢?
Thread和Runnable的關系實際上Thread也是一個Runnable,它實現了Runnable接口,在Thread類中有一個Runnable類型的target字段,代表要被執行在這個子線程的任務。相關代碼如下:
public class Thread implements Runnable { //要執行的目標任務 private Runnable target; //線程所屬的線程組 private ThreadGroup group; public Thread() { init(null, null, "Thread-" + nextThreadNum(), 0); } public Thread(Runnable target) { init(null, target, "Thread-" + nextThreadNum(), 0); } private void init(ThreadGroup g, Runnable target, String name, long stackSize, AccessControlContext acc) { if (name == null) { throw new NullPointerException("name cannot be null"); } this.name = name.toCharArray(); Thread parent = currentThread(); SecurityManager security = System.getSecurityManager(); if (g == null) { /* Determine if it"s an applet or not */ /* If there is a security manager, ask the security manager what to do. */ if (security != null) { g = security.getThreadGroup(); } /group為null則獲取當前線程的線程組 use the parent thread group. */ if (g == null) { g = parent.getThreadGroup(); } } /* checkAccess regardless of whether or not threadgroup is explicitly passed in. */ g.checkAccess(); /* * Do we have the required permissions? */ if (security != null) { if (isCCLOverridden(getClass())) { security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION); } } g.addUnstarted(); this.group = g; this.daemon = parent.isDaemon(); this.priority = parent.getPriority(); if (security == null || isCCLOverridden(parent.getClass())) this.contextClassLoader = parent.getContextClassLoader(); else this.contextClassLoader = parent.contextClassLoader; this.inheritedAccessControlContext = acc != null ? acc : AccessController.getContext(); //設置target this.target = target; setPriority(priority); if (parent.inheritableThreadLocals != null) this.inheritableThreadLocals = ThreadLocal.createInheritedMap(parent.inheritableThreadLocals); /* Stash the specified stack size in case the VM cares */ this.stackSize = stackSize; /* Set thread ID */ tid = nextThreadID(); } public synchronized void start() { /** * This method is not invoked for the main method thread or "system" * group threads created/set up by the VM. Any new functionality added * to this method in the future may have to also be added to the VM. * * A zero status value corresponds to state "NEW". */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group"s list of threads * and the group"s unstarted count can be decremented. */ group.add(this); boolean started = false; try { //調用native函數啟動線程 start0(); started = true; } finally { try { if (!started) { group.threadStartFailed(this); } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } } @Override public void run() { if (target != null) { target.run(); } } }
實際上最終被線程執行的任務是Runnable,而非Thread。Thread 只是對Runnable的包裝,并且通過一些狀態對Thread進行管理和調度。Runnable的聲明如下:
public interface Runnable { public void run(); }
當啟動一個線程時,如果Thread的target不為空,則會在子線程中執行這個target的run方法,否則虛擬機就會執行該線程自身的run方法。
線程的wait、sleep、join和yield先通過下面的表格來了解他們的區別:
函數名 | 作用 |
---|---|
wait | 當一個線程執行到wait()方法時,它就進入到一個和該對象相關的等待池中,同時釋放了對象的鎖,使得其他線程可以訪問。用戶可以使用notify、notifyAll或指定睡眠時間來喚醒當前等待池中的線程。 注意:wait、notify、notifyAll方法必須放在synchronized block中,否則則會拋出異常。 |
sleep | 該函數時Thread的靜態函數,作用是使調用線程進入睡眠狀態。因為sleep()Thread的靜態函數,因此它不能改變對象的鎖。所以當一個synchronized塊中調用sleep方法時,線程雖然休眠了,但是對象的鎖并沒有被釋放,其他線程無法訪問這個對象(即使睡著也持有對象鎖) |
join | 等待目標線程執行完成之后再繼續執行 |
yield | 線程禮讓。目標線程由運行狀態轉換為就緒狀態,也就是讓出執行權限,讓其他線程得以優先執行,但其他線程能否優先執行時未知的。 |
下面來看看wait、notify、notifyAll的使用:
public class WaitDemo { private static Object lockObject = new Object(); private static void waitAndNotifAll() { System.out.println("主線程運行"); //創建并啟動子線程 Thread thread = new WaitThread(); thread.start(); long startTime = System.currentTimeMillis(); try { //必須在synchronized塊中 synchronized (lockObject) { System.out.println("主線程等待"); lockObject.wait(); } } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } //被喚醒后繼續執行 long endTime = System.currentTimeMillis() - startTime; System.out.println("主線程繼續--->等待耗時: " + endTime + "ms"); } private static class WaitThread extends Thread { @Override public void run() { // TODO Auto-generated method stub synchronized (lockObject) { try { Thread.sleep(3000); //喚醒正在等待中的線程 lockObject.notifyAll(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub waitAndNotifAll(); } } 運行結果: 主線程運行 主線程等待 ... ... 主線程繼續--->等待耗時: 3001ms
wait、notify機制通常用于等待機制的實現,當條件未滿足時調用wait進入等待狀態,一旦條件滿足,調用notify或notifyAll喚醒等待的線程繼續執行。
join()join函數的原始解釋為“Block the cuurent thread(Thread.currentThread()) untile the receiver finishes its execution and dies。意思就是阻塞當前調用join函數的任務所在的線程,直到該任務執行完成后再繼續執行所在線程的任務。下面我們來看看一個具體是實例:
public class JoinDemo { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub joinDemo(); } static void joinDemo() { System.out.println("主線程開始執行"); Worker worker1 = new Worker("worker-1"); Worker worker2 = new Worker("worker-2"); worker1.start(); System.out.println("啟動線程1--執行完畢"); try { //等待worker1任務執行完成 worker1.join(); System.out.println("啟動線程2--執行完畢"); worker2.start(); //等待worker2任務執行完成 worker2.join(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("主線程繼續執行"); System.out.println("主線程執行完畢"); } static class Worker extends Thread { public Worker(String name) { super(name); } @Override public void run() { // TODO Auto-generated method stub try { Thread.sleep(3000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("Work in " + getName()); } } } 結果打印: 主線程開始執行 啟動線程1--執行完畢 Work in worker-1 啟動線程2--執行完畢 Work in worker-2 主線程繼續執行 主線程執行完畢
上述代碼的邏輯是主線程開始執行、啟動線程1、等待線程1執行完畢、啟動線程2、等待線程2執行完畢、繼續執行主線程任務。
yield()public static native void yield();
yield函數的官方解釋是"Causes the calling Thread to yiled execution time to another Thread that is ready to run",意思是使調用該函數的線程讓出執行時間給其他已就緒狀態的線程。
線程的執行是有時間片的,每個線程輪流占用CPU固定的時間,執行周期到了之后就讓出執行權給其他線程,而yield函數的功能就是主動讓出線程的執行權給其他線程,其他線程能否得到優先權就得看各個線程的狀態了。下面來看看一個具體的示例:
public class YieldDemo { /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub YieldThread t1 = new YieldThread("thread-1"); YieldThread t2 = new YieldThread("thread-2"); t1.start(); t2.start(); } static class YieldThread extends Thread { public YieldThread(String name) { // TODO Auto-generated constructor stub super(name); } @Override public synchronized void run() { // TODO Auto-generated method stub for(int i = 0; i < 5;i++) { System.out.println(this.getName() + " ; " + "線程優先級為: " + this.getPriority()+ "--->" + i); //當i為2時 調用當前線程yield函數 if (i== 2) { Thread.yield(); } } } } } 打印結果: thread-1 ; 線程優先級為: 5--->0 thread-2 ; 線程優先級為: 5--->0 thread-2 ; 線程優先級為: 5--->1 thread-2 ; 線程優先級為: 5--->2 thread-1 ; 線程優先級為: 5--->1 thread-1 ; 線程優先級為: 5--->2 thread-2 ; 線程優先級為: 5--->3 thread-2 ; 線程優先級為: 5--->4 thread-1 ; 線程優先級為: 5--->3 thread-1 ; 線程優先級為: 5--->4
從結果可知,thread-2首先執行到i的值為2,此時讓出執行權,thread-1得到執行權運行到i的值為2時讓出執行權,thread-2得到執行權執行任務結束,然后thread-1再繼續執行任務。
注意:yield僅在一個時間片內有效。
Callable、Future和FutureTask除了Runnable之外,Java還有Callable、Future和FutureTask這幾個與多線程相關的概念,與Runnable不同的是這個類型都只能運用到線程池中,而Runnable既能運用在Thread中,還能運用在線程池中。
CallableCallable與Runnable的功能大致相似不同的是Callable是一個泛型接口,它有一個泛型參數V,該接口中有一個返回值(類型為V)的Call函數,而Runnable中的run方法不能將結果返回至調用者。Callable的聲明如下:
public interface CallableFuture{ /** * Computes a result, or throws an exception if unable to do so. * * @return computed result * @throws Exception if unable to compute a result */ V call() throws Exception; }
Future為線程池制定了一個可管理的任務標準。它提供了對Runnable或者Callable任務的執行結果進行取消、查詢是否完成、獲取結果、設置結果操作,分別對應cancel、isDone、get、set函數。get方法會阻塞,直到任務返回結果。Future的聲明如下:
public interface FutureFutureTask{ //取消任務 boolean cancel(boolean mayInterruptIfRunning); //判斷任務是否已經取消 boolean isCancelled(); //判斷任務是否已經完成 boolean isDone(); //獲取結果,如果任務未完成則等待,直到完成,因此該函數會阻塞 V get() throws InterruptedException, ExecutionException; //獲取結果,如果未完成則等待,直到返回結果或timeout,該函數會阻塞 V get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException; }
Future只是定義了一些規范的接口,而FutureTask則是它的實現類。FutureTask實現了RunnableFuture
public class FutureTaskimplements RunnableFuture { ..... }
RunnableFuture
public interface RunnableFutureextends Runnable, Future { /** * Sets this Future to the result of its computation * unless it has been cancelled. */ void run(); }
FutureTask像Thread那樣包裝Runnable那樣對Runnable和Callable
public FutureTask(Callablecallable) { if (callable == null) throw new NullPointerException(); this.callable = callable; this.state = NEW; // ensure visibility of callable } public FutureTask(Runnable runnable, V result) { this.callable = Executors.callable(runnable, result); this.state = NEW; // ensure visibility of callable }
從上述代碼可以看出,如果注入的是Runnable則會被Executors.callable()函數轉換為Callable類型,即FutureTask最終都是執行Callable類型的任務,該轉換函數如下:
public staticCallable callable(Runnable task, T result) { if (task == null) throw new NullPointerException(); return new RunnableAdapter (task, result); } /** * Runnable適配器,將Runnable轉換為Callable */ static final class RunnableAdapter implements Callable { final Runnable task; final T result; RunnableAdapter(Runnable task, T result) { this.task = task; this.result = result; } public T call() { task.run(); return result; } }
由于FutureTask實現了Runnable,因此它既可以通過Thread包裝來執行,也可以提交給ExecuteService來執行,并且還可以通過get()函數來獲取執行結果,該函數會阻塞,直到結果返回。因此,FutureTask既是Future、Runnable,又是包裝了Callable(Runnable最終也會被轉換為Callable),它是這兩者的合體。
下面示例演示Runnable、Callable、FutureTask的運用,代碼如下:
public class FutureTaskDemo { //線程池 static ExecutorService mExecutor = Executors.newSingleThreadExecutor(); /** * 向線程池提交Runnable對象 */ private static void taskRunnable() { //無返回值 Future> future = mExecutor.submit(new Runnable() { @Override public void run() { // TODO Auto-generated method stub fibc(20); } }); System.out.println("taskRunnable: " + future.get()); } /** * 向線程池提交Callable對象 * @throws ExecutionException * @throws InterruptedException */ private static void taskCallable() throws InterruptedException, ExecutionException { Future線程池future = mExecutor.submit(new Callable () { @Override public Integer call() throws Exception { // TODO Auto-generated method stub return fibc(20); } }); //返回值 Integer result = future.get(); if (result != null) { System.out.println("taskCallable: " + result); } } /** * 向線程池提交FutureTask對象 * @throws ExecutionException * @throws InterruptedException */ private static void taskFutureTask() throws InterruptedException, ExecutionException { FutureTask futureTask = new FutureTask<>(new Callable () { @Override public Integer call() throws Exception { // TODO Auto-generated method stub return fibc(20); } }); mExecutor.submit(futureTask); Integer result = futureTask.get(); if (result != null) { System.out.println("taskFutureTask: " + result); } } /** * Thread包裝FutureTask * @throws InterruptedException * @throws ExecutionException */ private static void taskThread() throws InterruptedException, ExecutionException { FutureTask futureTask = new FutureTask<>(new Callable () { @Override public Integer call() throws Exception { // TODO Auto-generated method stub return fibc(20); } }); new Thread(futureTask).start(); Integer result = futureTask.get(); if (result != null) { System.out.println("taskThread: " + result); } } /** * 斐波那契數列 * @param num * @return */ private static int fibc(int num) { if (num == 0) { return 0; } if (num == 1) { return 1; } return fibc(num - 1) + fibc(num - 2); } /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub try { taskRunnable(); taskCallable(); taskFutureTask(); taskThread(); } catch (Exception e) { // TODO Auto-generated catch block e.printStackTrace(); } } } 打印結果: taskRunnable: null taskCallable: 6765 taskFutureTask: 6765 taskThread: 6765
當我們需要頻繁地創建多個線程進行耗時操作時,每次都通過new Thread實現并不是一種好的方式,每次new Thread新建銷毀對象性能較差,線程缺乏統一的管理,可能會無限制地創建新的線程,線程之間相互競爭從而占用過多系統資源導致死鎖,并且缺乏定期執行、定時執行、線程中斷等功能。
Java提供了4中線程池,它能夠有效地管理、調度線程,避免過多的資源消耗,它強大到幾乎不需要開發人員自定義的程序。它的優點如下:
重用存在的線程,減少對象創建、銷毀的開銷;
可有效控制最大并發線程數,提高系統資源的使用率,同時避免過多資源競爭,避免堵塞;
提供定時執行、定期執行、單線程、并發數控制等功能;
線程池的原理就是會創建創建多個線程并且對這些線程進行管理,提交給線程的任務 會被線程池指派給其中的線程執行,提供線程池的統一調度、管理。使得多線程的使用更簡單、高效。
線程池都實現了ExecutorService接口,該接口定義了線程池需要實現的接口,如submit、execute、shutdown等。它的實現有ThreadPoolExecutor和ScheduledPoolExecutor,ThreadPoolExecutor是運行最多的線程池實現,ScheduledPoolExecutor則用于執行周期性任務。
啟動指定數量的線程-ThreadPoolExecutorThreadPoolExecutor的功能是啟動指定數量的線程以及將任務添加到一個隊列中,并且將任務分發給空閑的線程。
ExecutorService的生命周期包括3中狀態:運行、關閉、終止,創建后進入運行狀態,調用shutdown()方法時便進入了關閉狀態,此時ExecutorService不再接受新的任務,但它繼續執行完已經提交的任務,當所有已經提交的任務都執行完后,就變成終止狀態。
ThreadPoolExecutor的構造函數如下:
public ThreadPoolExecutor(int corePoolSize, int maximumPoolSize, long keepAliveTime, TimeUnit unit, BlockingQueueworkQueue, ThreadFactory threadFactory, RejectedExecutionHandler handler)
下面對參數進行詳細說明:
參 數 名 | 作 用 |
---|---|
corePoolSize | 線程池中所保存的核心線程數。 |
maximumPoolSize | 線程池所容納的最大線程數,當活動線程達到這個數值后,后續的任務將會被阻塞 |
keepAliveTime | 非核心線程閑置時的超時時間,超出這個時長,非核心線程就會被回收 |
unit | 用于指定keepAliveTime參數的時間單位,有毫秒、秒、分鐘等 |
workQueue | 線程池中的任務隊列,如果線程池的線程數量已經達到核心線程數并且當前所有線程都處于活動狀態時,則將新任務放到此隊列中等待執行 |
threadFactory | 線程工廠,為線程池提供創建新線程的功能,通常不需要設置 |
handler | 拒絕策略,當線程池與workQueue隊列都滿了的情況下,對新任務采取的處理策略 |
線程池參數也可以參考這篇文章http://liuguoquan727.github.io/2016/04/25/Android%E7%9A%84%E7%BA%BF%E7%A8%8B%E5%92%8C%E7%BA%BF%E7%A8%8B%E6%B1%A0/:
其中workQueue有下列幾個常用的實現:
ArrayBlockingQueue
基于數組結構的有界隊列,此隊列按FIFO原則對任務進行排序。如果隊列滿了還有任務進來,則調用拒絕策略
LinkedBlockingQueue
基于鏈表結構的無界隊列,此隊列按FIFO原則對任務進行排序。因為它是無界的,所以才有此隊列后線程池將忽略handler參數。
SynchronousQueue
直接將任務提交給線程而不是將它加入到隊列,實際上該隊列是空的。每個插入的操作必須等到另一個調用移除的操作,如果新任務來了線程池沒有任何可用線程處理的話,則調用拒絕策略。
PriorityBlockingQueue
具有優先級的隊列的有界隊列,可用自定義優先級,默認是按自然排序的。
此外,當線程池與workQueue隊列都滿了的情況下,對新加任務采取的處理策略也有幾個默認實現:
AbortPolicy
拒絕任務,拋出RejectedExecutionException異常,線程池默認策略
CallerRunsPolicy
拒絕新任務加入,如果該線程池還沒有被關閉,那么將這個新任務執行在調用線程中
DiscardOldestPolicy
如果執行程序還沒有關閉,則將位于工作隊列頭部的任務刪除,然后重試執行程序(如果再次失敗,則重復此過程)
DiscardPolicy
加不進的任務都被拋棄了,同時沒有異常拋出
newFixedThreadPool對應Android平臺來說,最常使用的就是通過Executors.newFixedThreadPool(int size)函數來啟動固定數量的線程池,代碼如下
public class ExectorsDemo { private static final int MAX = 10; /** * @param args */ public static void main(String[] args) { // TODO Auto-generated method stub try { fixedThreadPool(MAX); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (ExecutionException e) { // TODO Auto-generated catch block e.printStackTrace(); } } private static void fixedThreadPool(int size) throws InterruptedException, ExecutionException { ExecutorService service = Executors.newFixedThreadPool(size); for(int i = 0;i < MAX;i++) { //提交任務 Futuretask = service.submit(new Callable () { @Override public Integer call() throws Exception { // TODO Auto-generated method stub System.out.println("執行線程: " + Thread.currentThread().getName()); return fibc(20); } }); //獲取結果 System.out.println("第"+i+"次計算結果: " + task.get()); } } /** * 斐波那契數列 * @param num * @return */ private static int fibc(int num) { if (num == 0) { return 0; } if (num == 1) { return 1; } return fibc(num - 1) + fibc(num - 2); } } 結果打印: 執行線程: pool-1-thread-1 第0次計算結果: 6765 執行線程: pool-1-thread-2 第1次計算結果: 6765 執行線程: pool-1-thread-3 第2次計算結果: 6765 執行線程: pool-1-thread-1 第3次計算結果: 6765 執行線程: pool-1-thread-2 第4次計算結果: 6765 執行線程: pool-1-thread-3 第5次計算結果: 6765 執行線程: pool-1-thread-1 第6次計算結果: 6765 執行線程: pool-1-thread-2 第7次計算結果: 6765 執行線程: pool-1-thread-3 第8次計算結果: 6765 執行線程: pool-1-thread-1 第9次計算結果: 6765
在上述例子中,我們啟動了含有3個線程的線程池,調用的是Executors的newFixedThreadPool函數,該函數的實現為
public static ExecutorService newFixedThreadPool(int nThreads) { return new ThreadPoolExecutor(nThreads, nThreads, 0L, TimeUnit.MILLISECONDS, new LinkedBlockingQueue()); }
可知它的corePoolSize和MaxnumPoolSize值都是nThreads,并且設置keepAliveTime為0毫秒,最后設置無界任務隊列,這樣該線程池中就含有固定個數的線程,并且能夠容納無數個任務。
newCacheThreadPool有時可能需要任務盡可能快地被執行,這就需要線程池中的線程足夠多也就是說此時需要拿空間來換時間,線程越多占用的內存消耗就越大。因此,我們可能需要一種場景,如果來了一個新的任務,并且沒有空閑線程可用,此時必須馬上創建一個線程來立即執行任務。我們可以通過Executors的newCacheThreadPool函數來實現。
private static void newCacheThreadPool() throws InterruptedException, ExecutionException { ExecutorService service = Executors.newCachedThreadPool(); for(int i = 0;i < MAX;i++) { //提交任務 service.submit(new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("執行線程: " + Thread.currentThread().getName() + ",結果:" + fibc(20)); } }); } } 結果打印 執行線程: pool-1-thread-1,結果:6765 執行線程: pool-1-thread-2,結果:6765 執行線程: pool-1-thread-4,結果:6765 執行線程: pool-1-thread-6,結果:6765 執行線程: pool-1-thread-8,結果:6765 執行線程: pool-1-thread-5,結果:6765 執行線程: pool-1-thread-3,結果:6765 執行線程: pool-1-thread-7,結果:6765 執行線程: pool-1-thread-10,結果:6765 執行線程: pool-1-thread-9,結果:6765
從上述結果可以看出,為了保證吞吐量,該線程池為每個任務都創建了一個線程,當然這是在沒有線程空閑的情況下創建的新的線程。假設執行前5個任務時都創建了一個線程,執行到底6個任務時剛好前面的第一個任務執行完畢,此時線程1空閑,那么第六個任務就會被執行在第一個線程中,而不是重新創建。
執行周期性任務的線程-ScheduledPoolExecutor通過Executors的newScheduledThreadPool函數即可創建定時執行任務的線程池。
private static void newScheduledThreadPool() throws InterruptedException, ExecutionException { ScheduledExecutorService service = Executors.newScheduledThreadPool(4); // 參數2為第一次延遲的時間,參數2為執行周期 service.scheduleAtFixedRate((new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("執行線程: " + Thread.currentThread().getName() + ",定時計算 1結果:" + fibc(20)); } }), 1, 2, TimeUnit.SECONDS); // 參數2為第一次延遲的時間,參數2為執行周期 service.scheduleAtFixedRate((new Runnable() { @Override public void run() { // TODO Auto-generated method stub System.out.println("執行線程: " + Thread.currentThread().getName() + ",定時計算2結果:" + fibc(30)); } }), 1, 2, TimeUnit.SECONDS); } 打印結果: 執行線程: pool-1-thread-1,定時計算 1結果:6765 執行線程: pool-1-thread-2,定時計算2結果:832040 執行線程: pool-1-thread-1,定時計算 1結果:6765 執行線程: pool-1-thread-3,定時計算2結果:832040 執行線程: pool-1-thread-1,定時計算 1結果:6765 執行線程: pool-1-thread-4,定時計算2結果:832040 執行線程: pool-1-thread-1,定時計算 1結果:6765 執行線程: pool-1-thread-3,定時計算2結果:832040 執行線程: pool-1-thread-2,定時計算 1結果:6765 執行線程: pool-1-thread-4,定時計算2結果:832040
該線程池有4個線程,我們指定了兩個定時任務,因此該線程池中有兩個線程來定時執行任務,哪個線程空閑就調度哪個線程來執行任務。
同步集合 程序中的優化策略-CopyOnWriteCopy-On-Write是一種用于程序設計中的優化策略,其基本思路是,從多個線程共享同一個列表,當某個線程想要修改這個列表的元素時,會把列表中的元素復制一份,然后進行修改,修改完成之后再將新的元素設置給這個列表,這是一種延時懶惰策略。這樣做的好處是我們可以對CopyOnWrite容器進行并發的讀而不需要加鎖,因為當前容器不會添加、移除任何元素。所有CopyOnWrite容器也是一種讀寫分離的思想,讀和寫不同的容器。從JDK1.5起Java并發包提供了兩個使用CopyOnWrite機制實現的并發容器,它們是CopyOnWriteArrayList和CopyOnWriteSet。
通過這種寫時拷貝的原理可以將讀、寫分離,使并發場景下對列表的操作效率得到提高,但它的缺點是,在添加、移除元素時占用的內存空間翻了一倍,因此,這是以空間換時間的策略。
提高并發效率-ConcurrentHasMapHashTable使用synchronized來保證線程安全,但在線程競爭激烈的情況下HashTable的效率非常低下。因為當一個線程訪問HashTable同步方法時,其他線程訪問HashTable的同步方法時,可能會進入阻塞或輪詢狀態。如線程1使用put進行添加元素,線程2不但不能使用put方法添加元素,并且也不能使用個圖方法來獲取元素,所以競爭越激烈效率越低。
HashTable在競爭激烈的并發環境下表現出效率低下的原因是因為所有訪問HashTable的線程都必須競爭同一把鎖。
假如容器里有多把鎖,每一把鎖用于鎖容器其中一部分數據,那么當多線程訪問容器里不同數據段的數據時,線程間就不會存在鎖競爭,從而可以有效的提高并發訪問效率,這就是ConcurrentHasMap所使用的鎖分段技術,首先將數據分成一段一段的存儲,然后給每一段數據配一把鎖,當一個線程占用鎖訪問其中一個段數據的時候,其他段的數據也能被其他線程訪問。有些方法需要跨段,如size()和containsValue(),它們可能需要鎖定整個表而不僅是某個段,這需要按順序鎖定所有段,操作完畢后,又按順序釋放所有段的鎖。
BlockingQueue的重要方法:
函 數 名 | 作 用 |
---|---|
add(e) | 把元素e添加到隊列中,成功返回true,否則拋出異常 |
offer(e) | 把元素e添加到隊列中,成功返回true,否則返回false |
offer(e,time,unit) | 把元素e添加到隊列中,成功返回true,否則在等待指定的時間之后繼續嘗試添加,如果失敗則返回false |
put(e) | 把元素e添加到隊列中,如果隊列不能容納,則調用此方法的線程被阻塞直到隊列里面有空間再繼續添加 |
take() | 取出隊列中的首個元素,若隊列為空,則線程進入等待直到隊列中新的元素加入為止 |
poll(time,unit) | 取出并移除隊列中的首個元素,如果在指定的時間內沒有獲取元素,則返回null |
element() | 獲取隊首元素,如果隊列為null,那么拋出NoSuchElementException異常 |
peek() | 獲取隊首元素,如果隊列為空,那么返回null |
remove() | 獲取并移除隊首元素,如果隊列為空,那么拋出NoSuchElementException異常 |
BlockingQueue常用的實現有:
ArrayBlockingQueue
LinkedBlockingQueue
LinkedBlockingDequeue
ConcurrentLinkedQueue
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://m.specialneedsforspecialkids.com/yun/66131.html
摘要:并發編程實戰水平很高,然而并不是本好書。一是多線程的控制,二是并發同步的管理。最后,使用和來關閉線程池,停止其中的線程。當線程調用或等阻塞時,對這個線程調用會使線程醒來,并受到,且線程的中斷標記被設置。 《Java并發編程實戰》水平很高,然而并不是本好書。組織混亂、長篇大論、難以消化,中文翻譯也較死板。這里是一篇批評此書的帖子,很是貼切。俗話說:看到有這么多人罵你,我就放心了。 然而知...
摘要:線程池的作用降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的資源浪費。而高位的部分,位表示線程池的狀態。當線程池中的線程數達到后,就會把到達的任務放到中去線程池的最大長度。默認情況下,只有當線程池中的線程數大于時,才起作用。 線程池的作用 降低資源消耗。通過重復利用已創建的線程降低線程創建和銷毀造成的資源浪費。 提高響應速度。當任務到達時,不需要等到線程創建就能立即執行...
閱讀 2818·2021-10-26 09:48
閱讀 1684·2021-09-22 15:22
閱讀 4063·2021-09-22 15:05
閱讀 621·2021-09-06 15:02
閱讀 2612·2019-08-30 15:52
閱讀 2118·2019-08-29 18:38
閱讀 2763·2019-08-28 18:05
閱讀 2336·2019-08-26 13:55