一、Amdahl定律
加速=优化前耗时/优化后耗时比
公式图:
二、设计模式
1、单例模式
静态内部类的方式:
/** * 内部类的单例模式 */ public class StaticSingleton { private StaticSingleton(){ System.out.println("aaa"); } private static class StaticSingletonHolder{ private static StaticSingleton singleton=new StaticSingleton(); } public static StaticSingleton getInstance(){ return StaticSingletonHolder.singleton; } }
除了反射机制强制调用私有构造函数,生成多个实例外,序列化和反序列化也可能会导致。
防止序列化的单例:
/** * 可以被串行化的单例 */ public class SerSingleton implements Serializable { String name; private SerSingleton(){ System.out.println("SerSingleton is create"); name="SerSingleton"; } private static SerSingleton instance=new SerSingleton(); public static SerSingleton getInstance(){ return instance; } public static void createString(){ System.out.println("createString in Singleton"); } //阻止生成新的实例,总是返回当前对象 private Object readResolve(){ return instance; } }
关键是实现了readResolve方法
2、代理模式
1、代理模式用于延迟加载
2、动态代理
动态代理是指再运行时,动态生成代理类。
注意:动态代理使用字节码动态生成加载技术,在运行时生成并加载类。
常见的有jdk动态代理,cglib和javassist三种方式。
JDK方式:
/** * JDK的动态代理 */ public class JdkDBQueryHandler implements InvocationHandler { IDBQuery real=null; @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { if(real==null){ real=new DBQuery(); } return real.request(); } public static IDBQuery createJdkProxy(){ IDBQuery jdkProxy=(IDBQuery)Proxy.newProxyInstance(ClassLoader.getSystemClassLoader(),new Class[]{IDBQuery.class}, new JdkDBQueryHandler()); return jdkProxy; } }
CGLIB:
/** * cglib方式 */ public class CglibDBQueryInterceptor implements MethodInterceptor { IDBQuery real=null; @Override public Object intercept(Object o, Method method, Object[] objects, MethodProxy methodProxy) throws Throwable { if(real==null){ real=new DBQuery(); } return real.request(); } public static IDBQuery createCglibProxy(){ Enhancer enhancer=new Enhancer(); enhancer.setCallback(new CglibDBQueryInterceptor()); //指定切入器 enhancer.setInterfaces(new Class[]{IDBQuery.class}); IDBQuery cglibQuery=(IDBQuery)enhancer.create(); return cglibQuery; } }
结论:jdk方式创建对象很快,但是调用方法较慢。
Hibernate中代理模式的应用:
User u=(User)HibernateSessionFactory.getSession().load(User.class,1); System.out.print(u.getClass().getName()); System.out.print(u.getName());
以上代码中,load方法后,并没有查询数据库,在调用u.getName()时才查询的数据库,这就是Hibernate用代理模式做了延迟加载。
3、享元模式
概念:如果在一个系统中存在多个相同的对象,那么只需要共享一份对象的拷贝,而不必每一次使用都创建新的对象。
功能组件如图:
注意:享元模式时为数不多的只为了提升系统性能而生的设计模式。他的主要作用就是复用大对象,节省内存和对象创建时间。
享元模式和对象池的最大不同在于:享元模式是不能互相替代的,他们有
/** * 报表接口 */ public interface IReportManager { String createReport(); }
/** * 员工报表 */ public class EmployeeReportManager implements IReportManager { //租户ID protected String tenanId=null; public EmployeeReportManager(String tenanId) { this.tenanId = tenanId; } @Override public String createReport() { return "this is a employee Report"; } }
/** * 财务报表 */ public class FinancialReportManager implements IReportManager { //租户ID protected String tenanId=null; public FinancialReportManager(String tenanId) { this.tenanId = tenanId; } @Override public String createReport() { return "this is a Financial Report"; } }
/** * 享元工厂类 * 保证同一个id获取到的是同一个对象 */ public class ReportManagerFactory { Map<String,IReportManager> financialReportManager=new HashMap<>(); Map<String,IReportManager> employeeReportManager=new HashMap<>(); IReportManager getFinancialReportManager(String tenantId){ IReportManager r=financialReportManager.get(tenantId); if(r==null){ r=new FinancialReportManager(tenantId); financialReportManager.put(tenantId,r); } return r; } IReportManager getEmployeeReportManager(String tenantId){ IReportManager r=employeeReportManager.get(tenantId); if(r==null){ r=new EmployeeReportManager(tenantId); employeeReportManager.put(tenantId,r); } return r; } }
4、装饰者模式
装饰者(Decorator)和被装饰者(ConcreteComponent)拥有相同的接口,装饰者可以在被装饰者的方法上加上特定的前后置处理,增强被装饰者的功能。
/** * 接口 */ public interface IPacketCreator { String handleContent(); }
public abstract class PacketDecorator implements IPacketCreator { IPacketCreator iPacketCreator; public PacketDecorator(IPacketCreator iPacketCreator) { this.iPacketCreator = iPacketCreator; } }
public class PacketHTMLHeaderCreator extends PacketDecorator { public PacketHTMLHeaderCreator(IPacketCreator iPacketCreator) { super(iPacketCreator); } /** * 将数据封装成html格式 * @return */ @Override public String handleContent() { StringBuffer sb=new StringBuffer(); sb.append("<html>"); sb.append("<body>"); sb.append(iPacketCreator.handleContent()); sb.append("</body>"); sb.append("</html>/n"); return sb.toString(); } }
public class PacketHTTPHeaderCreator extends PacketDecorator { public PacketHTTPHeaderCreator(IPacketCreator iPacketCreator) { super(iPacketCreator); } @Override public String handleContent() { StringBuffer sb=new StringBuffer(); sb.append("Cache-Control:no-cache/n"); sb.append(iPacketCreator.handleContent()); return sb.toString(); } }
使用类:
@Test public void test3(){ IPacketCreator iPacketCreator = new PacketHTTPHeaderCreator(new PacketHTMLHeaderCreator(new PacketBodyCreator())); System.out.println(iPacketCreator.handleContent()); }
5、观察者模式
在软件系统中,当一个对象的行为依赖于另一个对象的状态时,观察者模式就非常有用。
代码示例如下:
package com.mmc.concurrentcystudy.design.guanchazhe; import java.util.Observable; import java.util.Observer; /** * 读者类,实现了观察者接口 */ public class Reader implements Observer { private String name; public Reader(String name) { this.name = name; } public String getName() { return name; } public void setName(String name) { this.name = name; } /** * 关注作者 * @param writerName */ public void subscribe(String writerName){ WriterManager.getInstance().getWriter(writerName).addObserver(this); } /** * 取消关注 * @param writerName */ public void unsunbscribe(String writerName){ WriterManager.getInstance().getWriter(writerName).deleteObserver(this); } /** * 业务方法 * @param o * @param arg */ @Override public void update(Observable o, Object arg) { if(o instanceof Writer){ Writer writer=(Writer)o; System.out.println(name+"知道"+writer.getName()+"发布了新书《"+writer.getLastNovel()+"》"); } } }
package com.mmc.concurrentcystudy.design.guanchazhe; import java.util.Observable; /** * 作者类,要继承自被观察者类 */ public class Writer extends Observable { private String name; //作者的名称 private String lastNovel; //记录作者最新发布的小说 public String getName() { return name; } public void setName(String name) { this.name = name; } public String getLastNovel() { return lastNovel; } public void setLastNovel(String lastNovel) { this.lastNovel = lastNovel; } public Writer(String name) { this.name = name; WriterManager.getInstance().add(this); } //发布新书 public void addNovel(String novel){ System.out.println(name+"发布了新书:《"+novel+"》"); lastNovel=novel; setChanged(); notifyObservers(); } }
package com.mmc.concurrentcystudy.design.guanchazhe; import java.util.HashMap; import java.util.Map; /** * 管理器,保持一份独有的作者列表 */ public class WriterManager { private Map<String,Writer> map=new HashMap<>(); public void add(Writer writer){ map.put(writer.getName(),writer); } public Writer getWriter(String name){ return map.get(name); } //单例 private WriterManager(){} public static WriterManager getInstance(){ return WriterManagerInstance.writerManager; } private static class WriterManagerInstance{ private static WriterManager writerManager=new WriterManager(); } }
测试方法:
@Test public void test4(){ Reader reader=new Reader("小明"); Reader reader2=new Reader("小红"); Reader reader3=new Reader("小李"); Writer writer=new Writer("韩寒"); Writer writer2=new Writer("李敖"); reader.subscribe("韩寒"); reader2.subscribe("韩寒"); reader3.subscribe("李敖"); writer.addNovel("三重门"); writer2.addNovel("不知道"); }
三、常见的优化组件
1、缓冲
示意图:
IO操作很容易形成性能瓶颈,所以尽可能加入缓冲组件。
2、缓存
缓存是为了系统性能而开辟的内存空间。最为简单的缓存是使用HashMap,但是这样做会遇到很多问题,比如不知道合适清理无效的数据,如何防止数据过多而内存溢出。
现在有很多缓存框架,如EHCache,OSCache,JBossCache等。
3、对象复用-“池”
如果一个类被频繁的使用,那么不必每次都生成一个实例,可以将这个实例保存在一个池中,待需要的时候直接从池中获取。这个池就称为对象池。
Apache中提供了一个Jakarta Commons Pool对象池组件,可以直接使用。
API列表:
public interface ObjectPool<T> extends Closeable { //从对象池中获取到一个对象 T borrowObject() throws Exception, NoSuchElementException, IllegalStateException; //对象返回给对象池 void returnObject(T var1) throws Exception; }
Common Pool中内置了3个对象池,分别是StackObjectPool,GenericObjectPool,SoftReferenceObjectPool。
-
StackObjectPool:利用Stack来保存对象,可以指定初始化大小。
-
GenericObjectPool:是一个通用的对象池,可以设定对象池的容量,也可以设定无可用对象时应该怎样,有一个复杂的构造函数来定义这些行为。
-
SoftReferenceObjectPool:使用的是ArrayList保存,保存的是对象的软引用。
使用示例:
/** * 对象池 */ public class PoolFactory extends BasePooledObjectFactory<Object> { static GenericObjectPool<Object> pool = null; // 取得对象池工厂实例 public synchronized static GenericObjectPool<Object> getInstance() { if (pool == null) { GenericObjectPoolConfig poolConfig = new GenericObjectPoolConfig(); poolConfig.setMaxIdle(-1); poolConfig.setMaxTotal(-1); poolConfig.setMinIdle(100); poolConfig.setLifo(false); pool = new GenericObjectPool<Object>(new PoolFactory(), poolConfig); } return pool; } public static Object borrowObject() throws Exception{ return (Object) PoolFactory.getInstance().borrowObject(); } public static void returnObject(Object jdbcUtils) throws Exception{ PoolFactory.getInstance().returnObject(jdbcUtils); } public static void close() throws Exception{ PoolFactory.getInstance().close(); } public static void clear() throws Exception{ PoolFactory.getInstance().clear(); } @Override public Object create() throws Exception { return new Object(); } @Override public PooledObject<Object> wrap(Object obj) { return new DefaultPooledObject<Object>(obj); } }
测试代码:
@Test public void test6() throws Exception { Object o=PoolFactory.borrowObject(); PoolFactory.returnObject(o); Object o2=PoolFactory.borrowObject(); PoolFactory.returnObject(o2); System.out.println(o==o2); }
注意:只有对重量级对象使用对象池技术才能提高系统性能,对轻量级的对象使用反而会降低性能。
4、并行替代串行
5、负载均衡
为保证应用程序的服务质量,需要使用多台计算机协同工作,将系统负载尽可能分配到各个计算机节点上。
6、时间换空间
一般用于嵌入式设备或者内存,硬盘不足的情况下。
比如一个简单的例子,a和b两个变量的值的替换。最常用的方法是引入一个中间变量。为了省去中间变量可以用这样的方法:
@Test public void test7(){ int a=3; int b=5; a=a+b; b=a-b; a=a-b; System.out.println(a); System.out.println(b); }
7、空间换时间
最典型的应用就是缓存了,除了缓以外,有一些排序方法也会用到。
一个空间换时间的排序示例:
/** * 空间换时间的排序 */ public class SpaceSort { public static int arrayLen=1000000; public static void main(String[] args) { int [] a=new int[arrayLen]; int [] old=new int[arrayLen]; Map<Integer,Object> map=new HashMap<>(); int count=0; while (count<a.length){ //初始化数组 int value=(int)(Math.random()*arrayLen*10); if(map.get(value)==null){ map.put(value,value); a[count]=value; count++; } } System.arraycopy(a,0,old,0,a.length); long start=System.currentTimeMillis(); Arrays.sort(a); System.out.println("Arrays.sort spend:"+(System.currentTimeMillis()-start)); start=System.currentTimeMillis(); spaceToTime(old); System.out.println("spaceToTime spend:"+(System.currentTimeMillis()-start)); } public static void spaceToTime(int[] array){ int i=0; int max=array[0]; int l=array.length; //找出最大值 for (i=0;i<l;i++){ if(array[i]>max) max=array[i]; } int []temp=new int[max+1]; //分配临时空间 for (i=0;i<l;i++){ temp[array[i]]=array[i]; //以索引下标标识数字大小 } int j=0; int max1=max+1; for (i=0;i<max1;i++){ //线性复杂度 if (temp[i]>0){ array[j++]=temp[i]; } } } }
四、Java程序优化
1、字符串优化
使用StringTokenizer类分割字符串
public void test9(){ StringBuilder sb=new StringBuilder(); int len=1000; for (int i=0;i<len;i++){ sb.append(i); sb.append(";"); } String str=sb.toString(); long start=System.currentTimeMillis(); StringTokenizer st=new StringTokenizer(str,";"); for (int i=0;i<10000;i++){ while (st.hasMoreElements()){ String s = st.nextToken(); } st=new StringTokenizer(str,";"); } System.out.println(System.currentTimeMillis()-start); }
目前来说这种方法也没快多少
2、数据结构
ArrayList和LinkedList
- 添加元素到队列尾部 ArrayList块
- 添加到任意位置 LinkedList快
删除对比:
- 在能够有效评估ArrayList数组大小时,指定容量大小能对性能有提升
- LinkedList不要用for(int i=0;i<list.size();i++)遍历,用foreach。即如果需要通过索引下标对集合进行访问,那最好用ArrayList
TreeMap和LinkedHashMap的区别。
他们都是可排序的,LinkedHashMap基于元素进入集合或者被访问的先后顺序排序,TreeMap是根据元素的固有顺序(Comparator或者Comparable)
3、优化集合访问方法
- 分离循环中被重复调用的代码
如下面的那个list.size()会多次调用。但实际上这个方法也很快
for (int i=0;i<list.size();i++){ count++; } len=list.size(); for (int i=0;i<len;i++){ count++; }
- 减少方法调用
如果可以直接访问内部元素,就不用调用对应的接口。因为函数调用是需要消耗系统资源的。
4、NIO提升性能
在读写文件上使用NIO会更快
以写入4000000的int数字为例
— | Stream | ByteBuffer | MappedByteBuffer |
---|---|---|---|
写耗时 | 295ms | 123ms | 32ms |
读耗时 | 433ms | 59ms | 17ms |
测试示例:
/** * IO写文件 * @throws IOException */ @Test public void test12() throws IOException { long start=System.currentTimeMillis(); DataOutputStream dos=new DataOutputStream(new BufferedOutputStream(new FileOutputStream("d://1.txt"))); for (int i=0;i<4000000;i++){ dos.writeInt(i); } if(dos!=null) dos.close(); System.out.println(System.currentTimeMillis()-start); } /** * IO读文件 * @throws IOException */ @Test public void test13() throws IOException { long start=System.currentTimeMillis(); DataInputStream dis=new DataInputStream(new BufferedInputStream(new FileInputStream("d://1.txt"))); for (int i=0;i<4000000;i++){ dis.readInt(); } if(dis!=null) dis.close(); System.out.println(System.currentTimeMillis()-start); } /** * NIO写文件 * @throws IOException */ @Test public void test14() throws IOException { long start=System.currentTimeMillis(); FileOutputStream fos=new FileOutputStream("d:/1.txt"); FileChannel fc=fos.getChannel(); ByteBuffer byteBuffer=ByteBuffer.allocate(4000000*4); for (int i = 0; i <4000000 ; i++) { byteBuffer.put(int2byte(i)); } byteBuffer.flip(); fc.write(byteBuffer); System.out.println(System.currentTimeMillis()-start); } /** * NIO读文件 * @throws IOException */ @Test public void test15() throws IOException { long start=System.currentTimeMillis(); FileInputStream fin=new FileInputStream("d://1.txt"); FileChannel channel = fin.getChannel(); ByteBuffer byteBuffer=ByteBuffer.allocate(4000000*4); channel.read(byteBuffer); channel.close(); byteBuffer.flip(); while (byteBuffer.hasRemaining()){ byte2int(byteBuffer.get(),byteBuffer.get(),byteBuffer.get(),byteBuffer.get()); } System.out.println(System.currentTimeMillis()-start); } public static byte[] int2byte(int res){ byte[] targets=new byte[4]; targets[3]=(byte)(res&0xff); targets[2]=(byte)((res>>8)&0xff); targets[1]=(byte)((res>>16)&0xff); targets[0]=(byte)(res>>24); return targets; } public static int byte2int(byte b1,byte b2,byte b3,byte b4){ return ((b1&0xff)<<24)|((b2&0xff)<<16)|((b3&0xff)<<8)|(b4&0xff); } /** * NIO MappedByteBuffer写文件 * @throws IOException */ @Test public void test16() throws IOException { long start=System.currentTimeMillis(); FileChannel fc=new RandomAccessFile("d://1.txt","rw").getChannel(); IntBuffer ib=fc.map(FileChannel.MapMode.READ_WRITE,0,4000000*4).asIntBuffer(); for (int i = 0; i < 4000000; i++) { ib.put(i); } if(fc!=null) fc.close(); System.out.println(System.currentTimeMillis()-start); } /** * NIO MappedByteBuffer读文件 * @throws IOException */ @Test public void test17() throws IOException { long start=System.currentTimeMillis(); FileChannel fc=new FileInputStream("d://1.txt").getChannel(); IntBuffer ib=fc.map(FileChannel.MapMode.READ_ONLY,0,fc.size()).asIntBuffer(); while (ib.hasRemaining()){ ib.get(); } if(fc!=null) fc.close(); System.out.println(System.currentTimeMillis()-start); }
2、直接内存访问
DirectBuffer:直接可以访问系统物理内存的类,在对普通的ByteBuffer访问时,系统会使用一个“内核缓冲区”进行间接的操作,而DirectBuffer所处的位置就相当于这个“内核缓冲区”。因此他更接近底层,更快。
但是DirectBuffer创建销毁都比较费时间,在需要频繁创建和销毁的情况下不适合用。
/** * 使用DirectBuffer * @throws IOException */ @Test public void test18() throws IOException { long start=System.currentTimeMillis(); ByteBuffer b=ByteBuffer.allocateDirect(500); for (int i=0;i<100000;i++){ for (int j=0;j<99;j++) b.putInt(j); b.flip(); for (int j=0;j<99;j++) b.getInt(); b.clear(); } System.out.println(System.currentTimeMillis()-start); } @Test public void test19() throws IOException { long start=System.currentTimeMillis(); ByteBuffer b=ByteBuffer.allocate(500); for (int i=0;i<100000;i++){ for (int j=0;j<99;j++) b.putInt(j); b.flip(); for (int j=0;j<99;j++) b.getInt(); b.clear(); } System.out.println(System.currentTimeMillis()-start); }
5、引用类型
- 强引用
- 软引用
GC的时候,因为内存没满,没有被回收。
@Test public void test20(){ MyObject myObject=new MyObject(); //创建引用队列 ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>(); //创建软引用 SoftReference<MyObject> softReference=new SoftReference<>(myObject,referenceQueue); myObject=null; System.gc(); System.out.println("After Gc:soft get="+softReference.get()); System.out.println("分配大内存"); byte[] b=new byte[4*1024*925]; System.out.println("After new byte[]:soft get="+softReference.get()); }
- 弱引用
在GC的时候一旦发现有弱引用,直接被回收
@Test public void test20(){ MyObject myObject=new MyObject(); //创建引用队列 ReferenceQueue<MyObject> referenceQueue = new ReferenceQueue<>(); //创建软引用 WeakReference<MyObject> softReference=new WeakReference<>(myObject,referenceQueue); myObject=null; System.gc(); System.out.println("After Gc:soft get="+softReference.get()); System.out.println("分配大内存"); byte[] b=new byte[4*1024*925]; System.out.println("After new byte[]:soft get="+softReference.get()); }
- 虚引用
虚引用是引用类型最弱的一个,他的作用在于跟踪垃圾回收。 - WeakHashMap
当需要使用HashMap做一个简单的缓存时,建议使用WeakHashMap,他是弱引用的,可以在内存满的情况下,GC时清除没有被引用的表项
6、改善性能小技巧
- 使用局部变量
调用方法时传递的参数和在方法在创建的临时变量都保存在栈中,速度较快。其他变量,如静态变量,实例变量都在堆中。
@Test public void test21() throws IOException { long start=System.currentTimeMillis(); int a=0; for (int i=0;i<2000000000;i++){ a++; } System.out.println(a); System.out.println(System.currentTimeMillis()-start); } private static int ta=0; @Test public void test22() throws IOException { long start=System.currentTimeMillis(); int a=0; for (int i=0;i<2000000000;i++){ ta++; } System.out.println(ta); System.out.println(System.currentTimeMillis()-start); }
- 位运算代替乘除法
a*2 用a<<1
a/2 用a>>1
3. 替代switch
用数组替代switch,效率会更高
public int sw(int a){ switch (a){ case 1:return 1; case 2:return 3; case 3:return 5; case 4:return 9; default:return 0; } } //用数组来实现 public int sw2(int a){ int[] array=new int[]{1,3,5,9,0}; return array[a]; }
4、复制数组用System.arraycopy
System.arraycopy时浅拷贝。对于非基本类型而言,他拷贝的是对象的引用,而非新建一个对象。
5、clone方法代替new
clone方法不会调用构造函数,所以能够快速的创造一个实例。默认情况下是浅拷贝。但是拷贝的对象修改属性,旧的对象的值可能是不会变的。
6、静态方法代替实例方法
实例方法需要维护一张类似虚拟结构表的东西以支持对多态的实现。所以比静态方法慢。