同步和非同步有何異同,在什麼情況下分別使用他們?
如果資料將線上程間共享.例如正在寫的資料以後可能被另一個執行緒讀到,或者正在讀的資料可能已經被另一個執行緒寫過了,那麼這些資料就是共享資料,必須進行同步存取.
當應用程式在物件上呼叫了一個需要花費很長時間來執行的方法,並且不希望讓程式等待方法的返回時,就應該使用非同步程式設計,在很多情況下采用非同步途徑往往更有效率.
Java同步:
基本概念:
每個Object都會有1個鎖.
同步就是序列使用一些資源.
(說明:以下有些例子為了突出重點,省略了不必要的程式碼.非凡是省掉了一些成員變數,就是需要同步的物件.)
1. 多執行緒中對共享、可變的資料進行同步.
對於函式中的區域性變數沒必要進行同步.
對於不可變資料,也沒必要進行同步.
多執行緒中訪問共享可變資料才有必要.
2. 單個執行緒中可以使用synchronized,而且可以巢狀,但無意義.
class Test {
public static void main(String[] args) {
Test t = new Test();
synchronized(t) {
synchronized(t) {
System.out.println("ok!");
}
}
}
}
3. 物件例項的鎖
class Test{
public synchronized void f1(){
//do something here
}
public void f2(){
synchronized(this){
//do something here
}
}
}
上面的f1()和f2()效果一致, synchronized取得的鎖都是Test某個實列(this)的鎖.
比如: Test t = new Test();
執行緒A呼叫t.f2()時, 執行緒B無法進入t.f1(),直到t.f2()結束.
作用: 多執行緒中訪問Test的同一個例項的同步方法時會進行同步.
4. class的鎖
class Test{
final static Object o= new Object();
public static synchronized void f1(){
//do something here
}
public static void f2(){
synchronized(Test.class){
//do something here
}
}
public static void f3(){
try {
synchronized (Class.forName("Test")) {
//do something here
}
}
catch (ClassNotFoundException ex) {
}
}
public static void g(){
synchronized(o){
//do something here
}
}
}
上面f1(),f2(),f3(),g()效果一致
f1(),f2(),f3()中synchronized取得的鎖都是Test.class的鎖.
g()是自己產生一個物件o,利用o的鎖做同步
作用: 多執行緒中訪問此類或此類任一個例項的同步方法時都會同步. singleton模式lazily initializing屬於此類.
5. static method
class Test{
private static int v = 0;
public static void f1(){
//do something, 但函式中沒用用到v
}
public synchronized static void f2(){
//do something, 函式中對v進行了讀/寫.
}
}
多執行緒中使用Test的某個實列時,
(1) f1()是執行緒安全的,不需要同步
(2) f2()這個靜態方法中使用了函式外靜態變數,所以需要同步.
Java非同步:
一. 它要能適應不同型別的請求:
本節用 makeString來說明要求有返回值的請求.用displayString來說明不需要返回值的請求.
二. 要能同時併發處理多個請求,並能按一定機制排程:
本節將用一個佇列來存放請求,所以只能按FIFO機制排程,你可以改用LinkedList,就可以簡單實現一個優先順序(優先順序高的addFirst,低的addLast).
三. 有能力將呼叫的邊界從執行緒擴充套件到機器間(RMI)
四. 分離過度耦合,如分離呼叫控制代碼(取貨憑證)和真實資料的實現.分離呼叫和執行的過程,可以儘快地將調返回.
現在看具體的實現:
public interface Axman {
Result resultTest(int count,char c);
void noResultTest(String str);
}
這個介面有兩個方法要實現,就是有返回值的呼叫resultTest和不需要返回值的呼叫
noResultTest, 我們把這個介面用一個代理類來實現,目的是將方法呼叫轉化為物件,這樣就可以將多個請求(多個方法調)放到一個容器中快取起來,然後統一處理,因為 Java不支援方法指標,所以把方法呼叫轉換為物件,然後在這個物件上統一執行它們的方法,不僅可以做到非同步處理,而且可以將代表方法呼叫的請求物件序列化後通過網路傳遞到另一個機器上執行(RMI).這也是Java回撥機制最有力的實現.
一個簡單的例子.
如果 1: 做A
如果 2: 做B
如果 3: 做C
如果有1000個情況,你不至於用1000個case吧?以後再增加呢?
所以如果C/C++程式設計師,會這樣實現: (c和c++定義結構不同)
type define struct MyStruct{
int mark;
(*fn) ();
} MyList;
然後你可以宣告這個結構資料:
{1,A,
2,B
3,C
}
做一個迴圈:
for(i=0;i<length;i++) {
if(資料組[i].mark == 傳入的值) (資料組[i].*fn)();
}
簡單說c/c++中將要被呼叫的涵數可以被儲存起來,然後去訪問,呼叫,而Java中,我們無法將一個方法儲存,除了直接呼叫,所以將要呼叫的方法用子類來實現,然後把這些子類例項儲存起來,然後在這些子類的實現上呼叫方法:
interface My{
void test();
}
class A implements My{
public void test(){
System.out.println("A"):
}
}
class B implements My{
public void test(){
System.out.println("B"):
}
}
class C implements My{
public void test(){
System.out.println("C"):
}
}
class MyStruct {
int mark;
My m;
public MyStruct(int mark,My m){this.mark = amrk;this.m = m}
}
陣列:
{ new MyStruct(1,new A()),new MyStruct(2,new B()),new MyStruct(3,new C())}
for(xxxxxxxxx) if(引數 ==陣列[i].mark) 陣列[i].m.test();
這樣把要呼叫的方法轉換為物件的保程不僅僅是可以對要呼叫的方法進行排程,而且可以把物件序列化後在另一臺機器上執行,這樣就把呼叫邊界從執行緒擴充套件到了機器.
回到我們的例子:
class Proxy implements Axman{
private final Scheduler scheduler;
private final Servant servant;
public Proxy(Scheduler scheduler,Servant servant){
this.scheduler = scheduler;
this.servant = servant;
}
public Result resultTest(int count,char c){
FutureResult futrue = new FutureResult();
this.scheduler.invoke(new ResultRequest(servant,futrue,count,c));
return futrue;
}
public void noResultTest(String str){
this.scheduler.invoke(new NoResultRequest(this.servant,str));
}
}
其中scheduler是管理對呼叫的排程, servant是真正的對方法的執行:
Servant就是去真實地實現方法:
class Servant implements Axman{
public Result resultTest(int count,char c){
char[] buf = new char[count];
for(int i = 0;i < count;i++){
buf[i] = c;
try{
Thread.sleep(100);
}catch(Throwable t){}
}
return new RealResult(new String(buf));
}
public void noResultTest(String str){
try{
System.out.println("displayString :" + str);
Thread.sleep(10);
}catch(Throwable t){}
}
}
在scheduler 將方法的呼叫(invkoe)和執行(execute)進行了分離,呼叫就是開始"註冊"方法到要執行的容器中,這樣就可以立即返回出來.真正執行多久就是execute的事了,就象一個人點燃爆竹的引信就跑了,至於那個爆竹什麼時候爆炸就不是他能控制的了.
public class Scheduler extends Thread {
private final ActivationQueue queue;
public Scheduler(ActivationQueue queue){
this.queue = queue;
}
public void invoke(MethodRequest request){
this.queue.putRequest(request);
}
public void run(){
while(true){
//如果佇列中有請求執行緒,測開始執行請求
MethodRequest request = this.queue.takeRequest();
request.execute();
}
}
}
在scheduler中只用一個佇列來儲存代表方法和請求物件,實行簡單的FIFO呼叫,你要實更復雜的排程就要在這裡重新實現:
class ActivationQueue{
private static final int MAX_METHOD_REQUEST = 100;
private final MethodRequest[] requestQueue;
private int tail;
private int head;
private int count;
public ActivationQueue(){
this.requestQueue = new MethodRequest[MAX_METHOD_REQUEST];
this.head = this.count = this.tail = 0;
}
public synchronized void putRequest(MethodRequest request){
while(this.count >= this.requestQueue.length){
try {
this.wait();
}
catch (Throwable t) {}
}
this.requestQueue[this.tail] = request;
tail = (tail + 1)%this.requestQueue.length;
count ++ ;
this.notifyAll();
}
public synchronized MethodRequest takeRequest(){
while(this.count <= 0){
try {
this.wait();
}
catch (Throwable t) {}
}
MethodRequest request = this.requestQueue[this.head];
this.head = (this.head + 1) % this.requestQueue.length;
count --;
this.notifyAll();
return request;
}
}
為了將方法呼叫轉化為物件,我們通過實現MethodRequest物件的execute方法來方法具體方法轉換成具體物件:
abstract class MethodRequest{
protected final Servant servant;
protected final FutureResult future;
protected MethodRequest(Servant servant,FutureResult future){
this.servant = servant;
this.future = future;
}
public abstract void execute();
}
class ResultRequest extends MethodRequest{
private final int count;
private final char c;
public ResultRequest(Servant servant,FutureResult future,int count,char c){
super(servant,future);
this.count = count;
this.c = c;
}
public void execute(){
Result result = servant.resultTest(this.count,this.c);
this.future.setResult(result);
}
}
class NoResultRequest extends MethodRequest{
private String str;
public NoResultRequest(Servant servant,String str){
super(servant,null);
this.str = str;
}
public void execute(){
this.servant.noResultTest(str);
}
}
而返回的資料我們也將真實資料的獲取和取貨憑證邏輯分離:
package com.axman.jasync;
public abstract class Result {
public abstract Object getResult();
}
class FutureResult extends Result{
private Result result;
private boolean completed;
public synchronized void setResult(Result result){
this.result = result;
this.completed = true;
this.notifyAll();
}
public synchronized Object getResult(){
while(!this.completed){
try{
this.wait();
}catch(Throwable t){}
}
return this.result.getResult();
}
}
class RealResult extends Result{
private final Object result;
public RealResult(Object result){
this.result = result;
}
public Object getResult(){
return this.result;
}
}
OK,現在這個非同步訊息處理器已經有了模型,這個非同步處理器中有昭雪些物件參與呢?
Servant 忠心做真實的事務
ActivationQueue將請求快取起來以便排程
Scheduler對容器中的請求根據一定原則進行排程執行
Proxy將特定方法請求轉換為特定物件
所有這些都是這個非同步處理器的核心部件,雖然是核心部件,我們就要進行封裝而不能隨便讓呼叫者來修改,所以我們用工廠模式(我KAO,我實在不想提模式但有時找不到其它詞來表述)來產生處理器Axman物件:
package com.axman.jasync;
public class AxmanFactory {
public static Axman createAxman() {
Servant s = new Servant();
ActivationQueue queue = new ActivationQueue();
Scheduler st = new Scheduler(queue);
Proxy p = new Proxy(st,s);
st.start();
return p;
}
}
好了,我們現在用兩個請求的產生者不停產生請求:
ResultInvokeThreadv 傳送有返回值的請求:
package com.axman.jasync;
public class ResultInvokeThread extends Thread{
private final Axman ao;
private final char c;
public ResultInvokeThread(String name,Axman ao){
this.ao = ao;
this.c = name.charAt(0);
}
public void run(){
try{
int i = 0;
while(true){
Result result = this.ao.resultTest(i++,c);
Thread.sleep(10);
String = (String)result.getResult();
System.out.println(Thread.currentThread().getName() + " = " + );
}
}
catch(Throwable t){}
}
}
NoResultInvokeThread傳送無返回值的請求:
package com.axman.jasync;
public class NoResultInvokeThread extends Thread{
private final Axman ao;
public NoResultInvokeThread(String name,Axman ao){
super(name);
this.ao = ao;
}
public void run(){
try{
int i = 0;
while(true){
String s = Thread.currentThread().getName() + i++;
ao.noResultTest(s);
Thread.sleep(20);
}
}
catch(Throwable t){}
}
}
對了,我們還需要一個什麼東西來產生一個演示:
package com.axman.jasync;
public class Program {
public static void main(String[] args) {
Axman ao = AxmanFactory.createAxman();
new ResultInvokeThread("Axman",ao).start();
new ResultInvokeThread("Sager",ao).start();
new NoResultInvokeThread("Macke",ao).start();
}
}
看看結果吧.你可以把不同型別的請求不斷地向處理器傳送,處理器會不斷地接收請求,放到佇列中,並同時不斷從佇列中提出請求進行處理.