在程式開發過程當中,往往存在這樣一種情況,程式首先執行完method1得到結果result1之後,在執行method2獲得結果result2,然後再按照result1和result2的結果來判定程式下一步的執行。在這裡method1和method2是相互不關聯的,即method1的執行和method2的執行位置可以調整,而不影響程式的執行結果。
1、串列程式
傳統意義上的寫法,我們得到的往往會是串列執行的程式形態,程式的總的執行時間是method1的執行時間time1加上method2的執行時間time2,這樣總的執行時間time=time1+time2。我們得到的是串列的程式形態。
import com.yang.domain.BaseResult;import org.junit.Test;import java.util.concurrent.TimeUnit;/** * @Description: * @Author: yangzl2008 * @Date: 2016/1/9 19:06 */public class Serial { @Test public void test() { long start = System.currentTimeMillis(); BaseResult baseResult1 = method1();// 耗時操作1,時間 time1 BaseResult baseResult2 = method2();// 耗時操作2,時間 time2 long end = System.currentTimeMillis(); //總耗時 time = time1 + time2 System.out.println("baseResult1 is " + baseResult1 + "\nbaseResult2 is " + baseResult2 + "\ntime cost is " + (end - start)); } private BaseResult method1() { BaseResult baseResult = new BaseResult(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } baseResult.setCode(1); baseResult.setMsg("method1"); return baseResult; } private BaseResult method2() { BaseResult baseResult = new BaseResult(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } baseResult.setCode(1); baseResult.setMsg("method2"); return baseResult; }}執行結果:
baseResult1 is BaseResult{code=1, msg='method1'}baseResult2 is BaseResult{code=1, msg='method2'}time cost is 2000
2、串列程式的多線程過渡 而這種代碼是不是可最佳化的地方呢。加快程式的執行效率,降低程式的執行時間。在這裡method1和method2是相互不關聯的,即method1的執行和method2的執行位置可以調整,而不影響程式的執行結果,我們可不可以為建立線程1執行method1然後建立線程2來執行method2呢,因為method1和method2都需要得到結果,因此我們需要使用Callable介面,然後使用Future.get()得到執行的結果,但實際上Future.get()在程式返回結果之前是阻塞的,即,線程1在執行method1方式時,程式因為調用了Future.get()會等待在這裡直到method1返回結果result1,然後線程2才能執行method2,同樣,Future.get()也會一直等待直到method2的結果result2返回,這樣,我們開啟了線程1,開啟了線程2並沒有得到並發執行method1,method2的效果,反而會因為程式開啟線程而多佔用了程式的執行時間,這樣程式的執行時間time=time1+time2+time(線程開啟時間)。於是我們得到了串列程式的過渡態。
import com.yang.domain.BaseResult;import org.junit.Test;import java.lang.reflect.Method;import java.util.concurrent.*;/** * @Description: * @Author: yangzl2008 * @Date: 2016/1/9 19:13 */public class SerialCallable { @Test public void test01() throws Exception { // 兩個線程的線程池 ExecutorService executorService = Executors.newFixedThreadPool(2); long start = System.currentTimeMillis(); // 開啟線程執行 Future<BaseResult> future1 = executorService.submit(new Task(this, "method1", null)); // 阻塞,直到程式返回。耗時time1 BaseResult baseResult1 = future1.get(); // 開啟線程執行 Future<BaseResult> future2 = executorService.submit(new Task(this, "method1", null)); // 阻塞,直到程式返回。耗時time2 BaseResult baseResult2 = future2.get(); long end = System.currentTimeMillis(); // 總耗時 time = time1 + time2 + time(線程執行耗時) System.out.println("baseResult1 is " + baseResult1 + "\nbaseResult2 is " + baseResult2 + "\ntime cost is " + (end - start)); } public BaseResult method1() { BaseResult baseResult = new BaseResult(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } baseResult.setCode(1); baseResult.setMsg("method1"); return baseResult; } public BaseResult method2() { BaseResult baseResult = new BaseResult(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } baseResult.setCode(1); baseResult.setMsg("method2"); return baseResult; } class Task<T> implements Callable<T> { private Object object; private Object[] args; private String methodName; public Task(Object object, String methodName, Object[] args) { this.object = object; this.args = args; this.methodName = methodName; } @Override public T call() throws Exception { Method method = object.getClass().getMethod(methodName); return (T) method.invoke(object, args); } }}執行結果:
baseResult1 is BaseResult{code=1, msg='method1'}baseResult2 is BaseResult{code=1, msg='method1'}time cost is 2001
3、並行程式 有沒有方法可以在執行method1的時候同時執行method2,最後得到結果再進行處理。我們回到問題的出處,程式首先執行完method1得到結果result1之後,在執行method2獲得結果result2,然後再按照result1和result2的結果來判定程式下一步的執行,最終我們得到的結果是result1和result2,然後再進行下一步操作,那麼在我們得到result1和result2的時候,method1和method2其實是可以並發執行的,即我首先執行method1然後再執行mothod2,我不管他們的返回結果,只有在我要拿result1和result2進行操作的時候,程式才會調用Future.get()方法(這個方法會一直等待,直到結果返回),這是一種
消極式載入的思想,
與Hibernate中屬性的消極式載入是一致的,即對於屬性A,平時我是不用時不會進行賦值,只有我在用的時候,才執行SQL查詢對其進行賦值操作。於是,我們得到了並發執行的程式形態。
Hibernate亦使用CGLIB來實現消極式載入,因此,我們可以考慮使用CGLIB的消極式載入類,將串列的程式並行化。
import com.yang.domain.BaseResult;import net.sf.cglib.proxy.Enhancer;import net.sf.cglib.proxy.LazyLoader;import org.junit.Test;import java.lang.reflect.Method;import java.util.concurrent.*;/** * @Description: * @Author: yangzl2008 * @Date: 2016/1/9 19:39 */public class Parallel { @Test public void test02() throws Exception { ExecutorService executorService = Executors.newFixedThreadPool(2); long start = System.currentTimeMillis(); // 開啟線程執行 Future<BaseResult> future1 = executorService.submit(new Task(this, "method1", null)); // 不阻塞,正常執行,baseResult1是cglib的代理類,採用消極式載入,只有在使用的時候才調用方法進行賦值。 BaseResult baseResult1 = futureGetProxy(future1, BaseResult.class); // 開啟線程執行 Future<BaseResult> future2 = executorService.submit(new Task(this, "method2", null)); // 不阻塞,正常執行,baseResult1是cglib的代理類,採用消極式載入,只有在使用的時候才調用方法進行賦值。 BaseResult baseResult2 = futureGetProxy(future2, BaseResult.class); // 這裡要使用baseResult1和baseResult2 System.out.println("baseResult1 is " + baseResult1 + "\nbaseResult2 is " + baseResult2); long end = System.currentTimeMillis(); // 總耗時time = max(time1,time2) System.out.println("time cost is " + (end - start)); } private <T> T futureGetProxy(Future<T> future, Class clazz) { Enhancer enhancer = new Enhancer(); enhancer.setSuperclass(clazz); return (T) enhancer.create(clazz, new FutureLazyLoader(future)); } /** * 消極式載入類 * @param <T> */ class FutureLazyLoader<T> implements LazyLoader { private Future<T> future; public FutureLazyLoader(Future<T> future) { this.future = future; } @Override public Object loadObject() throws Exception { return future.get(); } } public BaseResult method1() { BaseResult baseResult = new BaseResult(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } baseResult.setCode(1); baseResult.setMsg("method1"); return baseResult; } public BaseResult method2() { BaseResult baseResult = new BaseResult(); try { TimeUnit.SECONDS.sleep(1); } catch (InterruptedException e) { e.printStackTrace(); } baseResult.setCode(1); baseResult.setMsg("method2"); return baseResult; } class Task<T> implements Callable<T> { private Object object; private Object[] args; private String methodName; public Task(Object object, String methodName, Object[] args) { this.object = object; this.args = args; this.methodName = methodName; } @Override public T call() throws Exception { Method method = object.getClass().getMethod(methodName); return (T) method.invoke(object, args); } }}執行結果:
baseResult1 is BaseResult{code=1, msg='method1'}baseResult2 is BaseResult{code=1, msg='method2'}time cost is 1057
4、思考 以上,將method1,method2分別開新線程執行,最終得到結果後,再繼續執行的實現有多種,在J.U.C中的CountDownLatch和CyclicBarrier就是能夠實現這種思想的典型工具類。
本文只是在另外想出一種實現方案而已,但對於已經成型的程式,即不改變程式碼的情況下,使用AOP,CGLIB等實現現有程式的並行化,本文思路更容易實現。使用Spring的AOP實現方案,在切面方法中開啟新線Callable程執行被代理的方法,同時使用CGLIB對傳回值進行消極式載入。
5、參考 實戰CGLib系列之proxy篇(三):消極式載入LazyLoader 實戰cglib 實現AOP — CGLIB
程式源碼下載