本地缓存(Cache)系统简易设计

本地Cache系统简易设计

为什么使用缓存?

  • 降低数据库的访问压力。
  • 提高查询效率。
  • 改善用户体验。

你都了解哪些缓存?

  • 数据库内置缓存(DBA修改)。
  • 数据层缓存(由持久层框架决定,例如mybatis)
  • 业务层缓存(由业务层框架以及第三缓存产品决定:本地缓存+分布式缓存)
  • 浏览器缓存(Cache-Control)

设计缓存都应该考虑什么问题?

  • 存储结构:使用什么结构存储数据?(数组,链表,散列存储-哈希存储)
  • 淘汰算法:有限容量(LRU,FIFO,.....),不限容量(GC)
  • 并发安全:保证线程安全。
  • 任务调度:每隔多长时间清理一下缓存。
  • 日志记录:是否命中?(命中率)

缓存系统设计基础

缓存标准定义

package com.cy.java.cache;  
/\*\* Cache接口设计\*/  
public interface Cache {  
 public void putObject(Object key,Object value);  
 public Object getObject(Object key);  
 public Object removeObject(Object key);  
 public void clear();  
 public int size();  
}

简易Cache实现

场景应用:
  • 存储数据量比较小(因为没有考虑淘汰机制)
  • 没有线程共享(一个线程的内部缓存)
  • 缓存对象生命周期比较短
package com.cy.java.cache;  
import java.util.HashMap;  
import java.util.Map;  
/**负责真正存储数据的一个对象,将数据存储到一个map中*/  
public class PerpetualCache implements Cache {  
/** 特点:线程不安全,key不允许重复,不能保证key的顺序  */  
private Map cache=new HashMap<>();  
@Override  
public void putObject(Object key, Object value) {  
cache.put(key, value);  
}  
@Override  
public Object getObject(Object key) {  
return cache.get(key);  
}  
@Override  
public Object removeObject(Object key) {  
return cache.remove(key);  
}  
@Override  
public void clear() {  
 cache.clear();  
}  
@Override  
public int size() {  
return cache.size();  
}  
@Override  
public String toString() {  
return cache.toString();  
}  
public static void main(String\[\] args) {  
 Cache cache=new PerpetualCache();  
 cache.putObject("A", 100);  
 cache.putObject("B", 200);  
 cache.putObject("C", 300);  
 System.out.println(cache);  
 cache.removeObject("D");  
 cache.clear();  
 System.out.println(cache.size());  
}  
}

构建线程安全Cache对象

场景应用:并发环境
package com.cy.java.cache;  
/**线程安全的cache对象*/  
public class SynchronizedCache implements Cache{  
private Cache cache;  
public SynchronizedCache(Cache cache) {  
this.cache=cache;  
}  
@Override  
public synchronized void putObject(Object key, Object value) {  
cache.putObject(key, value);  
}  
@Override  
public synchronized Object getObject(Object key) {  
// TODO Auto-generated method stub  
return cache.getObject(key);  
}  
@Override  
public synchronized Object removeObject(Object key) {  
// TODO Auto-generated method stub  
return cache.removeObject(key);  
}  
@Override  
public synchronized void clear() {  
cache.clear();  
}  
@Override  
public synchronized int size() {  
return cache.size();  
}  
@Override  
public String toString() {  
return cache.toString();  
}  
public static void main(String\[\] args) {  
SynchronizedCache cache=

 new SynchronizedCache(new PerpetualCache());  
cache.putObject("A", 100);  
cache.putObject("B", 200);  
cache.putObject("C", 300);  
System.out.println(cache);  
}  
  
}

思考:对于SynchronizedCache 有什么优势,劣势?

支持日志记录的Cache实现

package com.cy.java.cache;  
/** 用于记录命中率的日志cache*/  
public class LoggingCache implements Cache {  
private Cache cache;  
/**记录请求次数*/  
private int requests;  
/**记录命中次数*/  
private int hits;  
public LoggingCache(Cache cache) {  
this.cache=cache;  
}  
@Override  
public void putObject(Object key, Object value) {  
cache.putObject(key, value);  
}  
@Override  
public Object getObject(Object key) {  
requests++;  
Object obj=cache.getObject(key);  
if(obj!=null)hits++;  
System.out.println("Cache hit Ratio : "+hits\*1.0/requests);  
return obj;  
}  
@Override  
public Object removeObject(Object key) {  
return cache.removeObject(key);  
}  
@Override  
public void clear() {  
cache.clear();  
}  
@Override  
public int size() {  
return cache.size();  
}  
@Override  
public String toString() {  
// TODO Auto-generated method stub  
return cache.toString();  
}  
public static void main(String\[\] args) {  
SynchronizedCache cache=  
 new SynchronizedCache(  
new LoggingCache(  
new PerpetualCache()));  
cache.putObject("A", 100);  
cache.putObject("B", 200);  
cache.putObject("C", 300);  
System.out.println(cache);  
cache.getObject("D");  
cache.getObject("A");  
}  
  
}

思考:你觉得LoggingCache记录日志的方式有什么不好的地方?(信息的完整性,同步问题)

LruCache实现

应用场景:基于LRU算法的的基本实现
package com.cy.java.cache;  
import java.util.LinkedHashMap;  
import java.util.Map;  
  
/** 缓存淘汰策略:LRU(最近最少使用算法)*/  
public class LruCache implements Cache {  
 private Cache cache;  
 /**通过此属性记录要移除的数据对象*/  
 private Object eldestKey;  
 /**通过此map记录key的访问顺序*/  
 private Map keyMap;  
 @SuppressWarnings("serial")  
 public LruCache(Cache cache,int maxCap) {  
 this.cache=cache;  
//LinkedHashMap可以记录key的添加顺序或者访问顺序  
 this.keyMap=

 new LinkedHashMap(maxCap, 0.75f, true)

 {//accessOrder  
//此方法每次执行keyMap的put操作时调用  
@Override  
protected boolean removeEldestEntry

 (java.util.Map.Entry eldest) {  
boolean isFull=size()>maxCap;  
if(isFull)eldestKey=eldest.getKey();  
return isFull;  
}  
 };  
}  
@Override  
public void putObject(Object key, Object value) {  
//存储数据对象  
cache.putObject(key, value);  
//记录key的访问顺序,假如已经满了,就要从cache中移除数据  
keyMap.put(key, key);//此时会执行keyMap对象的removeEldestEntry  
if(eldestKey!=null) {  
cache.removeObject(eldestKey);  
eldestKey=null;  
}  
}  
@Override  
public Object getObject(Object key) {  
keyMap.get(key);//记录key的访问顺序  
return cache.getObject(key);  
}  
  
@Override  
public Object removeObject(Object key) {  
return cache.removeObject(key);  
}  
  
@Override  
public void clear() {  
cache.clear();  
keyMap.clear();  
}  
@Override  
public int size() {  
return cache.size();  
}  
@Override  
public String toString() {  
return cache.toString();  
}  
  
public static void main(String\[\] args) {  
SynchronizedCache cache=  
 new SynchronizedCache(  
new LoggingCache(  
new LruCache(new PerpetualCache(),3)));  
cache.putObject("A", 100);  
cache.putObject("B", 200);  
cache.putObject("C", 300);  
cache.getObject("A");  
cache.getObject("C");  
cache.putObject("D", 400);  
cache.putObject("E", 500);  
System.out.println(cache);  
  
}  
}

设置Cache淘汰算法:FIFO算法

package com.cy.java.cache;  
import java.util.Deque;  
import java.util.LinkedList;  
/**  
* FifoCache :基于FIFO算法(对象满了要按先进先出算法移除对象)实现cache对象  
*/  
public class FifoCache implements Cache{  
/**借助此对象存储数据*/  
private Cache cache;  
/**借助此队列记录key的顺序*/  
private Deque keyOrders;  
/**通过此变量记录cache可以存储的对象个数*/  
private int maxCap;  
public FifoCache(Cache cache,int maxCap) {  
this.cache=cache;  
keyOrders=new LinkedList<>();  
this.maxCap=maxCap;  
}  
@Override  
public void putObject(Object key, Object value) {  
//1.记录key的顺序(起始就是存储key,添加在队列最后位置)  
keyOrders.addLast(key);  
//2.检测cache中数据是否已满,满了则移除。  
if(keyOrders.size()>maxCap) {  
Object eldestKey=keyOrders.removeFirst();  
cache.removeObject(eldestKey);  
}  
//3.放新的对象  
cache.putObject(key, value);  
}  
@Override  
public Object getObject(Object key) {  
return cache.getObject(key);  
}  
  
@Override  
public Object removeObject(Object key) {  
Object obj=cache.removeObject(key);  
keyOrders.remove(key);  
return obj;  
}  
@Override  
public void clear() {  
cache.clear();  
keyOrders.clear();  
}  
@Override  
public int size() {  
return cache.size();  
}  
@Override  
public String toString() {  
// TODO Auto-generated method stub  
return cache.toString();  
}  
public static void main(String\[\] args) {  
Cache cache=

 new SynchronizedCache(  
new LoggingCache(  
 new FifoCache(  
  new PerpetualCache(),3)));  
cache.putObject("A",100);  
cache.putObject("B",200);  
cache.putObject("C",300);  
cache.getObject("A");  
cache.putObject("D",400);  
cache.putObject("E",500);  
System.out.println(cache);  
}   
} 
 

序列化Cache的实现

场景:存储到cache的是对象的字节
package com.cy.java.cache;  
import java.io.ByteArrayInputStream;  
import java.io.ByteArrayOutputStream;  
import java.io.ObjectInputStream;  
import java.io.ObjectOutputStream;  
  
public class SerializedCache implements Cache {  
  
private Cache cache;  
public SerializedCache(Cache cache) {  
this.cache=cache;  
}  
/**序列化*/  
private byte\[\] serialize(Object value) {  
//1.构建流对象  
ByteArrayOutputStream bos=null;  
ObjectOutputStream oos=null;  
try {  
//1.2构建字节数组输出流,此流对象内置可扩容的数组。  
bos=new ByteArrayOutputStream();  
//1.3构建对象输出流  
oos=new ObjectOutputStream(bos);  
//2.对象序列化  
oos.writeObject(value);

 //此时对象会以字节的方式写入到字节数组输出流  
oos.flush();  
return bos.toByteArray();  
}catch (Exception e) {  
 throw new RuntimeException(e);  
}finally {  
//3.关闭流对象  
if(bos!=null)

 try{bos.close();bos=null;}catch(Exception e) {}  
if(oos!=null)

 try{oos.close();oos=null;}catch (Exception e2) {}  
}  
}  
/**反序列化*/  
public Object deserialize(byte\[\] value) {  
//1.创建流对象  
ByteArrayInputStream bis=null;  
ObjectInputStream ois=null;  
try {  
//1.1构建字节数组输入流,此对象可以直接读取数组中的字节信息  
bis=new ByteArrayInputStream(value);  
//1.2构建对象输入流(对象反序列化)  
ois=new ObjectInputStream(bis);  
//2.反序列化对象  
Object obj=ois.readObject();  
return obj;  
}catch(Exception e) {  
throw new RuntimeException(e);  
}finally {  
//3.关闭流对象  
if(bis!=null)

 try{bis.close();bis=null;}catch(Exception e) {}  
if(ois!=null)

 try{ois.close();ois=null;}catch (Exception e2) {}  
}  
}  
@Override  
public void putObject(Object key, Object value) {  
cache.putObject(key, serialize(value));  
}  
@Override  
public Object getObject(Object key) {  
return deserialize((byte\[\])cache.getObject(key));  
}  
@Override  
public Object removeObject(Object key) {  
return cache.removeObject(key);  
}  
@Override  
public void clear() {  
cache.clear();  
}  
@Override  
public int size() {  
return cache.size();  
}  
public static void main(String\[\] args) {  
Cache cache=new SerializedCache(new PerpetualCache());  
cache.putObject("A", 200);  
cache.putObject("B", 300);  
Object v1=cache.getObject("A");  
Object v2=cache.getObject("A");  
System.out.println(v1==v2);  
System.out.println(v1);  
System.out.println(v2);  
}  
}

软件引用Cache实现

应用场景:内存不足时淘汰缓存中数据
package com.cy.java.cache;  
  
import java.lang.ref.ReferenceQueue;  
import java.lang.ref.SoftReference;  
/**软引用*/  
public class SoftCache implements Cache {  
private Cache cache;  
private ReferenceQueue garbageOfRequenceQueue=

 new ReferenceQueue<>();  
public SoftCache(Cache cache) {  
this.cache=cache;  
}  
@Override  
public void putObject(Object key, Object value) {  
//1.移除一些垃圾对象(Soft引用引用的已经被回收的对象)  
removeGarbageObjects();  
//2.将对象存储到cache(key不变,Value为为soft引用对象)  
cache.putObject(key, 

 new SoftEntry(key, value, garbageOfRequenceQueue));  
}  
  
@Override  
public Object getObject(Object key) {  
//1.基于key获取软引用对象并判断  
SoftEntry softEntry=(SoftEntry)cache.getObject(key);  
if(softEntry==null)return null;  
//2.基于软引用对象获取它引用的对象并判断  
Object target = softEntry.get();  
if(target==null)cache.removeObject(key);  
return target;  
}  
  
@Override  
public Object removeObject(Object key) {  
//1.移除一些垃圾对象(Soft引用引用的已经被回收的对象)  
removeGarbageObjects();  
//2.从cache中移除对象  
Object removedObj=cache.removeObject(key);  
return removedObj;  
}  
  
@Override  
public void clear() {  
//1.移除一些垃圾对象(Soft引用引用的已经被回收的对象)  
removeGarbageObjects();  
//2.清空cache  
cache.clear();  
}  
@Override  
public int size() {  
removeGarbageObjects();  
return cache.size();  
}  
private void removeGarbageObjects() {  
SoftEntry softEntry=null;  
//1.从引用队列中获取已经被GC的一些对象的引用  
 while((softEntry=

 (SoftEntry)garbageOfRequenceQueue.poll())!=null){  
//softEntry不为null表示softEntry引用的对象已经被移除  
//2.从cache中将对象引用移除。  
cache.removeObject(softEntry.key);  
}  
}  
/\*\*定义软引用类型\*/  
private static class SoftEntry extends SoftReference{  
private final Object key;  
public SoftEntry(Object key,

 Object referent, ReferenceQueue rQueue) {  
super(referent, rQueue);  
this.key=key;  
}  
}  
@Override  
public String toString() {  
// TODO Auto-generated method stub  
return cache.toString();  
}  
  
public static void main(String\[\] args) {  
Cache cache=new SoftCache(new PerpetualCache());  
cache.putObject("A", new byte\[1024\*1024\]);  
cache.putObject("B", new byte\[1024\*1024\]);  
cache.putObject("C", new byte\[1024\*1024\]);  
cache.putObject("D", new byte\[1024\*1024\]);  
cache.putObject("E", new byte\[1024\*1024\]);  
System.out.println(cache.size());  
System.out.println(cache);  
}  
  
} 
 

弱Cache对象实现

应用场景:GC触发清除缓存对象
package com.cy.java.cache;  
  
import java.lang.ref.ReferenceQueue;  
import java.lang.ref.WeakReference;  
/\*\*弱引用\*/  
public class WeakCache implements Cache {  
private Cache cache;  
private ReferenceQueue garbageOfRequenceQueue=

 new ReferenceQueue<>();  
public WeakCache(Cache cache) {  
this.cache=cache;  
}  
  
@Override  
public void putObject(Object key, Object value) {  
//1.移除一些垃圾对象(Soft引用引用的已经被回收的对象)  
removeGarbageObjects();  
//2.将对象存储到cache(key不变,Value为为soft引用对象)  
cache.putObject(key, 

 new WeakEntry(key, value, garbageOfRequenceQueue));  
}  
  
@Override  
public Object getObject(Object key) {  
//1.基于key获取软引用对象并判断  
WeakEntry softEntry=(WeakEntry)cache.getObject(key);  
if(softEntry==null)return null;  
//2.基于软引用对象获取它引用的对象并判断  
Object target = softEntry.get();  
if(target==null)cache.removeObject(key);  
return target;  
}  
  
@Override  
public Object removeObject(Object key) {  
//1.移除一些垃圾对象(Soft引用引用的已经被回收的对象)  
removeGarbageObjects();  
//2.从cache中移除对象  
Object removedObj=cache.removeObject(key);  
return removedObj;  
}  
  
@Override  
public void clear() {  
//1.移除一些垃圾对象(Soft引用引用的已经被回收的对象)  
removeGarbageObjects();  
//2.清空cache  
cache.clear();  
}  
  
@Override  
public int size() {  
removeGarbageObjects();  
return cache.size();  
}  
private void removeGarbageObjects() {  
WeakEntry softEntry=null;  
//1.从引用队列中获取已经被GC的一些对象的引用  
while((softEntry=

 (WeakEntry)garbageOfRequenceQueue.poll())!=null) {  
//softEntry不为null表示softEntry引用的对象已经被移除  
//2.从cache中将对象引用移除。  
cache.removeObject(softEntry.key);  
}  
}  
/**定义软引用类型*/  
private static class WeakEntry extends WeakReference{  
private final Object key;  
public WeakEntry(Object key,

 Object referent, ReferenceQueue rQueue) {  
super(referent, rQueue);  
this.key=key;  
}  
}  
@Override  
public String toString() {  
return cache.toString();  
}  
public static void main(String\[\] args) {  
Cache cache=new WeakCache(new PerpetualCache());  
cache.putObject("A", new byte\[1024\*1024\]);  
cache.putObject("B", new byte\[1024\*1024\]);  
cache.putObject("C", new byte\[1024\*1024\]);  
cache.putObject("D", new byte\[1024\*1024\]);  
cache.putObject("E", new byte\[1024\*1024\]);  
cache.putObject("F", new byte\[1024\*1024\]);  
cache.putObject("G", new byte\[1024\*1024\]);  
System.out.println(cache.size());  
System.out.println(cache);  
}  
  
} 
 

缓存系统设计进阶

缓存应用需求升级

  • 缓存系统既要保证线程安全又要保证性能。
  • 缓存日志的记录要写到文件,而且是异步写
  • 向缓存中写数据时要提高序列化性能。

缓存对象读写锁应用

package com.cy.java.cache;  
  
import java.util.concurrent.locks.ReentrantReadWriteLock;  
  
/**  
* 构建线程安全对象,基于ReentrantReadWriteLock对象实现读写锁应用。  
* @author qilei  
*/  
public class ReentrantLockCache implements Cache {  
  
private Cache cache;  
/**  
* 此对象提供了读锁,写锁应用方式.  
* 1)写锁:排他锁  
* 2)读锁:共享锁  
* 说明:读写不能同时执行。  
*/  
private final ReentrantReadWriteLock readWriteLock =  
new ReentrantReadWriteLock();  
public ReentrantLockCache(Cache cache) {  
this.cache=cache;  
// TODO Auto-generated constructor stub  
}  
@Override  
public void putObject(Object key, Object value) {  
readWriteLock.writeLock().lock();  
try {  
cache.putObject(key, value);  
}finally {  
readWriteLock.writeLock().unlock();  
}  
}  
  
@Override  
public Object getObject(Object key) {  
readWriteLock.readLock().lock();  
try {  
 Object object=cache.getObject(key);  
 return object;  
}finally{  
readWriteLock.readLock().unlock();  
}  
}  
  
@Override  
public Object removeObject(Object key) {  
readWriteLock.writeLock().lock();  
try {  
 Object object=cache.removeObject(key);  
 return object;  
}finally{  
readWriteLock.writeLock().unlock();  
}  
}  
  
@Override  
public void clear() {  
readWriteLock.writeLock().lock();  
try {  
 cache.clear();  
}finally{  
readWriteLock.writeLock().unlock();  
}  
}  
  
@Override  
public int size() {  
readWriteLock.readLock().lock();  
try {  
 int size=cache.size();  
 return size;  
}finally{  
readWriteLock.readLock().unlock();  
}  
}  
}

异步日志Cache实现

第一步:添加依赖
`   
 ch.qos.logback  
 logback-classic  
 1.2.3  
  `
第二步:添加配置文件logback.xml (参考项目代码)
 
  
  
  
  
  
  
  
logs/context-log.%d{yyyy-MM-dd}.log  
  
  
30  
  
  
\[%-5level\] %date --%thread-- \[%logger\] %msg %n  
  
  
  
  
0  
256  
  
  
  
  
  
  
  
第三步:构建AsyncLoggingCache
package com.cy.java.cache;  
  
import org.slf4j.Logger;  
import org.slf4j.LoggerFactory;  
  
/**  
* 通过此对象异步记录查询操作的命中率  
* 1)选择日志库  
* 2)执行异步写操作。  
*/  
public class AsyncLoggingCache implements Cache {  
 //日志门面应用  
 private static Logger log=LoggerFactory.getLogger(LoggingCache.class);  
private Cache cache;  
/**表示请求次数*/  
private int requests;  
/**命中次数(命中表示从缓存中取到数据了)*/  
private int hits;  
public AsyncLoggingCache(Cache cache) {  
this.cache=cache;  
}  
@Override  
public void putObject(Object key, Object value) {  
cache.putObject(key, value);  
}  
@Override  
public Object getObject(Object key) {  
requests++;  
 Object obj=cache.getObject(key);  
 if(obj!=null)hits++;  
 //记录日志耗时  
 log.info("Cache hit Ratio:{}",hits\*1.0/requests);  
return obj;  
}  
@Override  
  
public Object removeObject(Object key) {  
return cache.removeObject(key);  
}  
@Override  
public void clear() {  
cache.clear();  
}  
@Override  
public int size() {  
return cache.size();  
}  
public static void main(String\[\] args) {  
Cache cache= 

 new AsyncLoggingCache(new PerpetualCache());  
cache.putObject("A", 100);  
cache.putObject("B", 200);  
cache.putObject("C", 300);  
cache.putObject("D", 400);  
//System.out.println(cache);  
cache.getObject("E");  
cache.getObject("A");  
cache.getObject("B");  
}  
  
}

Kryo构建序列化Cache

第一步:添加依赖
  
 com.esotericsoftware  
 kryo  
 5.0.0-RC5  
第二步:构建项目工具类
public class KryoUtils {  
  
/**  
* 多线程并发执行时,可能会出现线程不安全,具体原因是什么?  
* 1)多个线程的并发  
* 2)多个线程有数据共享  
* 3)多个线程在共享数据集上的操作不是原子操作  
*  
* 分析:当出现了线程不安全,如何进行修改来保证线程安全  
* 1)将多线程改为单线程。  
* 2)取消共享 (例如在当前应用中我们一个线程一个Kryo对象)  
* 3)加锁+CAS  
*  
* ThreadLocal提供了这样的一种机制:  
* 1)可以将对象绑定到当前线程(其实是将对象存储到当前线程的map中)  
* 2)可以从当前线程获取绑定的对象(从当前线程的map中获取对象)  
*/  
static private final ThreadLocal kryos = new ThreadLocal() {  
  protected Kryo initialValue() {  
  Kryo kryo = new Kryo();  
  // Configure the Kryo instance.  
  kryo.setRegistrationRequired(false);  
  //....  
  return kryo;  
  };  
 };  
 public static Object deserialize(byte\[\] array){  
Kryo kryo=kryos.get();  
 Input input = new Input(new ByteArrayInputStream(array));  
 Object obj=kryo.readClassAndObject(input);  
return obj;  
 }  
 public static byte\[\] serialize(Object object){  
 //从当前线程获取kryo对象,当前线程没有会调用ThreadLocal的initialValue方法创建对象并绑定线程  
 Kryo kryo=kryos.get();  
 ByteArrayOutputStream bos=new ByteArrayOutputStream();  
 Output output = new Output(bos);  
 kryo.writeClassAndObject(output, object);  
  output.close();  
 return bos.toByteArray();  
 }  
 }

> 构建高性能序列化Cache

public class KryoSerializedCache implements Cache {  
  
private Cache cache;  
public KryoSerializedCache(Cache cache) {  
this.cache=cache;  
}  
  
@Override  
public void putObject(Object key, Object value) {  
//1.将对象序列化  
byte\[\] array=KryoUtils.serialize(value);  
//2.将序列化后的字节数组引用存储到cache  
cache.putObject(key,array);  
}  
  
@Override  
public Object getObject(Object key) {  
//1.基于key获取缓存中的字节数组引用  
byte\[\] array=(byte\[\])cache.getObject(key);  
//2.将字节数组反序列化为对象  
return KryoUtils.deserialize(array);  
}  
  
@Override  
public Object removeObject(Object key) {  
return KryoUtils.deserialize((byte\[\])cache.removeObject(key));  
}  
  
@Override  
public void clear() {  
cache.clear();  
}  
@Override  
public int size() {  
return cache.size();  
}  
public static void main(String\[\] args) {  
Cache cache=new KryoSerializedCache(new PerpetualCache());  
cache.putObject("A", 500);  
Object a1=cache.getObject("A");  
Object a2=cache.getObject("A");  
System.out.println("a1="+a1);  
System.out.println("a2="+a2);  
System.out.println(a1==a2);//false  
}  
  
}

你可能感兴趣的:(java)