最近做注冊的時候,發現同步發送注冊郵件多了一個耗時,就想到異步處理郵件發送,直接返回成功給用戶。
設計了一個線程,用來發送郵件,需要發送的時候再來喚醒就好了,但是對於沒有系統了解過多線程的我來說,想的太簡單了。
public class MailSendThread extends Thread{ private static Logger log = Logger.getLogger(MailSendThread.class); public final static long mail_user_time = 48 * 1800000L;//一天運行一次 public void run(){ log.error("MailSendThread is running!"); try { MailUtil.sendMailInfo(); sleep(mail_user_time); } catch (Exception e) { // TODO Auto-generated catch block log.error("MailSendThread run error", e); } } }
private static MailSendThread mailSender = new MailSendThread(); public static void notifyMailSender(){ mailSender.notify(); }
多傻的代碼!!!!
仔細研究后發現,首先sleep只能用作線程內部等待使用,指定時間段內休眠,不能外部喚醒;
其次,nofity方法必須依托與一個線程正在等待的對象,就是鎖住的對象,不能直接對線程操作,因為wait函數需要一個鎖;
研究了一下生產者和消費者,這里用到的鎖是對象實例
package com.thread; /** * 生產者,制造饅頭 * @author huangjc * */ public class A extends Thread{ private C c; public A(C c){ this.c = c; } public void run(){ int i=0; while(i < 10){ try { c.add(); i++; } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } } } }
package com.thread; /** * 消費者,消耗饅頭 * @author huangjc * */ public class B extends Thread{ private C c; public B(C c){ this.c = c; } public void run(){ int i=0; while(i<10){ i++; c.minus(); } } public static void main(String[] args) { C c = new C(); new Thread(new B(c)).start(); new Thread(new A(c)).start(); } }
具體的鎖就在下面
package com.thread; public class C { private static int i=0; public synchronized void add() throws InterruptedException{ System.out.println("增加饅頭:現在有"+(i)+"個饅頭"); if(i == 5){ System.out.println("饅頭夠多了,趕快吃吧!"); wait(); }else{ i++; System.out.println("放了一個饅頭"); notify();//喚醒消耗線程 } } public synchronized void minus(){ System.out.println("取出饅頭:現在有"+(i)+"個饅頭"); if (i == 0){ System.out.println("饅頭沒有了,等會兒吧!"); try { wait(); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } }else{ i--; System.out.println("取出一個饅頭"); notify(); } } }
這里的notify和wait方法鎖住的都是在B的main方法中創建的實例對象
所以問題又來了,我需要在主線程中啟動分線程,所以不能給兩個線程分配同一個實例對象,如果每次都重新分配一個實例對象,再創建一個分線程,是不是太愚蠢了;
對於靜態方法和非靜態方法的同步問題,靜態方法鎖住的是Class,例如A.class,而非靜態方法鎖住的是當前實例。
所以靜態方法和非靜態方法並不適用同一個鎖。一個類中的所有靜態方法使用同一個鎖。
所以,是否可以考慮使用靜態方法來發送郵件,鎖住類本身,然后再主線程中依托類本身進行喚醒;
太坑爹了,完全不是這回事,查了API后發現,wait方法是Object對象的,那不就是說必須依托於一個對象實例嗎?
再來看api中關於thread類下面的方法,其中有個
interrupt()
interrupt public void interrupt() 中斷線程。 如果當前線程沒有中斷它自己(這在任何情況下都是允許的),則該線程的 checkAccess 方法就會被調用,這可能拋出 SecurityException。 如果線程在調用 Object 類的 wait()、wait(long) 或 wait(long, int) 方法,或者該類的 join()、join(long)、join(long, int)、sleep(long) 或 sleep(long, int) 方法過程中受阻,則其中斷狀態將被清除,它還將收到一個 InterruptedException。 如果該線程在可中斷的通道上的 I/O 操作中受阻,則該通道將被關閉,該線程的中斷狀態將被設置並且該線程將收到一個 ClosedByInterruptException。 如果該線程在一個 Selector 中受阻,則該線程的中斷狀態將被設置,它將立即從選擇操作返回,並可能帶有一個非零值,就好像調用了選擇器的 wakeup 方法一樣。 如果以前的條件都沒有保存,則該線程的中斷狀態將被設置。 中斷一個不處於活動狀態的線程不需要任何作用。 拋出: SecurityException - 如果當前線程無法修改該線程
線程A正在使用sleep()暫停着: Thread.sleep(100000);
如果要取消他的等待狀態,可以在正在執行的線程里(比如這里是B)調用
a.interrupt();
令線程A放棄睡眠操作,這里a是線程A對應到的Thread實例
執行interrupt()時,並不需要獲取Thread實例的鎖定.任何線程在任何時刻,都可以調用其他線程interrupt().當sleep中的線程被調用interrupt()時,就會放棄暫停的狀態.並拋出InterruptedException.丟出異常的,是A線程
package com.thread; /** * 消費者,消耗饅頭 * @author huangjc * */ public class B extends Thread{ public void run(){ while(true){ System.out.println("線程執行"); try { System.out.println("睡一會兒"); sleep(180000); } catch (InterruptedException e) { System.out.println("cao ,誰在打擾我睡覺呢!"); } } } public static void main(String[] args) { B b = new B() ; b.start(); int i=0; while(i < 100000000){ i++; } System.out.println("別睡了"); b.interrupt(); } }
執行main函數
線程執行
睡一會兒
別睡了
cao ,誰在打擾我睡覺呢!
線程執行
睡一會兒