Java多線程(1)
一.實現方法
1.繼承Thread類,重寫run方法
實例代碼:
publicclass MyFirstThread extends Thread {
publicinti;
@Override
publicvoid run() {
while (i++ < 100) {
System.out.println(this.getName() + ":i=" + i);
}
}
publicstaticvoid main(String[] args) {
MyFirstThread thread1 = new MyFirstThread();
MyFirstThread thread2 = new MyFirstThread();
thread1.start();
thread2.start();
}
}
2.實現Runnable接口,傳給Thread實現方法。
實現實例
public class ThreadRunnableTest implements Runnable{
@Override
public void run() {
while (true) {
System.out.println("線程執行。。。。。。。");
}
}
public static void main(String[] args) {
new Thread(new ThreadRunnableTest()).start();
System.out.println("主線程執行。。。。。。");
}
}
二、線程的生命週期
新建,就緒,運行,阻塞和死亡狀態
·新建:線程被new出來就是新建狀態
·就緒:調用start方法,則進入就緒狀態,等待CPU的調度
·運行:得到CPU的時間片(可以理解爲時間片輪轉)則進入運行狀態
·阻塞:運行狀態中可能會被CPU調度下來(比如被掛起,調用sleep方法,調用一個阻塞方法比如getchar()之類的方法),進入阻塞狀態
·死亡:線程執行完畢或者因爲異常或者調用stop方法之後線程結束則進入死亡狀態。
·ALive狀態:是否死亡,是就緒、運行和阻塞三種狀態的合集。
·線程進入阻塞狀態的情況:
n 線程調用一個阻塞方法,方法返回前該線程一直阻塞。
n 線程調用sleep方法進入阻塞。
n 線程嘗試獲取同步監視器(常說的線程鎖),但該同步監視器被其他線程持有。
線程解除阻塞,重新進入就緒狀態的情況:
n 調用的阻塞方法返回。
n 調用的sleep到期。
n 線程成功獲取同步監視器(我們常說的線程鎖)。
線程的死亡狀態就是線程的結束,線程結束的情況有如下幾種:
n run方法執行完成
n 線程拋出異常
n 直接調用線程的stop方法結束線程(該方法已經過時,並且無代替方法,不建議使用)
判斷線程是否死亡可以使用isAlive方法,當線程處於就緒、運行和阻塞三種狀態的時候返回true,否則返回false。另外,不能對已經死亡的線程重新調用start方法重新啓動。
另外,線程的suspend方法和stop方法非常容易導致死鎖,一般不推薦使用。
三.線程控制
join線程
當一個線程需要等待另一個線程完畢再執行的話,可以使用Thread的join方法。
假設線程A和線程B,在A執行時調用了B的join方法(前提是B已經進入Alive狀態),A將被阻塞,一直等到B線程執行完畢後,A線程繼續執行,就好像排隊加塞。
示例代碼:
publicclass TestJoinThread {
publicstaticvoid main(String[] args) {
MainThread mt = new MainThread();
mt.start();
}
}
class MainThread extends Thread {
publicvoid run() {
// TODO Auto-generated method stub
for (int i = 0; i < 50; i++) {
if (i == 30) {
JoinThread jt = new JoinThread();
jt.setName("join線程");
jt.start();
try {
jt.join();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
System.out.println(this.getName() + " " + i);
}
}
}
class JoinThread extends Thread {
@Override
publicvoid run() {
// TODO Auto-generated method stub
for (int i = 0; i < 50; i++) {
System.out.println(this.getName() + " " + i);
}
}
}
運行結果:
…
Thread-0 27
Thread-0 28
Thread-0 29
join線程 0
join線程 1
join線程 2
…
join線程 46
join線程 47
join線程 48
join線程 49
Thread-0 30
Thread-0 31
Thread-0 32
Thread-0 33
…
Thread-0 48
Thread-0 49
可以看到Main線程正常運行,調用了另外一個線程JoinThread的join方法,那麼Main線程等待JoinThread執行完畢後再執行。
線程睡眠
如果讓線程休息一會,我們可以使用Thread的靜態方法sleep(long millis)。這個方法的意思是:在指定的毫秒數內讓當前正在執行的線程休眠(暫停執行),此操作受到系統計時器和調度程序精度和準確性的影響。
sleep方法會讓線程休息指定的毫秒數,在sleep的時候,不會放棄執行權力,等待休息時間到了,馬上獲取執行機會。也就是說,在這個線程sleep的時候,其他線程不會獲得CPU的執行權力。
但如果睡眠過程中會持有鎖的話,最好睡眠之前把鎖放棄(這是後話了)。
示例代碼:
publicclass TestThreadSleep {
publicstaticvoid main(String[] args) {
for (int i = 0; i < 50; i++) {
System.out.println(i);
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
程序將每隔一秒打印一下變量i的值。
·線程讓步和線程優先級使用頻率很低,一方面因爲其隨機性對線程的控制弱,另一方面現在多核CPU的原因,所以不在總結。
線程安全
線程安全的概念不容易定義,在《Java 併發編程實踐》中,作者做出瞭如此定義:多個線程訪問一個類對象時,如果不用考慮這些線程在運行時環境下的調度和交替執行,並且不需要額外的同步及在調用方法代碼不必作其他的協調,這個類的行爲仍然是正確的,那麼這個類是線程安全的。
也就是說一堆線程去操作一個方法去控制同一個資源,由於是交替執行的,可能會出現一個數據一個線程正在運算還沒來得急把數據寫進去,結果被另外一個線程把這個數據的髒數據讀取出去了。
這樣說,可能有些朋友沒有看明白,那麼我們先來看一個示例,來演示一下什麼是現成不安全的情況。
publicclass SafeThreadTest {
V v = new V();
publicstaticvoid main(String[] args) {
SafeThreadTest test = new SafeThreadTest();
test.test();
}
/**
* 開兩個線程,分別調用V對象的打印字符串的方法
*/
publicvoid test(){
new Thread(new Runnable() {
@Override
publicvoid run() {
while(true){
v.printString("AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA");
}
}
}).start();
new Thread(new Runnable() {
@Override
publicvoid run() {
while(true){
v.printString("BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB");
}
}
}).start();
}
/**
* 這個類負責打印字符串
* @author Administrator
*/
class V {
//創建一個鎖對象
Lock lock = new ReentrantLock();
/**
* 爲了能使方法運行速度減慢,我們一個字符一個字符的打印
* @param s
*/
publicvoid printString(String s){
//加鎖,只允許一個線程訪問
lock.lock();
try {
for(int i = 0;i<s.length();i++){
System.out.print(s.charAt(i));
}
System.out.println();
}finally{
//解鎖,值得注意的是,這裏鎖的釋放放到了finally代碼塊中,保證解鎖工作一定會執行
lock.unlock();
}
}
}
}
使用這樣的方式,與使用synchronized的功能一樣,只不過這樣使代碼看起來更加面向對象一些,怎麼加鎖,怎麼解鎖一目瞭然。
另外,ReentrantLock其實比synchronized增加了一些功能,主要有:
等待可中斷
這是指的當前持有鎖的線程如果長期不釋放鎖,正在等待的線程可以放棄等待,處理其他事情。
公平鎖
這個是說多個線程在等待同一個鎖的時候,必須按照申請鎖的時間順序依次獲得鎖。synchronized中的鎖是不公平鎖,鎖被釋放的時候任何一個等待鎖的線程都有機會獲得鎖,ReentrantLock默認也是不公平鎖,可以使用構造函數使得其爲公平鎖。如果爲true代表公平鎖Lock lock = new ReentrantLock(true);
綁定條件
這個就是的條件鎖。
在性能上,在jdk1.6之前的版本,使用ReentrantLock的性能要好於使用synchronized。而在jdk1.6開始,兩者性能均差不多。