一、WeakHashMap 概述
1、WeakHashMap的功能实现上面和HashMap等非常的相似,无非也是用来hash表+单向链表的结构作为底层数据存储,
2、WeakHashMap的特点是以一种弱引用的关系存储数据,存储对象长期不用,可以被垃圾回收。
3、
二、WeakHashMap 类结构
1、WeakHashMap 类继承结构
2、WeakHashMap 类签名
1 2 3 | public class WeakHashMap<K,V> extends AbstractMap<K,V> implements Map<K,V> {} |
3、WeakHashMap 方法列表
三、与 HashMap 异同
WeakHashMap的实现和HashMap非常类似,他们有相同的数据结构,类似的存储机制。数据底层都是通过hash表+单向链表的结构存储数据。
1、get
获取元素的流程非常类似,首先对key进行hash处理,通过hash值寻找到对应的元素位置。数组元素保存的是单向链表,这是hash冲突导致的结果。然后对每个entry对比key值是否相同,最终找到对应的元素。
2、put
添加元素的流程也和HashMap类似,首先对key值hash处理,通过hash值求出对应的元素位置,然后通过对比单向链表中的key值,将元素添加到链表头部。最后会更加当前元素的数量与threshold对比,进行动态扩容。扩容部分的原理也和HashMap类似。
3、getTable
getTable方法是WeakHashMap特有的,这个方法是干什么用的呢?因为我们知道WeakHashMap的元素是通过弱引用的关系存储的。在容器中,有部分元素长时间未用,会被垃圾回收,getTable的作用就是清除被垃圾回收的元素。源码如下:
1 privateEntry<K,V>[] getTable(){
2 expungeStaleEntries();
3 return table;
4 }
5
6 // 清除所有被垃圾回收的元素
7 privatevoid expungeStaleEntries(){
8 // queue队列中保存的是已经被垃圾回收的元素key值。遍历队列
9 for(Object x;(x = queue.poll())!=null;){
10 synchronized(queue){
11 @SuppressWarnings("unchecked")
12 Entry<K,V> e =(Entry<K,V>) x;
13 int i = indexFor(e.hash, table.length);
14 Entry<K,V> prev = table[i];
15 Entry<K,V> p = prev;
16 while(p !=null){
17 Entry<K,V>next= p.next;
18 if(p == e){
19 if(prev == e)
20 table[i]=next;
21 else
22 prev.next=next;
23 e.value =null;// 有助于垃圾回收
24 size--;
25 break;
26 }
27 prev = p;
28 p =next;
29 }
30 }
31 }
32 }
从源码中可以看出,首先会遍历queue队列,队列中保存的元素是已经被垃圾回收的元素的key值。然后将该key从table中删除。这步方法在getTable,resize,size方法中使用到,并且在垃圾回收过程中也会使用,因此他是在多线程环境下调用的,所以该方法使用了同步方法,确保线程安全。
那么是谁将垃圾回收的后的元素放入queue中的呢?这就要从Entry的结构说起。
4、Entry结构介绍
WeakHashMap的Entry是Reference的子类。Entry实例化时会引用当前的queue,如果当前Entry被垃圾回收后,会将key注册的queue中。在后文中会详细介绍Reference类。
1 /**
2 * The entries in this hash table extend WeakReference, using its main ref
3 * field as the key.
4 */
5 private static class Entry<K,V> extends WeakReference<Object> implements Map.Entry<K,V> {
6 V value;
7 final int hash;
8 Entry<K,V> next;
9
10 /**
11 * Creates new entry.
12 */
13 Entry(Object key, V value,
14 ReferenceQueue<Object> queue,
15 int hash, Entry<K,V> next) {
16 super(key, queue);
17 this.value = value;
18 this.hash = hash;
19 this.next = next;
20 }
21
22 @SuppressWarnings("unchecked")
23 public K getKey() {
24 return (K) WeakHashMap.unmaskNull(get());
25 }
26
27 public V getValue() {
28 return value;
29 }
30
31 public V setValue(V newValue) {
32 V oldValue = value;
33 value = newValue;
34 return oldValue;
35 }
36
37 public boolean equals(Object o) {
38 if (!(o instanceof Map.Entry))
39 return false;
40 Map.Entry<?,?> e = (Map.Entry<?,?>)o;
41 K k1 = getKey();
42 Object k2 = e.getKey();
43 if (k1 == k2 || (k1 != null && k1.equals(k2))) {
44 V v1 = getValue();
45 Object v2 = e.getValue();
46 if (v1 == v2 || (v1 != null && v1.equals(v2)))
47 return true;
48 }
49 return false;
50 }
51
52 public int hashCode() {
53 K k = getKey();
54 V v = getValue();
55 return Objects.hashCode(k) ^ Objects.hashCode(v);
56 }
57
58 public String toString() {
59 return getKey() + "=" + getValue();
60 }
61 }
5、
四、弱键引用
WeakHashMap的重点是对元素进行垃圾回收的部分。下面将结合源码进行讲解。
1、源码
1 public abstract class Reference<T> {
2
3
4 private T referent; /* Treated specially by GC */
5
6 // 将回收的元素添加到队列中。
7 volatile ReferenceQueue<? super T> queue;
8
9
10 @SuppressWarnings("rawtypes")
11 volatile Reference next;
12
13
14 transient private Reference<T> discovered; /* used by VM */
15
16
17 static private class Lock { }
18 private static Lock lock = new Lock();
19
20
21 // 垃圾收集器将回收的引用加入队列
22 private static Reference<Object> pending = null;
23
24 /* High-priority thread to enqueue pending References
25 */
26 private static class ReferenceHandler extends Thread {
27
28 private static void ensureClassInitialized(Class<?> clazz) {
29 try {
30 Class.forName(clazz.getName(), true, clazz.getClassLoader());
31 } catch (ClassNotFoundException e) {
32 throw (Error) new NoClassDefFoundError(e.getMessage()).initCause(e);
33 }
34 }
35
36 static {
37 // pre-load and initialize InterruptedException and Cleaner classes
38 // so that we don't get into trouble later in the run loop if there's
39 // memory shortage while loading/initializing them lazily.
40 ensureClassInitialized(InterruptedException.class);
41 ensureClassInitialized(Cleaner.class);
42 }
43
44 ReferenceHandler(ThreadGroup g, String name) {
45 super(g, name);
46 }
47
48 public void run() {
49 while (true) {
50 tryHandlePending(true);
51 }
52 }
53 }
54
55
56 static boolean tryHandlePending(boolean waitForNotify) {
57 Reference<Object> r;
58 Cleaner c;
59 try {
60 synchronized (lock) {
61 if (pending != null) {
62 r = pending;
63 // 'instanceof' might throw OutOfMemoryError sometimes
64 // so do this before un-linking 'r' from the 'pending' chain...
65 c = r instanceof Cleaner ? (Cleaner) r : null;
66 // unlink 'r' from 'pending' chain
67 pending = r.discovered;
68 r.discovered = null;
69 } else {
70 // The waiting on the lock may cause an OutOfMemoryError
71 // because it may try to allocate exception objects.
72 if (waitForNotify) {
73 lock.wait();
74 }
75 // retry if waited
76 return waitForNotify;
77 }
78 }
79 } catch (OutOfMemoryError x) {
80 // Give other threads CPU time so they hopefully drop some live references
81 // and GC reclaims some space.
82 // Also prevent CPU intensive spinning in case 'r instanceof Cleaner' above
83 // persistently throws OOME for some time...
84 Thread.yield();
85 // retry
86 return true;
87 } catch (InterruptedException x) {
88 // retry
89 return true;
90 }
91
92 // Fast path for cleaners
93 if (c != null) {
94 c.clean();
95 return true;
96 }
97
98 ReferenceQueue<? super Object> q = r.queue;
99 if (q != ReferenceQueue.NULL) q.enqueue(r);
100 return true;
101 }
102
103 static {
104 ThreadGroup tg = Thread.currentThread().getThreadGroup();
105 for (ThreadGroup tgn = tg;
106 tgn != null;
107 tg = tgn, tgn = tg.getParent());
108 Thread handler = new ReferenceHandler(tg, "Reference Handler");
109 /* If there were a special system-only priority greater than
110 * MAX_PRIORITY, it would be used here
111 */
112 handler.setPriority(Thread.MAX_PRIORITY);
113 handler.setDaemon(true);
114 handler.start();
115
116 // provide access in SharedSecrets
117 SharedSecrets.setJavaLangRefAccess(new JavaLangRefAccess() {
118 @Override
119 public boolean tryHandlePendingReference() {
120 return tryHandlePending(false);
121 }
122 });
123 }
124
125 /* -- Referent accessor and setters -- */
126
127 public T get() {
128 return this.referent;
129 }
130
131
132 public void clear() {
133 this.referent = null;
134 }
135
136
137 /* -- Queue operations -- */
138
139 public boolean isEnqueued() {
140 return (this.queue == ReferenceQueue.ENQUEUED);
141 }
142
143
144 public boolean enqueue() {
145 return this.queue.enqueue(this);
146 }
147
148
149 /* -- Constructors -- */
150
151 Reference(T referent) {
152 this(referent, null);
153 }
154
155 Reference(T referent, ReferenceQueue<? super T> queue) {
156 this.referent = referent;
157 this.queue = (queue == null) ? ReferenceQueue.NULL : queue;
158 }
159
160 }
2、Reference的实现原理
第一步通过静态代码块启动ReferenceHandler线程,所有Reference实例共享该线程。pending会被虚拟机调用,当有元素被垃圾回收以后,会添加到该队列中。Reference线程通过无线循环检查pending是否存在元素。当发现有元素被垃圾回收以后,会将该元素添加到queue中。如果当前没有发现被回收的元素,也就是pending为null时,会通过lock.wait() 阻塞线程。直到有元素被回收以后,会调用nodify唤醒线程。
3、案例
该过程涉及到多线程的知识,jvm垃圾回收的原理等,这部分比较复杂,暂时无法很好的讲解。将回收的元素加入到queue队列的部分实现用了消费者模式。为了读者更好的理解该原理,我对他进行模仿,实现了一个简单的消费者模式的demo。
这个demo主要功能如下:
(1)多个生产者不断的生产某件产品,当产品数量大于10以后就停止生产,只要当产品数量小于10,就会生产。
(2)多个消费者不断的消费产品,知道消费结束。
(3)当生产者生产到10件以后,就阻塞生产线,通知消费者消费。
(4)当消费者消费完结束以后,阻塞消费,通知生产者生产。
1 public class ProducerCustomerDemo{
2 private static int index =0;
3 private static int size =0;
4 static private class Lock{};
5 private static Lock lock= new Lock();
6 private static Entry head =null;
7 public synchronized int getSize(){
8 return size;
9 }
10 public synchronized void addSize(){
11 ProducerCustomerDemo.size++;
12 }
13 public synchronized void minusSize(){
14 ProducerCustomerDemo.size--;
15 }
16 public synchronized int getIndex(){
17 return index;
18 }
19 public synchronized void addIndex(){
20 index++;
21 }
22 public synchronized void minusIndex(){
23 index--;
24 }
25 public static void main(String[] args){
26 new Thread(new ProducerCustomerDemo().new Consumer("aaa")).start();
27 new Thread(new ProducerCustomerDemo().new Consumer("bbb")).start();
28 new Thread(new ProducerCustomerDemo().new Consumer("ccc")).start();
29 new Thread(new ProducerCustomerDemo().new Consumer("ddd")).start();
30 new Thread(new ProducerCustomerDemo().new Producer("张三")).start();
31 new Thread(new ProducerCustomerDemo().new Producer("李四")).start();
32 }
33 class Entry{
34 Entry next;
35 int value;
36 public Entry(int value){
37 this.value = value;
38 }
39 }
40 class Consumer implements Runnable{
41 private String name;
42 public Consumer(String name){
43 this.name = name;
44 }
45 @Override
46 public void run(){
47 for(;;){
48 Entry temp =null;
49 synchronized(lock){
50 if(head !=null){
51 try{
52 Thread.sleep(1000);
53 }catch(InterruptedException e){
54 e.printStackTrace();
55 }
56 temp = head;
57 head = temp.next;
58 temp.next=null;
59 minusSize();
60 System.out.println("消费,当前产品数"+getSize()+"--"+name+":"+ temp.value);
61 lock.notify();
62 }else{
63 try{
64 lock.wait();
65 }catch(InterruptedException e){
66 e.printStackTrace();
67 }
68 }
69 }
70 }
71 }
72 }
73 class Producer implements Runnable{
74 private String name;
75 public Producer(String name){
76 this.name = name;
77 }
78 @Override
79 public void run(){
80 for(;;){
81 synchronized(lock){
82 if(getSize()<10){
83 try{
84 Thread.sleep(1000);
85 }catch(InterruptedException e){
86 e.printStackTrace();
87 }
88 addIndex();
89 Entry temp =new Entry(getIndex());
90 temp.next= head;
91 head = temp;
92 addSize();
93 System.out.println("生产,当前产品数"+getSize()+"--"+ name +":"+ temp.value);
94 lock.notify();
95 }else{
96 try{
97 lock.wait();
98 }catch(InterruptedException e){
99 e.printStackTrace();
100 }
101 }
102 }
103 }
104 }
105 }
106 }
五、
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】凌霞软件回馈社区,博客园 & 1Panel & Halo 联合会员上线
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】博客园社区专享云产品让利特惠,阿里云新客6.5折上折
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 没有源码,如何修改代码逻辑?
· 一个奇形怪状的面试题:Bean中的CHM要不要加volatile?
· [.NET]调用本地 Deepseek 模型
· 一个费力不讨好的项目,让我损失了近一半的绩效!
· .NET Core 托管堆内存泄露/CPU异常的常见思路
· 微软正式发布.NET 10 Preview 1:开启下一代开发框架新篇章
· 没有源码,如何修改代码逻辑?
· PowerShell开发游戏 · 打蜜蜂
· 在鹅厂做java开发是什么体验
· WPF到Web的无缝过渡:英雄联盟客户端的OpenSilver迁移实战