日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當前位置: 首頁 > 编程资源 > 编程问答 >内容正文

编程问答

java并发编程之美-阅读记录11

發布時間:2024/9/15 编程问答 26 豆豆
生活随笔 收集整理的這篇文章主要介紹了 java并发编程之美-阅读记录11 小編覺得挺不錯的,現在分享給大家,幫大家做個參考.

java并發編程實踐

11.1ArrayBlockingQueue的使用

  有關logback異步日志打印中的ArrayBlockingQueue的使用

  1、異步日志打印模型概述

    在高并發、高流量并且響應時間要求比較小的系統中同步打印日志在性能上已經滿足不了了,這是以因為打印本身是需要寫磁盤的,寫磁盤操作會暫時阻塞調用打印日志的業務系統,這會造成調用線程的響應時間增加。

    ? ?----- 》》》

?

    異步日志打印,是將打印日志任務放入一個隊列后就返回,然后使用一個線程專門從隊列中獲取日志任務,并將其寫入磁盤。

?  2、異步日志實現

   一般情況下的同步日志logback.xml配置如下:(pattern會特殊定制)

<configuration><appender name="FILE" class="ch.qos.logback.core.FileAppender"><file>myapp.log</file><encoder><pattern>%logger{35} - %msg%n</pattern></encoder></appender><root level="DEBUG"><appender-ref ref="FILE" /></root> </configuration>

  而異步日志的logback.xml配置如下:多了個AsyncAppender配置,該類就是實現異步日志的關鍵類

<configuration><appender name="FILE" class="ch.qos.logback.core.FileAppender"><file>myapp.log</file><encoder><pattern>%logger{35} - %msg%n</pattern></encoder></appender><appender name="ASYNC" class="ch.qos.logback.classic.AsyncAppender"><appender-ref ref="FILE" /></appender><root level="DEBUG"><appender-ref ref="ASYNC" /></root> </configuration>

  3、異步日志

  類圖:

    由類圖可以看出,AsyncAppender繼承AsyncAppenderBase類,實現AppenderAttachable接口,而關鍵實現異步方法的是AsyncAppenderBase類,其中blockingQueue是有界的阻塞隊列,queueSize表示有界隊列的元素個數,worker則是工作線程,也就是也不打印日志的消費者線程,aai則是一個appender的裝飾器,里邊存放的同步日志的appender,其中appenderCount記錄aai里邊附加的同步appender的個數(這個和配置文件相對應,一個異步的appender對應一個同步的appender),neverBlock用來指示當同步隊列已滿時是否阻塞打印日志線程,discardingThreshold是一個閾值,當日志隊列里邊的空閑元素個數小于該值時,新來的某些級別的日志就會直接被丟棄。

  

  4、AsyncAppenderBase類

  何時創建日志隊列?

public void start() {if (isStarted())return;if (appenderCount == 0) {addError("No attached appenders found.");return;}if (queueSize < 1) {addError("Invalid queue size [" + queueSize + "]");return;}// 創建一個ArrayBlockingQueue阻塞隊列,queueSize默認為256,創建阻塞隊列的原因是:防止生產者過多,造成隊列中元素過多,產生OOM異常blockingQueue = new ArrayBlockingQueue<E>(queueSize);// 如果discardingThreshold未定義的話,默認為queueSize的1/5if (discardingThreshold == UNDEFINED)discardingThreshold = queueSize / 5;addInfo("Setting discardingThreshold to " + discardingThreshold);// 將工作線程設置為守護線程,即當jvm停止時,即使隊列中有未處理的元素,也不會在進行處理worker.setDaemon(true);// 為線程設置name便于調試worker.setName("AsyncAppender-Worker-" + getName());// make sure this instance is marked as "started" before staring the worker Thread// 啟動線程super.start();worker.start();}

當隊列已滿時,是丟棄老的日志還是阻塞日志打印線程直到隊列有空余元素時?? ?這個問題需要關注append方法

@Overrideprotected void append(E eventObject) {// 判斷隊列中的元素數量是否小于discardingThreshold,如果小于的話,并且日志等級小于info的話,則直接丟棄這些日志任務if (isQueueBelowDiscardingThreshold() && isDiscardable(eventObject)) {return;}preprocess(eventObject);// 日志入隊put(eventObject);}private boolean isQueueBelowDiscardingThreshold() {return (blockingQueue.remainingCapacity() < discardingThreshold);}// 子類重寫的方法 判斷日志等級protected boolean isDiscardable(ILoggingEvent event) {Level level = event.getLevel();return level.toInt() <= Level.INFO_INT;}private void put(E eventObject) {// 判斷是否阻塞(默認為false),則會調用阻塞隊列的put方法if (neverBlock) {blockingQueue.offer(eventObject);} else {putUninterruptibly(eventObject);}}// 可中斷的阻塞put方法private void putUninterruptibly(E eventObject) {boolean interrupted = false;try {while (true) {try {blockingQueue.put(eventObject);break;} catch (InterruptedException e) {interrupted = true;}}} finally {if (interrupted) {Thread.currentThread().interrupt();}}}// ArrayBlockingQueue的put方法,當count==len時,調用await方法阻塞線程public void put(E e) throws InterruptedException {checkNotNull(e);final ReentrantLock lock = this.lock;lock.lockInterruptibly();try {while (count == items.length)notFull.await();enqueue(e);} finally {lock.unlock();}}

addAppender方法,有該方法可以看出,一個異步的appender只能綁定一個同步appender,這個appender會被放入AppenderAttachableImpl的appenderList列表里邊。

public void addAppender(Appender<E> newAppender) {if (appenderCount == 0) {appenderCount++;addInfo("Attaching appender named [" + newAppender.getName() + "] to AsyncAppender.");aai.addAppender(newAppender);} else {addWarn("One and only one appender may be attached to AsyncAppender.");addWarn("Ignoring additional appender named [" + newAppender.getName() + "]");}}

注意內部類Worker的run方法(消費者,將日志寫入磁盤的線程方法)

class Worker extends Thread {public void run() {AsyncAppenderBase<E> parent = AsyncAppenderBase.this;AppenderAttachableImpl<E> aai = parent.aai;// loop while the parent is started 一直循環知道線程被中斷while (parent.isStarted()) {try {// 從阻塞隊列中獲取元素,交由給同步的appender將日志打印到磁盤E e = parent.blockingQueue.take();aai.appendLoopOnAppenders(e);} catch (InterruptedException ie) {break;}}addInfo("Worker thread will flush remaining events before exiting. ");//執行到這里說明該線程被中斷,則把隊列里邊的剩余日志任務刷新到磁盤for (E e : parent.blockingQueue) {aai.appendLoopOnAppenders(e);parent.blockingQueue.remove(e);}aai.detachAndStopAllAppenders();}}

?

11.2Tomcat的NioEndPoint中的ConcurrentLinkedQueue

  Tomcat的容器結構:

  

  其中Connector是一個橋梁,他把server和Engine連接起來了,Connector的作用是接受客戶端請求,然后把請求委托給Engine。在Connector中使用Endpoint來進行處理根據不同的處理方式可分為NioEndpoint、JIoEndpoint、AprEndpoint。

 NioEndpoint中三大組件的關系:

  

  Acceptor作用:是套接字的接受線程,用來接受用戶的請求,并把請求封裝進Poller的隊列,一個Connector中只有一個Acceptor。

  Poller偶用:是套接字的處理線程,每一個Poller內部都有一個獨有的隊列,Poller線程則從自己的隊列里邊獲取具體的事件任務,然后將其交給Worker進行處理,其中Poller的線程數和cpu個數有關。

  Worker:是時間處理請求的線程,Worker只是組件的名字,真正做事情的是SocketProcessor。

  由此可見,tomcat使用隊列將接受請求和處理請求操作進行解耦,實現異步處理。

  其實Tomcat中Endpoint中的每一個Poller里邊都維護著一個ConcurrentLinkedQueue隊列,用來緩存請求任務,其本身也是一個多生產者-單消費者模型。

  1、Acceptor生產者

  Acceptor線程的作用:接受客戶端請求并將其放入Poller中的隊列。

  時序圖(簡單):

  

?

?11.7創建線程和線程池時要指定與業務相關的名稱

  在日常開發過程中,當在一個應用中需要創建多個線程或者線程池時,最好給每個線程或者每個線程池根據業務類型設置具體的名稱,以便在出現問題時方便定位。

  1、創建多個線程案例

package com.nxz.blog.otherTest;public class TestThread0014 {public static void main(String[] args) {// 假設該線程操作保單模塊Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("操作保單");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}// 手動拋異常throw new NullPointerException();}});// 假設該模塊是投保模塊Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("操作投保");}});t1.start();t2.start();} }

?  以上代碼執行結果: 從異常信息中只能看出是Thread-0出現問題了,不能確定具體是哪一個模塊出的問題,確認問題困難

操作保單 操作投保 Exception in thread "Thread-0" java.lang.NullPointerExceptionat com.nxz.blog.otherTest.TestThread0014$1.run(TestThread0014.java:18)at java.lang.Thread.run(Thread.java:748)

看Thread構造函數:

public Thread(Runnable target) {// 當參數中沒有提供name時,默認使用nextThreadNum生成編號來當做線程nameinit(null, target, "Thread-" + nextThreadNum(), 0);}

修改以上代碼:即將創建線程時使用多個參數的構造函數:

public static void main(String[] args) {// 假設該線程操作保單模塊Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("操作保單");try {Thread.sleep(500);} catch (InterruptedException e) {e.printStackTrace();}// 手動拋異常throw new NullPointerException();}}, "保單模塊");// 假設該模塊是投保模塊Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {System.out.println("操作投保");}}, "投保模塊");t1.start();t2.start();}

  執行結果: 很明顯的看出是保單模塊產生了問題

操作保單 操作投保 Exception in thread "保單模塊" java.lang.NullPointerExceptionat com.nxz.blog.otherTest.TestThread0014$1.run(TestThread0014.java:19)at java.lang.Thread.run(Thread.java:748)

  2、創建線程池時也要指定線程池的名稱

  由線程池的構造函數,可以看出,默認的名稱是類似“pool-1-thread-1”這樣的名稱

public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,RejectedExecutionHandler handler) {this(corePoolSize, maximumPoolSize, keepAliveTime, unit, workQueue,Executors.defaultThreadFactory(), handler);}public static ThreadFactory defaultThreadFactory() {return new DefaultThreadFactory();}DefaultThreadFactory() {SecurityManager s = System.getSecurityManager();group = (s != null) ? s.getThreadGroup() :Thread.currentThread().getThreadGroup();namePrefix = "pool-" +poolNumber.getAndIncrement() +"-thread-";}

因此使用ThreadPoolExecutor構建線程池的時候自定義ThreadFactory的名稱(即仿照DefaultThreadFactory仿照一個CustomerThreadFactory,只需修改namePrefix即可):

package com.nxz.blog.otherTest;import java.util.concurrent.*; import java.util.concurrent.atomic.AtomicInteger;public class TestThread0015 {static class NamedThreadFactory implements ThreadFactory {private static final AtomicInteger poolNumber = new AtomicInteger();private final ThreadGroup group;private final AtomicInteger threadNumber = new AtomicInteger();private final String namePrefix;public NamedThreadFactory(String name) {SecurityManager s = System.getSecurityManager();group = (s != null) ? s.getThreadGroup() : Thread.currentThread().getThreadGroup();if (name == null || name.isEmpty()) {name = "pool";}namePrefix = name + "-" + poolNumber.getAndIncrement() + "-thread-";}@Overridepublic Thread newThread(Runnable r) {// 給線程也設置名稱Thread t = new Thread(group, r, namePrefix + threadNumber.getAndIncrement(), 0);if (t.isDaemon()) {t.setDaemon(false);}if (t.getPriority() != Thread.NORM_PRIORITY) {t.setPriority(Thread.NORM_PRIORITY);}return t;}}static ExecutorService executorServicePolicy = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,new LinkedBlockingQueue<>(), new NamedThreadFactory("保單模塊"));static ExecutorService executorServiceProposal = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,new LinkedBlockingQueue<>(), new NamedThreadFactory("投保模塊"));public static void main(String[] args) {executorServicePolicy.execute(new Runnable() {@Overridepublic void run() {System.out.println("aaaa");throw new NullPointerException();}});executorServiceProposal.execute(new Runnable() {@Overridepublic void run() {System.out.println("bbbb"); // throw new NullPointerException();}});executorServicePolicy.shutdown();executorServiceProposal.shutdown();} }

  以上代碼執行結果:可以明顯看出是保單模塊線程池報錯了

aaaa Exception in thread "保單模塊-0-thread-0" java.lang.NullPointerException bbbbat com.nxz.blog.otherTest.TestThread0015$1.run(TestThread0015.java:51)at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149)at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624)at java.lang.Thread.run(Thread.java:748)

?

11.8使用線程池的情況下當程序結束的時候記得調用shutdown方法關閉線程池

  在日常開發過程中,為了復用線程,經常會用到線程池,然而使用完線程池后如果不調用shutdown方法關閉線程池,則會導致線程池資源得不到釋放。

  1、問題復現

package com.nxz.blog.otherTest;import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors;public class TestThread0013 {static void executeOne() {ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.execute(new Runnable() {@Overridepublic void run() {System.out.println("execute One");}});//executorService.shutdoan();}static void executeTwo() {ExecutorService executorService = Executors.newSingleThreadExecutor();executorService.execute(new Runnable() {@Overridepublic void run() {System.out.println("execute Two");}});//executorService.shutdoan();}public static void main(String[] args) {System.out.println("main start");executeOne();executeTwo();System.out.println("end");}}

執行代碼:當前主線程并沒有結束,即資源沒有釋放; 如果將注釋放開的話,則主線程會結束

? ----- 》》

那為什么不執行shutdown時,不釋放資源?

  在基礎篇,曾經說過守護線程和用戶現場,jvm退出的條件是當前不存在用戶線程,而線程池默認創建的線程都是用戶線程,而線程池中的線程會一直存在,所有jvm會一直運行。

?

11.9使用FutureTask時需要注意的事情

?  線程池使用FutureTask時如果把拒絕策略設置為DiscardPolicy或者DiscardOldestPolicy,并且在被拒絕任務的Future對象上調用了無參的get方法,那么調用線程會一直阻塞。

  1、問題復現

package com.nxz.blog.otherTest;import java.util.concurrent.*;public class TestThread0012 {private final static ThreadPoolExecutor executorService = new ThreadPoolExecutor(1, 1, 1L, TimeUnit.MINUTES,new ArrayBlockingQueue<Runnable>(1), new ThreadPoolExecutor.DiscardPolicy());public static void main(String[] args) throws ExecutionException, InterruptedException {Future<?> futureOne = executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("start runnable one");try {Thread.sleep(5000);} catch (InterruptedException e) {e.printStackTrace();}}});Future<?> futureTwo = executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("start runnable two");}});Future futureThree = null;try {futureThree = executorService.submit(new Runnable() {@Overridepublic void run() {System.out.println("start runnable three");}});} catch (Exception e) {System.out.println(e.getLocalizedMessage());}System.out.println("futureOne" + futureOne.get());System.out.println("futureTwo" + futureTwo.get());// 代碼執行到該位置后不向下執行了,也就是futureThree.get方法阻塞了System.out.println("futureThree" + futureThree.get());System.out.println("end");executorService.shutdown();} }

  那么為什么futureThree.get方法阻塞,需要看FutureTask中的get方法是怎樣實現的(什么情況下會返回值,什么情況下會阻塞):

    先分析上邊代碼的流程,線程池的大小為1,有界隊列也是1,也就是說,當阻塞隊列中已經有一個任務時,在submit任務時,就會執行拒絕策略,上邊代碼futureOne的任務里邊有個睡眠(該作用就是使futureTwo進入阻塞隊列,futureThree中的任務執行拒絕策略),那么看下submit的代碼流程干了什么:

// 提交任務時,會現將runnable封裝為一個Future對象public Future<?> submit(Runnable task) {if (task == null) throw new NullPointerException();RunnableFuture<Void> ftask = newTaskFor(task, null);execute(ftask);return ftask;}// newTaskFor方法,直接創建了一個FutureTask對象,而FutureTask對象的默認狀態是NEWprotected <T> RunnableFuture<T> newTaskFor(Runnable runnable, T value) {return new FutureTask<T>(runnable, value);}public FutureTask(Runnable runnable, V result) {this.callable = Executors.callable(runnable, result);this.state = NEW; // ensure visibility of callable}

  從上邊代碼可以看出submit方法會將任務封裝成一個FutureTask對象,而該對象默認的狀態是NEW,那么繼續看execute方法(在該方法里邊會根據當前任務的個數來判斷是否執行阻塞隊列):

public void execute(Runnable command) {if (command == null)throw new NullPointerException();int c = ctl.get();if (workerCountOf(c) < corePoolSize) {if (addWorker(command, true))return;c = ctl.get();}if (isRunning(c) && workQueue.offer(command)) {int recheck = ctl.get();if (! isRunning(recheck) && remove(command))reject(command);else if (workerCountOf(recheck) == 0)addWorker(null, false);}else if (!addWorker(command, false))// 執行拒絕策略reject(command);}// 因為創建線程池時,配置的是DIscardPolicy,因此看該對象中reject方法執行了什么操作final void reject(Runnable command) {handler.rejectedExecution(command, this);}public static class DiscardPolicy implements RejectedExecutionHandler {public DiscardPolicy() { }// 可以看到rejfect什么也沒有執行public void rejectedExecution(Runnable r, ThreadPoolExecutor e) {}}

  從上邊代碼可以看出,當執行拒絕策略時,什么都沒有執行,也就是說沒有對當前任務做任何操作。(而下邊這個future的get方法,能夠返回值的時候,futureTask的狀態必須大于COMPLETING,這和上邊說的不符合,因此,當futureThree.get時,會阻塞(當拒絕策略設置為DIscardOldestPolicy時,同樣有該問題))

  從下邊FutureTask中的get方法可以看出,當Future的狀態(future是有狀態的)值,小于COMPLETING時,就會阻塞,大于的話就會返回一個值

public V get() throws InterruptedException, ExecutionException {int s = state;if (s <= COMPLETING)s = awaitDone(false, 0L);return report(s);} private volatile int state;private static final int NEW = 0;private static final int COMPLETING = 1;private static final int NORMAL = 2;private static final int EXCEPTIONAL = 3;private static final int CANCELLED = 4;private static final int INTERRUPTING = 5;private static final int INTERRUPTED = 6;

  解決阻塞的方法:1、可以將拒絕策略設置為默認(AbortPolicy)2、盡量使用帶超時時間的get方法,這樣即使會阻塞,也會因為超時而返回。3、自定義拒絕策略,重寫rejectedExecution方法,將futureTask的狀態設置為大于COMPLETING即可。

?

?

?11.10ThreadLocal使用不當導致的內存泄漏

  內存泄漏Memory leak:是指程序中已經動態分配的堆內存,由于某種原因程序未釋放或無法釋放,造成系統內存浪費,導致程序運行速度減慢,甚至程序崩潰等后果。

  •   有關內存泄漏的博客:https://blog.csdn.net/xlgen157387/article/details/78298840

  在基礎篇有介紹,ThreadLocal只是一個工具類,具體存放變量時線程的threadlocals變量。該變量是ThreadLocalMap類型的變量,如下圖:

  

  由圖可知,ThreadLocalMap內部是一個Entry數組,Entry繼承自WeakReference,Entry內部的value用來存放ThreadLocal的set方法傳遞的池,key則是ThreadLocal對象引用。

  Entry構造:

  key傳遞給WeakReference構造,也就是說ThreadLocalMap里邊的key為ThreadLocal的弱引用,具體就是referent變量引用了ThreadLocal對象,value為具體調用ThreadLocal的set方法時傳遞的值

Entry(ThreadLocal<?> k, Object v) {super(k);value = v; } public WeakReference(T referent) {super(referent);}Reference(T referent) {this(referent, null);}Reference(T referent, ReferenceQueue<? super T> queue) {this.referent = referent;this.queue = (queue == null) ? ReferenceQueue.NULL : queue;}

  當一個線程調用ThreadLocal的set方法設置變量時,當前線程的ThreadLocalMap里邊會存放一個記錄,這個記錄的key為ThreadLocal的弱引用,value為設置的值。如果線程一直沒有調用remove方法,并且這個時候其他地方還有對ThreadLocal的引用,則當前線程的ThreadLocalMap變量里邊存在對ThreadLocal變量的引用和對value對象的應用,他們是不會被釋放的,這就會造成內存泄漏。另外,即使這個ThreadLocal變量沒有其他強依賴,而當前線程還存在的,由于線程的ThreadLocalMap里邊的key是弱引用,所以當前線程的ThreadLocal變量的弱引用會在GC的時候回收,但是對應的value不會回收,還是會造成內存泄漏。

  雖然ThreadLocalMap提供的set、get、remove方法提供了在一些時機下對entry進行清理,但這是不及時的,也不是每次執行的,所以在一些情況下還是會有內存泄漏。

  解決內ThreadLocal內存泄漏的方法:在ThreadLocal使用完畢后,及時調用remove方法進行清理工作。

  案例:在線程池中使用ThreadLocal導致內存泄漏

package com.nxz.blog.otherTest;import java.util.concurrent.LinkedBlockingQueue; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.TimeUnit;public class TestThread0010 {static class LocalVariable {//申請一塊固定大小的內存private Long[] a = new Long[1024 * 1024];}static ThreadLocal<LocalVariable> localVariable = new ThreadLocal<>();// 核心線程數和最大線程數都為5,超時時間1分鐘final static ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(5, 5, 1, TimeUnit.MINUTES,new LinkedBlockingQueue<>());public static void main(String[] args) throws InterruptedException {// 50個線程,每個線程都往ThreadLocal中放入一個固定大小的對象for (int i = 0; i < 50; i++) {poolExecutor.execute(new Runnable() {@Overridepublic void run() {localVariable.set(new LocalVariable());System.out.println("user LocalVariable");Thread thread1 = Thread.currentThread();//localVariable.remove();}});Thread thread = Thread.currentThread();Thread.sleep(1000);}System.out.println("pool executor over");//poolExecutor.shutdown();}}

運行代碼,使用jconcle監控內存變化(我自己頁測試來,但是顯示效果沒有書上的圖好):

第二次運行時,放開remove方法的注釋,繼續看內存變化:

由兩次運行的內存變化,可以看出,第一次運行時,當50個線程運行完畢后(此時主線程并沒有結束,因為沒有調用shutdown方法),結束時的內存為75MB左右,第二次運行時,結束內存為25MB左右,由此可以明顯的看出,當沒有調用remove方法是會造成內存泄漏。(PS:尚未理解的問題:這里為啥兩次差距50MB,這個值是怎么出來的,還是沒理順??new LocalVariable對象大概占用1MB內存,相差50MB,相當于每一個Runnable任務,都沒有經過回收,仍然保留在內存中。還有就是線程池中有5個線程循環利用,那么也就表示總共有5個ThreadLocalMap對象,而ThreadLocalMap的key為當前線程,那個set方法的時候,后序的set的value沒有覆蓋之前的嗎?如果覆蓋的話,那么最終兩次運行應該相差5MB,或者是經過一個回收弱引用,那么也是10MB,50MB是怎樣出來的??)

原因:

  第一次運行代碼的時候,沒有調用remove方法,這就導致了當5個核心線程運行完畢后,線程的threadlocals里邊的new LocalVariable()對象并沒有釋放。雖然線程執行完畢了,但是5個核心線程會一直存在,知道被JVM殺死。(這里需要注意的是:localVariable被定義為static變量,雖然在線程的ThreadLocalMap里邊對localVariable進行了弱引用,但是localVariable并不會被回收),沒有被回收是因為存在強引用:thread--》threadLocalMap--》entry--》value

  第二次,由于及時的調用了remove方法,所以不會造成內存泄漏。?

?

總結

以上是生活随笔為你收集整理的java并发编程之美-阅读记录11的全部內容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網站內容還不錯,歡迎將生活随笔推薦給好友。