JAVA多線程學習筆記

前陣子,重新複習並學習了有關多線程的知識,下面是我的一些學習筆記。每一點,只爲儘量簡明的表達出知識內容,不會全部具體展開講,並附上相關代碼。(其實,我現在也還做不到全部展開來講啦,啊哈哈),日後這個筆記會不斷補充更新。

1.能中斷的線程


public class Test1 {
    public static void main(String[] args) throws Exception {
        Thread thread = new Thread() {
            public void run() {
                while (!this.isInterrupted()) {
                    try {
                        Thread.sleep(10);
                    } catch (InterruptedException e) {
                        System.out.println("如果在睡眠(sleep)狀態或者等待(wait)下被中斷的話,就會進入到這裏");
                        return;
                    }
                    System.out.println(1111);
                }
            };
        };
        thread.start();
        Thread.sleep(100);
        thread.interrupt();
    }
}

tips:1.上面實際上,展示了兩種中斷線程的方法,一種是通過while循環,不斷的判斷this.isInterrupted()狀態。不過這種判斷方法,有一個缺點,就是萬一循環裏面的操作十分耗時,如果我們想在那操作完成前,就中斷線程的話,其實是不可以的。2.利用異常的方法,中斷線程也是可以的,但是缺點跟上面一樣,我們不能依靠這種方法,中斷一個正在進行嚴重耗時操作的線程,因爲要麼已經跳過了sleep或者還沒到達sleep(wait應用場景就不一樣了,這裏不展開討論),所以是進不了catch的。

所以,我們可以得到一個結論:interrupt()並不直接中斷線程,而是設定一箇中斷標識,然後由程序進行中斷檢查,確定是否中斷。


2.線程可以利用suspend方法(暫停),和resume方法(恢復),對線程進行暫停和恢復。需要注意的是,suspend()方法是不釋放鎖的,也就是說如果某個線程在同步代碼塊中暫停了,那其他線程,就無法再進入,利用這個同步(監聽)對象的同步代碼塊裏面了。而且這兩個方法都是過時被廢棄的方法。


3.synchronized鎖重入

在使用synchronized時,當一個線程得到一個對象鎖之後,再次請求此對象鎖時,是可以再一次得到這個對象的鎖的。(本來就得到了嘛= =)這也證明了在一個synchronized方法或者synchronized代碼塊內部,調用依賴於這個對象鎖的synchronized方法/塊時,是永遠可以得到鎖的。可重入鎖也支持在父子類繼承的環境中。即:

public synchronized void aa() {
		// 某些操作
		// 當一個方法進行到這裏之後,因爲bb方法也需要獲得鎖,而此時當前線程已經獲得了這個當前對象的對象鎖,所以bb方法的鎖是可以馬上獲得的。即不會被其他的線程先獲得鎖,然後先執行了bb方法這樣。
		bb();
		// 某些操作
	}

	public synchronized void bb() {
		// 某些操作
	}

4.Future.cancel()更安全,條理,高效的中斷線程


public boolean cancel(boolean mayInterruptIfRunning)
從接口 Future 複製的描述
試圖取消對此任務的執行。如果任務已完成、或已取消,或者由於某些其他原因而無法取消,則此嘗試將失敗。當調用 cancel 時,如果調用成功,而此任務尚未啓動,則此任務將永不運行。如果任務已經啓動,則 mayInterruptIfRunning 參數確定是否應該以試圖停止任務的方式來中斷執行此任務的線程。

此方法返回後,對 Future.isDone() 的後續調用將始終返回 true。如果此方法返回 true,則對 Future.isCancelled() 的後續調用將始終返回 true

指定者:
接口 Future<V> 中的 cancel
參數:
mayInterruptIfRunning - 如果應該中斷執行此任務的線程,則爲 true;否則允許正在運行的任務運行完成
返回:
如果無法取消任務,則返回 false,這通常是由於它已經正常完成;否則返回 true


5.當一個線程執行的代碼出現異常時,其所持有的鎖會自動釋放


6.同步不具有繼承性,即父類的某個方法是有synchronized關鍵字的,但是子類繼承父類時覆蓋了這個方法後,把synchronized關鍵字去掉了的話,這個子類的這個方法就不再是同步的方法了。


7.synchronized關鍵字加到static靜態方法上是給Class類上鎖,而synchronized關鍵字加到非static靜態方法上,是給對象加鎖。而Class鎖可以對類的所有對象實例起作用。


8.關鍵字volatile的作用,就是強制從公共堆棧中取得變量的值,而不是從線程私有數據棧中取得變量的值。但是,它只能保證可見性,並不能保持原子性,就是它是非線程安全的。


9.關鍵字synchronized可以使多個線程訪問同一個資源具有與同步性,而且它還具有將線程工作內存中的私有變量與公共內存中變量同步的功能。

public class Test2 {

	public static void main(String[] args) {
		final A a = new A();

		new Thread() {
			public void run() {
				a.startMethod();
			};
		}.start();

		try {
			// 模擬一些耗時操作等,確保上面的線程已經執行了startMethod
			Thread.sleep(10);
		} catch (InterruptedException e) {
			e.printStackTrace();
		}

		new Thread() {
			public void run() {
				a.stopMethod();
			};
		}.start();
	}
}

class A {
	// private volatile boolean isStop = false;如果使用volatile關鍵字的話,就不需要同步代碼塊了
	private boolean isStop = false;

	public void startMethod() {
		while (!isStop) {
			// 如果變量不用volatile修飾,這裏又不加這個同步代碼塊的話,當A線程調用這個類的一個實例的startMethod後,B線程調用再調用這個實例的stopMethod方法的話,while循環還是不會停止的
			// synchronized (this) {
			//
			// }
		}
		System.out.println("停下來了");
	}

	public void stopMethod() {
		System.out.println("結束了嗎?");
		isStop = true;
	}
}


10.在調用wait之前,線程必須獲得該對象的對象級別鎖,即只能在同步方法或同步塊中調用wait方法。在執行wait方法之後,當前線程釋放鎖


11.在執行notify方法之後,當前線程不會馬上釋放該對象鎖,要等到退出synchronized代碼塊範圍之後,當前線程纔會釋放鎖。當已經wait的方法,重新獲得鎖執行下面的代碼時,也是如此。也是要執行到退出synchronized代碼塊範圍之後再釋放鎖。


12.ThreadLocal主要解決的就是每個線程綁定自己的值,可以將ThreadLocal類比喻成全局存放數據的盒子,盒子中可以存儲每個線程的私有數據。即即使將它弄成static(static ThreadLocal<Integer> local = new ThreadLocal<Integer>();),並且讓每一個線程都往裏面添加數據,最終取出來的數據,都是自己線程的數據,不會被其他線程所幹擾:並且還可以複寫裏面的initialValue方法,這樣第一次get的時候,就不會得到空值。


13.創建一個Timer就是啓動一個新的線程,這個新啓動的線程並不是一個守護線程(守護線程的意思就是,如果當前沒有非守護線程,只剩下守護線程時,守護線程會自動結束),它會一直在運行。new Timer(true);這樣可以創建一個守護線程的Timer 。



14.TimerTask類中的cancel方法的作用是將自身從任務隊列中清除。Timer類中的cancel方法的作用是將任務隊列中的全部任務清空。但是,需要注意的是Timer類中的cancel方法有時不一定會停止執行計劃任務,而是正常執行。這是因爲Timer類中cancel方法有時並沒有爭搶到queue鎖,所以TimerTask類中的任務繼續正常執行。


15.ThreadGroup線程組,既可以添加線程也可以添加線程組,方便統一管理裏面的線程。


16.ReentrantLock:一個可重入的互斥鎖 Lock,它具有與使用 synchronized 方法和語句所訪問的隱式監視器鎖相同的一些基本行爲和語義,但功能更強大。

private ReentrantLock lock = new ReentrantLock();
    public void aa(String s) {
        lock.lock();
        try {
            System.out.println("進來了");
            Thread.sleep(1000);
            System.out.println(s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }


17.使用Condition實現等待/通知(代替notify和wait)

public class Test {
    public static void main(String args[]) throws Exception {
        new Thread() {
            @Override
            public void run() {
                await("aaa");
            }
        }.start();

        new Thread() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                signal("bbb");
            }
        }.start();
    }

    private static ReentrantLock lock = new ReentrantLock();
    private static Condition condition = lock.newCondition();

    public static void await(String s) {
        lock.lock();
        try {
            System.out.println("準備等待");
            condition.await();
            System.out.println(s);
        } catch (InterruptedException e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

    public static void signal(String s) {
        lock.lock();
        try {
            System.out.println("準備喚醒");
            condition.signal();
            System.out.println(s);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            lock.unlock();
        }
    }

}


tip:
輸出結果爲:準備等待
準備喚醒
bbb
aaa
很明顯,也是跟notify一樣,馬上是執行了喚醒,等待的線程也不能馬上獲得鎖,必須等到退出同步代碼塊之後,這裏就是必須等到調用lock.unlock();方法之後,等待的線程才能重新獲得鎖並重新繼續運行下去。而且,很明顯的是,如果你只想單獨喚醒某些線程時,你只需要不斷的使用lock.newCondition(),獲取更多的Condition即可。

18.ReentrantLock 裏面的tryLock()方法的作用是:僅在調用時鎖定未被另一個線程保持的情況下,才獲取該鎖。即其中一個線程獲得鎖後,會執行if爲true裏面的方法,沒有獲得鎖的線程,會執行false裏面的方法,並且不會一直阻塞等待獲取鎖.
  if (lock.tryLock()) {
            System.out.println(s + "拿到鎖了");
        } else {
            System.out.println(s + "沒有拿到鎖");
        }


19.ReentrantLock類具有完全排他的效果,效率比較低。ReentrantReadWriteLock讀寫鎖效率更高。它一共包含兩種鎖,一個是讀操作的相關鎖,稱爲共享鎖readWriteLock.readLock().lock();另一個是寫操作相關的鎖,稱爲排它鎖readWriteLock.writeLock().lock();需要注意的是,只有是共享鎖時,多個線程才能同時進入。如果遇到的是,排它鎖時,跟之前的效果是一樣的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章