<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      netty Recycler對象池

      前言

      池化思想在實際開發(fā)中有很多應用,指的是針對一些創(chuàng)建成本高,創(chuàng)建頻繁的對象,用完不棄,將其緩存在對象池子里,下次使用時優(yōu)先從池子里獲取,如果獲取到則可以直接使用,以此降低創(chuàng)建對象的開銷。
      我們最熟悉的數(shù)據(jù)庫連接池就是一種池化思想的應用,數(shù)據(jù)庫操作是非常頻繁的,數(shù)據(jù)庫連接的創(chuàng)建、銷毀開銷很大,每次都需要進行TCP三次握手和四次揮手,權限檢查等,所以如果每次操作數(shù)據(jù)庫都重新創(chuàng)建連接,用完就丟棄,對于應用程序來說是不可接受的。在java世界里,一切皆對象,所以需要有一個數(shù)據(jù)庫對象連接池,用于保存連接池對象。例如使用hikari,可以配置spring.datasource.hikari.maximum-pool-size=20,表示最多可以池化20個數(shù)據(jù)庫連接對象。
      此外,頻繁的創(chuàng)建銷毀對象還會影響GC,當一個對象使用完,再沒被GC root引用,就變成不可達,所引用的內存可以被垃圾回收,GC是需要STW的,頻繁的GC也會影響程序的吞吐量。

      本篇我們要介紹的是netty的對象池Recycler,Recycler是對象池核心類,netty為了減少依賴,以及追求高性能,并沒有使用第三方的對象池,而是自己設計了一套。
      netty在高并發(fā)處理IO讀寫,內存對象的使用是非常頻繁的,如果每次都重新申請,無疑性能會大打折扣,特別是對于堆外內存,申請和銷毀的成本更高,所以對內存對象使用池化是很有必要的。
      例如:PooledHeapByteBuf,PooledDirectByteBuf,ChannelOutboundBuffer.Entry都使用了對象池,這些類內部都有一個Recycler靜態(tài)變量和一個Handle實例變量。

      static final class Entry {
          private static final Recycler<Entry> RECYCLER = new Recycler<Entry>() {
              @Override
              protected Entry newObject(Handle<Entry> handle) {
                  return new Entry(handle);
              }
          };
      
          private final Handle<Entry> handle;
      }
      

      原理

      我們先通過一個例子感受一下Recycler的使用,然后再來分析它的原理。

      public final class Connection {
      
      	private Recycler.Handle handle;
      
      	private Connection(Recycler.Handle handle) {
      		this.handle = handle;
      	}
      
      	private static final Recycler<Connection> RECYCLER = new Recycler<Connection>() {
      		@Override
      		protected Connection newObject(Handle<Connection> handle) {
      			return new Connection(handle);
      		}
      	};
      
      	public static Connection newInstance() {
      		return RECYCLER.get();
      	}
      
      	public void recycle() {
      		handle.recycle(this);
      	}
      
      	public static void main(String[] args) {
      		Connection c1 = Connection.newInstance();
      		int hc1 = c1.hashCode();
      		c1.recycle();
      		Connection c2 = Connection.newInstance();
      		int hc2 = c2.hashCode();
      		c2.recycle();
      		System.out.println(hc1 == hc2); //true
      	}
      }
      

      代碼非常簡單,我們用final修飾Connection,這樣就無法通過繼承創(chuàng)建對象。同時構造方法定義為私有,防止外部直接new創(chuàng)建對象,這樣就只能通過newInstance靜態(tài)方法創(chuàng)建對象。
      Recycler是一個抽象類,newObject是它的抽象方法,這里使用匿名類繼承Recycler并重寫newObject,用于創(chuàng)建一個新的對象。
      Handle是一個接口,Recycler會創(chuàng)建并通過newObject方法傳進來,默認是DefaultHandle,它的作用是用來回收對象,放回對象池。
      接著我們創(chuàng)建兩個Connection實例,可以看到它們的hashcode是一樣的,證明是同一個對象。
      需要注意的是,使用對象池創(chuàng)建的對象,用完需要調用recycle回收。

      原理分析
      想象一下,如果由我們設計,怎么設計一個高性能的對象池呢?對象池的操作很簡單,一取一放,但考慮到多線程,實際情況就變得復雜了。
      如果只有一個全局的對象池,多線程操作需要保證線程安全,那就需要通過加鎖或者CAS,這都會影響存取效率,由于線程競爭,鎖等待,可能通過對象池獲取對象的效率還不如直接new一個,這樣就得不償失了。
      針對這種情況,已經(jīng)有很多的經(jīng)驗供我們借鑒,核心思想都是一樣的,降低鎖競爭。例如ConcurrentHashMap,通過每個節(jié)點上鎖,hash到不同節(jié)點的線程就不會相互競爭;例如ThreadLocal,通過在線程級別綁定一個ThreadLocalMap,每個線程操作的都是自己的私有變量,不會相互競爭;再比如jvm在分配內存的時候,內存區(qū)域是共享的,所以jvm為每個線程設計了一塊私有的TLAB,可以高效進行內存分配,關于TLAB可以參考:這篇文章

      這種無鎖化的設計在netty中非常常見,例如對象池,內存分配,netty還設計了FastThreadLocal來代替jdk的ThreadLocal,使得線程內的存取更加高效。
      Recycler設計如下:

      如上圖,Recycler內部維護了兩個重要的變量,StackWeakOrderQueue,實際對象就是包裝成DefaultHandle,保存在這兩個結構中。
      默認情況一個線程最多存儲4 * 1024個對象,可以根據(jù)實際情況,通過Recycler的構造函數(shù)指定。

      private static final int DEFAULT_INITIAL_MAX_CAPACITY_PER_THREAD = 4 * 1024; // Use 4k instances as default.
      

      Stack是一個棧結構,是線程私有的,Recycler內部通過FastThreadLocal進行定義,對Stack的操作不會有線程安全問題。

       private final FastThreadLocal<Stack<T>> threadLocal = new FastThreadLocal<Stack<T>>() {};        
      

      FastThreadLocal是netty版的ThreadLocal,搭配FastThreadLocalThread,F(xiàn)astThreadLocalMap使用,主要優(yōu)化jdk ThreadLocal擴容需要rehash,和hash沖突問題。

      當獲取對象時,就是嘗試從Stack棧頂pop出一個對象,如果有,則直接使用。如果沒有就嘗試從WeakOrderQueue“借”一點過來,放到Stack,如果借不到,那就調用newObject()創(chuàng)建一個。

      WeakOrderQueue主要是用來解決多線程問題的,考慮這種情況,線程A創(chuàng)建的對象,可能被線程B使用,那么對象的釋放就應該由線程B決定。如果線程B也將對象歸還到線程A的Stack,那就出現(xiàn)了線程安全問題,線程A對Stack的讀取,寫入就需要加鎖,影響并發(fā)效率。
      為了無鎖化操作,netty為其它每個線程都設計了一個WeakOrderQueue,各個線程只會操作自己的WeakOrderQueue,不會有并發(fā)問題了。其它線程的WeakOrderQueue會通過指針構成一個鏈表,Stack對象內部通過3個指針指向鏈表,這樣就可以遍歷整個鏈表對象。

      站在線程A的角度,其它線程就是B,C,D...,站在線程B的角度,其它線程就是A,C,D...

      從上圖可以看到,WeakOrderQueue實際不是一個隊列,內部是由一些Link對象構成的雙向鏈表,它也是一個鏈表。
      Link對象是一個包含讀寫索引,和一個長度為16的數(shù)組的對象,數(shù)組存儲的就是DefaultHandler對象。

      整個過程是這樣的,當本線程從Stack獲取不到可用對象時,就會通過cursor指針變量WeakOrderQueue鏈表,開始從其它線程獲取對象。如果找到一個可用的Link,就會將整個Link里的對象遷移到Stack,然后刪除鏈表節(jié)點,為了保證效率,每次最多遷移一個Link。如果還獲取不到,就通過newObject()方法創(chuàng)建一個新的對象。

      Recycler#get 方法如下:

       public final T get() {
          if (maxCapacityPerThread == 0) {
              return newObject((Handle<T>) NOOP_HANDLE);
          }
          Stack<T> stack = threadLocal.get();
          DefaultHandle<T> handle = stack.pop();
          if (handle == null) {
              handle = stack.newHandle();
              handle.value = newObject(handle);
          }
          return (T) handle.value;
      }
      

      pop方法判斷Stack沒有對象,就會調用scavenge方法,從WeakOrderQueue遷移對象。scavenge,翻譯過來是拾荒,撿的意思。

       DefaultHandle<T> pop() {
          int size = this.size;
          if (size == 0) {
              if (!scavenge()) {
                  return null;
              }
              size = this.size;
          }
          //...
      }
      

      最終會調用到WeakOrderQueue的transfer方法,這個方法比較復雜,主要是對WeakOrderQueue鏈表和內部Link鏈表的遍歷。
      這里dst就是前面說的Stack對象,可以看到會把element元素遷移過去。

      boolean transfer(Stack<?> dst) {
          //...
          if (srcStart != srcEnd) {
              final DefaultHandle[] srcElems = head.elements;
              final DefaultHandle[] dstElems = dst.elements;
              int newDstSize = dstSize;
              for (int i = srcStart; i < srcEnd; i++) {
                  DefaultHandle element = srcElems[i];
                  if (element.recycleId == 0) {
                          element.recycleId = element.lastRecycledId;
                  } else if (element.recycleId != element.lastRecycledId) {
                      throw new IllegalStateException("recycled already");
                  }
                  srcElems[i] = null;
      
                  if (dst.dropHandle(element)) {
                      // Drop the object.
                      continue;
                  }
                  element.stack = dst;
                  dstElems[newDstSize ++] = element;
              }            
          }
          //...
      }
      

      應用

      我們項目使用了mybatis plus作為orm,其中用得最多的就是QueryWrapper了,每次查詢都需要new一個QueryWrapper。例如:

      QueryWrapper<User> queryWrapper = new QueryWrapper();
      queryWrapper.eq("uid", 123);
      return userMapper.selectOne(queryWrapper);
      

      數(shù)據(jù)庫查詢是非常頻繁的,QueryWrapper的創(chuàng)建雖然不會很耗時,但過多的對象也會給GC帶來壓力。
      QueryWrapper是mp提供的類,它沒有池化的實現(xiàn),不過我們可以參考上面netty DefaultHandle的思路,在它外面再包一層,然后池化包裝后的對象。
      回收的時候還要注意清空對象的屬性,例如上面給uid賦值了123,下個對象就不能用這個條件,否則就亂套了,QueryWrapper提供了clear方法可以重置所有屬性。
      同時,每次用完都需要手動recycle也是比較麻煩的,開發(fā)容易忘記,可以借助AutoCloseable接口,使用try-with-resource的寫法,在結束后自動完成回收。
      對于修改和刪除還有UpdateWrapper和DeleteWrapper,同樣思路也可以實現(xiàn)。

      有了這些思路,代碼就出來了:

      public final class WrapperUtils {
      
      	private WrapperUtils() {}
      
      	private static final Recycler<PooledQueryWrapper> QUERY_WRAPPER_RECYCLER = new Recycler<PooledQueryWrapper>() {
      		@Override
      		protected PooledQueryWrapper newObject(Handle<PooledQueryWrapper> handle) {
      			return new PooledQueryWrapper<>(handle);
      		}
      	};
      
      	public static <T> PooledQueryWrapper<T> newInstance() {
      		return QUERY_WRAPPER_RECYCLER.get();
      	}
      
      	static class PooledQueryWrapper<T> implements AutoCloseable {
      
      		private QueryWrapper<T> queryWrapper;
      		private Recycler.Handle<PooledQueryWrapper> handle;
      
      		public PooledQueryWrapper(Recycler.Handle<PooledQueryWrapper> handle) {
      			this.queryWrapper = new QueryWrapper<>();
      			this.handle = handle;
      		}
      
      		public QueryWrapper<T> getWrapper() {
      			return this.queryWrapper;
      		}
      
      		@Override
      		public void close() {
      			queryWrapper.clear();
      			handle.recycle(this);
      		}
      	}
      }
      

      使用如下,可以看到打印出來的hashcode都是一樣的,每次執(zhí)行后都會自動調用close方法,進行QueryWrapper屬性重置。

      public static void main(String[] args) {
      	try (PooledQueryWrapper<Case> objectPooledWrapper = WrapperUtils.newInstance()) {
      		QueryWrapper<Case> wrapper = objectPooledWrapper.getWrapper();
      		wrapper.eq("age", 1);
      		wrapper.select("id,name");
      		wrapper.last("limit 1");
      		System.out.println(wrapper.hashCode());
      	}
      
      	try (PooledQueryWrapper<Case> objectPooledWrapper = WrapperUtils.newInstance()) {
      		QueryWrapper<Case> wrapper = objectPooledWrapper.getWrapper();
      		wrapper.eq("age", 2);
      		wrapper.select("id,email");
      		wrapper.last("limit 2");
      		System.out.println(wrapper.hashCode());
      	}
      
      	try (PooledQueryWrapper<Case> objectPooledWrapper = WrapperUtils.newInstance()) {
      		QueryWrapper<Case> wrapper = objectPooledWrapper.getWrapper();
      		wrapper.eq("age", 3);
      		wrapper.select("id,phone");
      		wrapper.last("limit 3");
      		System.out.println(wrapper.hashCode());
      	}
      }
      

      總結

      之前我們也分析過apache common pool,這也是一個池化實現(xiàn),在redis客戶端也有應用,但它是通過加鎖解決并發(fā)問題的,設計沒有netty這么精細。
      上面的源碼來自netty4.1.42,從整體上看整個Recycler的設計還是比較復雜的,主要為了解決多線程競爭和GC問題,導致整個代碼復雜度比較高,所以netty在后來的版本中對其進行重構。
      不過這不影響我們對它思想的學習,以后也可以借鑒到實際開發(fā)中。

      更多分享,歡迎關注我的github:https://github.com/jmilktea/jtea

      posted @ 2024-03-15 09:57  jtea  閱讀(687)  評論(0)    收藏  舉報
      主站蜘蛛池模板: 亚洲精品国产一二三区| 国产麻豆91网在线看| 日韩欧美猛交xxxxx无码| 午夜射精日本三级| 日韩精品有码中文字幕| 性按摩玩人妻hd中文字幕| 国产精品一区二区在线欢| 国产超高清麻豆精品传媒麻豆精品 | 亚洲av成人无码精品电影在线| 亚洲老熟女一区二区三区| 亚洲欧美人成人让影院| 伊人天天久大香线蕉av色| 亚洲成av人片无码天堂下载| 最近日本免费观看高清视频| 国产欧美亚洲精品第一页在线| 国产永久免费高清在线观看| 色噜噜久久综合伊人一本| 亚洲一区二区三区四区| 91精品人妻中文字幕色| 国产va免费精品观看精品 | 色秀网在线观看视频免费| 无码日韩精品一区二区三区免费 | 69人妻精品中文字幕| 三男一女吃奶添下面视频| 粉嫩av一区二区三区蜜臀| 亚洲欧美中文日韩V日本| 在线a级毛片无码免费真人| av一区二区中文字幕| 樱花草视频www日本韩国| 彭阳县| 久久av高潮av喷水av无码| 熟女一区| 尤物国精品午夜福利视频| 久久精品国产99国产精品澳门 | 无码中文字幕av免费放| 国产成人综合欧美精品久久| 国产综合色在线精品| 察雅县| 在线看av一区二区三区| 九九热在线视频观看这里只有精品| 亚洲中文字幕第二十三页|