――Java培训、Android培训、iOS培训、.Net培训、期待与您交流! ――
一:动态代理技术
1.程序中的代理
为具有相同接口的目标类的各个方法,添加一些系统功能,如日志,异常处理,计算方法运行的
时间,事务管理等等,都可以交给另一个类去实现这些功能,该类称为代理类。
注意:为了让代理类共享目标类中的各个方法,可以让代理类实现和目标类相同的接口。
public class AProxy { //AProxy类为A的代理类,可以计算sayHi方法的运行时间 public void getTime() { //方法开始前时间 new A().sayHi(); //方法结束后时间 } } class A { void sayHi() { System.out.println("hi,everyone!"); } }
2.代理架构图
它采用工厂模式和配置文件的方式进行管理,这样就不需要修改客户端程序,通过
配置文件指定使用目标类,还是代理类。这样做添加/去掉系统功能变得简单。
当我们需要测试系统的性能时,可以使用代理类,客户要使用时可以使用目标类
3.AOP(Aspect oriented program)
交叉业务:要处理不同层面的业务(service),称为交叉业务,如安全,事务,日志等。
安全 事务 日志
StudentService ------|----------|------------|-------------
CourseService ------|----------|------------|-------------
MiscService ------|----------|------------|-------------
用具体的程序代码描述交叉业务:
method1 method2 method3
{ { {
------------------------------------------------------切面
.... .... ......
------------------------------------------------------切面
} } }
面向方面的编程(简称AOP),AOP的目标就是要使交叉业务模块化。
可以采用将切面代码移动到原始方法的周围,这与直接在方法中编写切面代码的运行效果是一样的,如下所示:
------------------------------------------------------切面
func1 func2 func3
{ { {
.... .... ......
} } }
------------------------------------------------------切面
这里就是使用代理技术解决这种问题,代理是实现AOP功能的核心和关键技术。
4.动态代理技术
要为系统中的各种接口的类增加代理功能,那将需要太多的代理类,全部采用静态代理方式,将是一件非常麻烦的事情!
JVM提供了动态代理类(Proxy)。即可以在运行期动态生成出类的字节码,这种动态生成的类往往被用作代理类。这里是仅指具有接口的代理类和目标类。
对于没有接口的目标类,CGLIB库可以动态生成一个类的子类,一个类的子类也可以用作该类的代理,所以,如果要为一个没有实现接口的类生成动态代理类,那么可以使用CGLIB库。
代理方法中的有如下四个位置加上系统功能代码:
1.在调用目标方法之前
2.在调用目标方法之后
3.在调用目标方法前后
4.在处理目标方法异常的catch块中
class proxy{ void sayHello(){ ………//1 try{ target.sayHello(); }catch(Exception e){ ………//4 } …………//2 } }
5.Proxy(代理类)
|-java.lang.reflect.Proxy
Proxy提供了创建动态代理类和实例的静态方法,它也是这些创建出来类的超(父)类。
构造方法:
protected Proxy(InvocationHandler h);//使用其调用处理程序的指定值从子类(通常为动态代理类)构建新的 Proxy
实例
成员方法:
static Class<?> getProxyClass(ClassLoader loader,Class<?> interfaces);//创建一个具有接口的动态代理类。
static Object newProxyInstance (ClassLoader loader,Class<?>[] interfaces,InvocationHandler h);//返回一个代理类实例对象
创建代理类及其实例对象:
法一:先创建代理类,再用代理类的构造方法创建实例对象
public static void main(String[] args) { // 获得一个集合接口的代理类,为其指定接口和类加载器 //通常loader,interface是一样的字节码产生的 Class clazzProxy1=Proxy.getProxyClass(Collection.class.getClassLoader(), Collection.class); System.out.println(clazzProxy1.getName()); //创建一个代理类实例 //注意:clazzProxy1.newInstance()调用的是它的无参构造方法创建实例,但是它没有无参的构造方法 //通过有参构造方法创建实例 Constructor ct=clazzProxy1.getConstructor(InvocationHandler.class); class MyinvocationHandler implements InvocationHandler { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { return null; } } Collection proxy1=(Collection) ct.newInstance(new MyinvocationHandler()); }}
法二:用new方法直接创建代理类实例对象
Object target=new Object (); Object proxy1=Proxy.newProxyInstance(target.getClass().getClassLoader(),target.getClass().getInterfaces(), new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { // TODO Auto-generated method stub return null; } }); Class<?> []cs={Collection.class}; Collection proxy3=(Collection)Proxy.newProxyInstance (Collection.class.getClassLoader(), cs,new MyinvocationHandler()); Collection proxy4=(Collection)Proxy.newProxyInstance (Collection.class.getClassLoader(), new Class[] {Collection.class}, new MyinvocationHandler());
6.InvocationHandler(接口)
成员方法:
Object invoke(Object proxy, Method method, Object[] args);//
在代理实例上处理方法调用并返回结果。
事实上,Proxy代理类对象调用(该接口上的)方法时,其实是去调用了handler的
invoke方法。对于不是该接口上的方法,如getClass(),则调用它本身的,不用委托handler去调用invoke方法。
7.代理类的模式
以面向对象的思想处理AOP(面向方面编程)的业务,即在实现代理的系统功能时,将目标类对象和接口(该接口通常以实现接口某个类的方式传入,实现接口里的方法(系统功能)的类)作为参数传入一个要实现系统功能的方法中。
ArrayList al=new ArrayList(); Collection proxy4=(Collection)getProxy(al,new Myadvice()); proxy4.add("hq"); //以下是封装了实现代理功能的一个方法 //参数:目标类对象,和接口(实现代理功能的类,类里有实现功能的方法),该接口通常以实现接口某个类的方式传入。 private static Object getProxy(final Object target,final interfaceAdvice advice) { Object proxy=Proxy.newProxyInstance (target.getClass().getClassLoader(), target.getClass().getInterfaces(), new InvocationHandler(){ @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { advice.beforeMethod(method); Object obj=method.invoke(target, args); advice.afterMethod(method); return obj; } }); return proxy; }
8.实现AOP功能的封装与配置
工厂类BeanFactory负责创建目标类或代理类的实例对象,并通过配置文件实现切换。其getBean方法根据参数字符串返回一个相应的实例对象,如果参数字符串在配置文件中对应的类名不是ProxyFactoryBean,则直接返回该类的实例对象,否则,返回该类实例对象的getProxy方法返回的对象。
BeanFactory的构造方法接收代表配置文件的输入流对象,配置文件格式如下:
#xxx=java.util.ArrayList
xxx=cn.itcast.ProxyFactoryBean
xxx.target=java.util.ArrayList
xxx.advice=cn.itcast.MyAdvice
ProxyFacotryBean充当封装生成动态代理的工厂,需要为工厂类提供哪些配置参数信息?
目标:target
通知:advice
编写客户端应用:
编写实现Advice接口的类和在配置文件中进行配置
调用BeanFactory获取对象
二:线程同步与并发
1.Timer类
import java.util.Date; import java.util.Timer; import java.util.TimerTask; public class TimerDemo { public static void main(String[] args) throws Exception{ /* * Timer类的演示:ava.util.Timer类是一个定时器,可以让它定时做某件事情, * 方法:schedule(task,delay,period);//在delay毫秒后执行task,并以period为周期循环执行。 * * */ Timer t=new Timer(); // t.schedule(new TimerTask () // { // @Override // public void run() { // System.out.println("bombing!!!"); // } // }, 3000); t.schedule(new MyTimerTask(), 3000); //每隔一秒,打印出时间 while(true) { System.out.println(new Date().getSeconds()); Thread.currentThread().sleep(1000); } } } //实现了每3秒爆炸,每2秒爆炸,并以此循环 //写成类的原因是:我们要不停的使用该类中的run代码, //这样可以通过不停的new对象出来重复使用run代码 //int i写成静态的原因是:new的对象要共享数据i,所以写成静态 //i=(i+1)%count,的结果是i以count的为周期,不停循环。 //注意:这里不能写成TimerDemo的内部类,因为一个非静态的内部类相当于其他非静态的方法,不能在 // 里面声明静态变量(static int i)!!或静态方法!! class MyTimerTask extends TimerTask { static int i=0; @Override public void run() { System.out.println("bombing!!!"); new Timer().schedule(new MyTimerTask() , (i+2)*1000); i=(i+1)%2; } }
2.多个线程访问共享对象和数据的方式
2.1 如果每个线程执行的代码相同,可以使用同一个Runnable对象,这个Runnable对象中有那个共享数据,例如,买票系统就可以这么做。
2.2 如果每个线程执行的代码不同,这时候需要用不同的Runnable对象,有如下两种方式来实现这些Runnable对象之间的数据共享:
a.将共享数据封装在另外一个对象中,然后将这个对象逐一传递给各个Runnable对象。每个线程对共享数据的操作方法也分配到那个对象身上去完成,
这样容易实现针对该数据进行的各个操作的互斥和通信。
b.将这些Runnable对象作为某一个类中的内部类,共享数据作为这个外部类中的成员变量,每个线程对共享数据的操作方法也分配给外部类,以便实现
对共享数据进行的各个操作的互斥和通信,作为内部类的各个Runnable对象调用外部类的这些方法。
上面两种方式的组合:将共享数据封装在另外一个对象中,每个线程对共享数据的操作方法也分配到那个对象身上去完成,对象作为这个外部类中的成员变量或方法中的局部变量
每个线程的Runnable对象作为外部类中的成员内部类或局部内部类。
总之,要同步互斥的几段代码最好是分别放在几个独立的方法中,这些方法再放在同一个类中,这样比较容易实现它们之间的同步互斥和通信。
极端且简单的方式,即在任意一个类中定义一个static的变量,这将被所有线程共享。
3.ThreadLoal解决线程同步问题(http://blog.csdn.net/qjyong/article/details/2158097)
对于多线程资源共享的问题,同步机制采用了“以时间换空间”的方式,而ThreadLocal采用了“以空间换时间”的方式。
前者仅提供一份变量,让不同的线程排队访问,而后者为每一个线程都提供了一份变量,因此可以同时访问而互不影响。
ThreadLocal并不是一个Thread,而是Thread的局部变量,也许把它命名为线程局部变量(ThreadLocalVariable)更容易让人理解一些。
ThreadLocal类接口很简单,只有4个方法,我们先来了解一下:
void set(Object value)
设置当前线程的线程局部变量的值。
public Object get()
该方法返回当前线程所对应的线程局部变量。
public void remove()
将当前线程局部变量的值删除,目的是为了减少内存的占用,该方法是JDK 5.0新增的方法。需要指出的是,当线程结束后,
对应该线程的局部变量将自动被垃圾回收,所以显式调用该方法清除线程的局部变量并不是必须的操作,但它可以加快内存回收的速度。
protected Object initialValue()
返回该线程局部变量的初始值,该方法是一个protected的方法,显然是为了让子类覆盖而设计的。这个方法是一个延迟调用方法,
在线程第1次调用get()或set(Object)时才执行,并且仅执行1次。ThreadLocal中的缺省实现直接返回一个null。
值得一提的是,在JDK5.0中,ThreadLocal已经支持泛型,该类的类名已经变为ThreadLocal<T>。
API方法也相应进行了调整,新版本的API方法分别是void set(T value)、T get()以及T initialValue()。
public class SequenceNumber { ①通过匿名内部类覆盖ThreadLocal的initialValue()方法,指定初始值 private static ThreadLocal<Integer> seqNum = new ThreadLocal<Integer>(){ public Integer initialValue(){ return 0; } };
②获取下一个序列值 public int getNextNum(){ seqNum.set(seqNum.get()+1); return seqNum.get(); }
public static void main(String[] args) { SequenceNumber sn = new SequenceNumber(); ③ 3个线程共享sn,各自产生序列号 TestClient t1 = new TestClient(sn); TestClient t2 = new TestClient(sn); TestClient t3 = new TestClient(sn); t1.start(); t2.start(); t3.start(); } private static class TestClient extends Thread { private SequenceNumber sn; public TestClient(SequenceNumber sn) { this.sn = sn; } public void run() { for (int i = 0; i < 3; i++) {④每个线程打出3个序列值 System.out.println("thread["+Thread.currentThread().getName()+ "] sn["+sn.getNextNum()+"]"); } } } }
通常我们通过匿名内部类的方式定义ThreadLocal的子类,提供初始的变量值,如例子中①处所示。TestClient线程产生一组序列号,在③处,我们生成3个TestClient,它们共享同一个SequenceNumber实例。运行以上代码,在控制台上输出以下的结果:
thread[Thread-2] sn[1] thread[Thread-0] sn[1] thread[Thread-1] sn[1] thread[Thread-2] sn[2] thread[Thread-0] sn[2] thread[Thread-1] sn[2] thread[Thread-2] sn[3] thread[Thread-0] sn[3] thread[Thread-1] sn[3]
小结
ThreadLocal是解决线程安全问题一个很好的思路,它通过为每个线程提供一个独立的变量副本解决了变量并发访问的冲突问题。
在很多情况下,ThreadLocal比直接使用synchronized同步机制解决线程安全问题更简单,更方便,且结果程序拥有更高的并发性。
4.Executors类
|-java.util.concurrent.Executors 它可以提供了多种功能的线程池
静态方法:
ExecutorService newFixedThreadPool(int nThreads); //创建固定大小的线程池
ExecutorService newCachedThreadPool();//创建缓存线程池
ExecutorService newSingleThreadExecutor();//创建单一线程池
ScheduledExecutorService newScheduledThreadPool(int);//创建定时处理的线程池
5.ExecutorService
成员方法:
void
shutdown();//
对于执行完所有任务的线程会处于wait状态,所以最后需要
Executors类的shutdown方法销毁执行完任务的线程
void execute(Runnable command);//可以添加多个任务
Future<T> submit(Callable<T> task);//在task里会调用call方法,将结果返回给future
6.ScheduledExecutorService
schedule(Runnable command, long delay, TimeUnit unit);//
创建并执行在给定延迟后启用的一次性操作
线程池启动定时器
调用ScheduledExecutorService的schedule方法,返回的ScheduleFuture对象可以取消任务。
支持间隔重复任务的定时方式,不直接支持绝对定时方式,需要转换成相对时间方式。
7.CompletionService<V>
所有已知实现类:ExecutorCompletionService 它是ExecutorService里submit方法的加强版,可以submit多个Callable<T> task,使用者take会按照完成这些任务的顺序处理它们的结果(get方法返回结果)。
成员方法:
Future<V>
take();//
获取并移除表示下一个已完成任务的 Future,如果目前不存在这样的任务,则等待。
Future<V>
submit(Callable<V> task);//
提交要执行的值返回任务,并返回表示挂起的任务结果的 Future。
//ExecutorService类下的<T> Future<T> submit(Callable<T> task) 方法使用 ExecutorService pool= Executors.newSingleThreadExecutor(); Future<String> future=pool.submit(new Callable<String>() { @Override public String call() throws Exception { // TODO Auto-generated method stub System.out.println(Thread.currentThread().getName()); Thread.sleep(3000); return "哈哈哈"; } }); System.out.println("等待结果"); System.out.println("结果:"+future.get()); //ExecutorCompletionService类演示 CompletionService<Integer> service=new ExecutorCompletionService<Integer>(Executors.newFixedThreadPool(3)); //submit 10个task for(int i=0;i<10;i++) { final int t=i+1; service.submit(new Callable<Integer>() { @Override public Integer call() throws Exception { Thread.sleep(new Random().nextInt(5)*1000); return t; } }); } //按先后顺序取出结果 for(int j=0;j<10;j++) { System.out.println(service.take().get()); }
//线程池的演示:Executors类下(静态方法)提供了多种功能的线程池 /* 注意:可以添加任意个任务进去.(ExecutorService类下的void execute(Runnable command)方法) * 创建固定大小的线程池:Executors.newFixedThreadPool(3);3为自动启动3个线程执行任务 创建缓存线程池:Executors.newCachedThreadPool();//系统会根据你添加多少任务智能分配多少个线程帮你执行 创建单一线程池:Executors.newSingleThreadExecutor();//系统会保证始终一个线程帮你执行任务,如果该线程死了,系统会创建出另一个线程代替。 创建定时处理的线程池:Executors.newScheduledThreadPool(3).schedule()方法 注意:对于执行完所有任务的线程会处于wait状态,所以最后需要 Executors类的shutdown方法销毁执行完任务的线程。 * */ ExecutorService pool=Executors.newFixedThreadPool(3); Executors.newCachedThreadPool(); Executors.newSingleThreadExecutor(); //添加任务 for(int i=0;i<5;i++) { final int task =i+1; pool.execute(new Runnable() { @Override public void run() { for(int j=0;j<10;j++) System.out.println(Thread.currentThread().getName()+"执行第"+task+"个任务:"+(j+1)); } }); } System.out.println("shutdown"); pool.shutdown();
8.java.util.concurrent.atomic下
有很多对基本数据的原子(同步)操作,甚至是类中的成员变量