背景:併發知識是一個程式員段位升級的體現,同樣也是進入BAT的必經之路,有必要把併發知識重新梳理一遍。 說到併發concurrent,肯定首先想到了線程,創建線程有兩種方法:1、從Java.lang.Thread類派生一個新的線程類,重載它的run()方法;2、實現Runnalbe介面,重載Runn ...
背景:併發知識是一個程式員段位升級的體現,同樣也是進入BAT的必經之路,有必要把併發知識重新梳理一遍。
併發concurrent:
說到併發concurrent,肯定首先想到了線程,創建線程有兩種方法:1、從Java.lang.Thread類派生一個新的線程類,重載它的run()方法;2、實現Runnalbe介面,重載Runnalbe介面中的run()方法;建議使用方法二創建線程,因為,如果是通過擴展 Thread類的方法來創建線程,那麼這個自定義類就不能再去擴展其他的類,也就無法實現更加複雜的功能;而實現Runnable介面的方法來定義該類為線程類,這樣就可以避免Java單繼承所帶來的局限性,也更符合面向對象編程的思想,最重要的就是使用實現Runnable介面的方式創建的線程可以處理同一資源,從而實現資源的共用。
創建線程的兩種方法:
1 package www.concurent.test;
2 public class TraditionalThread {
3
4 public static void main(String[] args) {
5 //Thread1:
6 Thread thread = new Thread() {
7 @Override
8 public void run() {
9 while(true) {
10 try {
11 Thread.sleep(1000);
12 } catch (InterruptedException e) {
13 e.printStackTrace();
14 }
15 System.out.println("thread1: "+Thread.currentThread().getName());
16 }
17 }
18 };
19 thread.start();
20
21 //Thread2:
22 //Runnable變數是線程要運行的代碼的宿主,更適合面向對象思想的線程方法
23 Thread thread2 = new Thread(new Runnable() {
24 @Override
25 public void run() {
26 while(true) {
27 try {
28 Thread.sleep(1000);
29 } catch (InterruptedException e) {
30 e.printStackTrace();
31 }
32 System.out.println("thread2: "+Thread.currentThread().getName());
33 }
34 }
35 });
36 thread2.start();
37 }
38 }
線程和Timer定時器很類似,下麵介紹了兩種和線程相似的定時器寫法:1、定時一天之後調用方法查詢天氣情況介面,然後每隔60秒後繼續調用該方法;2、定時每天00:39:32調用查詢天氣情況介面,通過Hutool工具和Timer定時器調用HTTP天氣狀況介面的返回結果如下截圖:(result2得到了天津的天氣狀況)
通過Hutool工具和Timer定時器調用HTTP天氣狀況介面:
1 import java.util.Calendar;
2 import java.util.Date;
3 import java.util.Timer;
4 import java.util.TimerTask;
5 import cn.hutool.http.HttpUtil;
6
7 public class TraditionalTimerTest {
8 //時間間隔
9 private static final long PERIOD_DAY = 24 * 60 * 60 * 1000;
10 //Timer 定時器
11 public static void main(String[] args) {
12 Calendar cl = Calendar.getInstance();
13 cl.set(Calendar.HOUR_OF_DAY, 0);
14 cl.set(Calendar.MINUTE, 39);
15 cl.set(Calendar.SECOND, 32);
16 Date date = cl.getTime();
17 Date dateNow = new Date();
18 //如果第一次執行定時任務的時間 小於 當前的時間
19 //此時要在 第一次執行定時任務的時間 加一天,以便此任務在下個時間點執行。如果不加一天,任務會立即執行。
20 if (date.before(dateNow)) {
21 Calendar clAdd = Calendar.getInstance();
22 clAdd.setTime(dateNow);
23 clAdd.add(Calendar.DAY_OF_MONTH, 1);
24 date = clAdd.getTime();
25 }
26 //Timer1:
27 new Timer().schedule(new TimerTask() {
28 @Override
29 public void run() {
30 System.out.println("hello");
31 //Hutool調用http介面
32 String result1 = HttpUtil.get("http://t.weather.sojson.com/api/weather/city/101030100");
33 System.out.println("result1: "+result1);
34 }
35 //一天之後調用方法查詢天氣情況介面,然後每隔60秒後繼續調用該方法
36 },PERIOD_DAY , 1000*60);
37 //Timer2:
38 new Timer().schedule(new TimerTask() {
39 @Override
40 public void run() {
41 //Hutool調用http介面
42 String result2 = HttpUtil.get("http://t.weather.sojson.com/api/weather/city/101030100");
43 System.out.println("result2: " + result2);
44 }
45 //定時每天00:39:32調用查詢天氣情況介面
46 }, date , PERIOD_DAY);
47 }
48
49 }
如果是單個線程調用都還ok,要是有多個線程同時調用那就會出現併發產生;比如有一個方法Output()是經過charAt(i)獲取字元串i的字元並且列印再控制台,然後線程A和線程B同時調用Output()方法,此時就會出現線程不安全問題(如銀行取錢和轉賬同時進行),也就是併發;執行結果發現,線程A為執行完畢線程B就開始執行了,為了能後保證當有一個線程來執行某個方法時,其他的線程不能進來執行該方法,實現排他性,可以通過synchronized和ReentrantLock來實現線程同步;二者其實區別不大,synchronized由於是底層JVM實現的互斥,因此效率會高一些,而ReentrantLock的功能則比synchronized更多,比如定時獲取某個鎖,多個等待條件等,另外synchronized 會讓線程阻塞,ReentrantLock會讓線程等待,但是從行為效果上來看是一樣的;下麵有個例子:併發結果如截圖顯示,理想狀態是列印“huawei”或者“isoftstone”,但是由於併發列印出來諸如此類“ishuaweoftstoni”結果。
FYI:
1 import java.util.concurrent.locks.Lock;
2 import java.util.concurrent.locks.ReentrantLock;
3
4 public class MyThreadSynchronized {
5 public static void main(String[] args) {
6 //要想調用內部類的對象,必須有外部類的實例對象
7 new MyThreadSynchronized().init(); // 外部類的實例對象
8 }
9 public void init() {
10 final Outputer outputer = new Outputer();
11 //thread1:
12 new Thread(new Runnable() {
13 @Override
14 public void run() {
15 while(!false) {
16 try {
17 Thread.sleep(100);
18 } catch (InterruptedException e) {
19 e.printStackTrace();
20 }
21 outputer.output3("huawei");
22 }
23 }
24 }).start();
25
26 //thread2:
27 new Thread(new Runnable() {
28 @Override
29 public void run() {
30 while(!false) {
31 try {
32 Thread.sleep(100);
33 } catch (InterruptedException e) {
34 e.printStackTrace();
35 }
36 outputer.output("isoftstone");
37 }
38 }
39 }).start();
40 }
41 //當有一個線程來執行某個方法時,其他的線程不能進來執行該方法,排他性、獨一無二;
42 //使用synchronized/lock同步,且線程用的同步鎖是同一個同步對象,可用this互斥或方法.class
43 //synchronized由於是底層JVM實現的互斥,因此效率會高一些
44 //ReentrantLock的功能則比synchronized更多,比如定時獲取某個鎖,多個等待條件
45 //synchronized 會讓線程阻塞,ReentrantLock會讓線程等待,但是從行為效果上來看是一樣的;
46 class Outputer{
47 //內部類 靜態方法中不能new內部類的實例對象
48 //synchronized:
49 public synchronized void output(String name) {
50 for(int i = 0; i<name.length(); i++) {
51 System.out.print(name.charAt(i));
52 }
53 System.out.println();// switch line
54 }
55
56 //線程不安全
57 public void output3(String name) {
58 for(int i = 0; i<name.length(); i++) {
59 System.out.print(name.charAt(i));
60 }
61 System.out.println();// switch line
62 }
63
64 //lock:
65 public void ouputlock(String name) {
66 Lock lock = new ReentrantLock();
67 lock.lock(); // 上鎖同步
68 try {
69 for (int i = 0; i < name.length(); i++) {
70 System.out.print(name.charAt(i));
71 }
72 System.out.println();// switch line
73 } finally {
74 lock.unlock(); // 解鎖
75 }
76 }
77
78 }
79
80 }