【Java多線程編程核心技術】第一章(多線程技能 currentThread, isAlive,sleep,getId)

Thread類的核心方法較多,讀者應該着重掌握如下關鍵技術點:

□線程的啓動

□如果使線程暫停

□如何使線程停止

□線程的優先級

□線程安全相關的問題

1.1 進程與多線程的概念及線程的優點

     本節主要介紹在Java語言中使用多線程技術。但是講到多線程不得不提到進程這個概念:

進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,
是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。在早
期面向進程設計的計算機結構中,進程是程序的基本執行實體;在當代
面向線程設計的計算機結構中,進程是線程的容器。程序是指令、數據
及其組織形式的描述,進程是程序的實體。

    將一個正在操作系統中運行的exe程序理解成一個進程!通過查看任務管理器中的列表,完全可以將運行在內存中的exe文件理解成進程,進程是受操作系統管理的基本運行單元。

   那什麼是線程呢?線程可以理解成是進程中獨立運行的子任務。比如,QQ.exe運行時就有很多的子任務在同時運行。再如,好友視頻線程,下載文件線程,傳輸數據線程等等,這些不同的任務或者說功能都可以同時運行,其中每一項任務完全可以理解成是線程在工作。使用多任務操作系統後,可以最大限度地利用CPU的空閒時間來處理其它的任務,比如一邊打印,一邊使用word編輯。而CPU在這些任務之間不停地切換,由於切換速度非常快,給使用者的感覺就是這些任務似乎在同時運行。所以在使用多線程技術後,可以在同一時間內運行更多不同種類的任務。

1.2 使用多線程

  一個進程正在運行時至少會有一個線程在運行,這種情況在java中也是存在的。這些線程在後臺默默地執行:

	public static void main(String[] args) {
		System.out.println(Thread.currentThread().getName());// main
	}

  在控制檯輸出的main其實就是一個名稱叫做main的線程在執行main方法中的代碼。另外需要說明一下,在控制檯輸出的main和main方法沒有任何的關係,僅僅是名字相同而已

1.2.1 繼承Thread類

         實現多線程編程的方式有兩種,一種是繼承Thread類,另一種是實現Runnable接口。先來看看Thread類的結構:

public class Thread implements Runnable

        從源代碼可以發現,Thread類實現了Runnable接口,它們之間具有多態關係。其實,使用繼承Thread類的方式創建線程時,最大的侷限就是不支持多繼承,因爲Java語言的特點就是單根繼承,所以爲了支持多繼承,完全可以實現Runnable接口的方式,一邊實現一邊繼承。

public class MyThread extends Thread {
	@Override
	public void run() {
		super.run();
		System.out.println("My Thread!");
	}	
	public static void main(String[] args) {
		MyThread my = new MyThread();
		my.start();
		System.out.println("運行結束");// 運行結束/n My Thread!
	}
}

        以上代碼的打印順序可以看出來,run方法執行的時間比較晚,這也說明在使用多線程技術時,代碼的運行結果與代碼執行順序或調用順序無關

       上面代碼介紹了線程的調用的隨機性,下面將演示線程的隨機性。

public class MyThread extends Thread {

	@Override
	public void run() {
		try {
			for(int i=0; i<10; i++) {
				int time = (int)(Math.random()*1000);
				Thread.sleep(time);
				System.out.println("run="+Thread.currentThread().getName());
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
	public static void main(String[] args) {
		MyThread my = new MyThread();
		my.setName("myThread");
		my.start();
		
		try {
			for(int i=0; i<10; i++) {
				int time = (int)(Math.random()*1000);
				Thread.sleep(time);
				System.out.println("main="+Thread.currentThread().getName());
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}
}

       在代碼中,爲了展現出線程具有隨機特性,所以使用隨機數的形式來使線程得到掛起的效果,從而表現出CPU執行哪個線程具有不確定性。Thread.java類中的start方法通過‘線程規劃器‘此線程已經準備就緒,等待調用線程對象的run方法。這個過程其實就是讓系統安排一個時間來調用Thread中的run方法,也就是線程得到運行,啓動線程,具有異步執行的效果。如果直接調用代碼thread.run就不是異步執行。 而是同步,那麼此線程對象並不會交給"線程規劃器"來進行處理,而是由main主線程來調用run方法,也就是必須等run方法中的代碼執行完成後才能執行後面的代碼。(看下面代碼中的註釋)

    public static void main(String[] args) {
		MyThread my = new MyThread();
		my.setName("myThread");
		my.run();// 由start替換成run,運行結果發生變化,先執行完線程裏面的,然後再執行main方法裏面的	
		try {
			for(int i=0; i<10; i++) {
				int time = (int)(Math.random()*1000);
				Thread.sleep(time);
				System.out.println("main="+Thread.currentThread().getName());
			}
		} catch (InterruptedException e) {
			e.printStackTrace();
		}
	}

   另外還需要注意一點,執行start()方法的順序不代表線程啓動的順序.

 

1.2.2 實現Runnable接口

    如果欲創建的線程類已經有一個父類了,這時就不能再繼承自Thread類了,因爲Java不支持多繼承,所以就需要實現Runnable接口來應對這樣的情況.

public class MyRunnable implements Runnable{

    @Override
    public void run() {
        System.out.println("運行中!");
    }

    public static void main(String[] args) {
        Runnable runnable = new MyRunnable();
        Thread thread = new Thread(runnable);
        thread.start();
        System.out.println("運行結束");// 運行結束/n 運行中!
    }
}

   使用繼承Thread類的方式來開發多線程應用程序在設計上是有侷限性的,因爲Java是單根繼承,不支持多繼承.另外需要說明的是,Thread類也實現了Runnable接口,那也就意味着構造函數Thread(Runnable target)不光可以傳入Runnable接口的對象,還可以傳入一個Thread類的對象,這樣做完全可以將一個Thread對象中的run()方法交由其他的線程進行調用.

1.2.3 實例變量與線程安全

    自定義線程類中的實例變量針對其他線程可以有共享與不共享之分,這在多個線程之間進行交互時是很重要的一個技術點.

    (1)不共享數據的情況

public class MyThread extends Thread {
    private int count = 5;
    public MyThread(String name) {
        super();
        this.setName(name);
    }

    @Override
    public void run() {
        super.run();
        while(count > 0) {
            count --;
            System.out.println("由"+this.currentThread().getName()+"計算,count=" + count);
        }
    }

    public static void main(String[] args) {
        Thread t1 = new MyThread("A");
        Thread t2 = new MyThread("B");
        Thread t3 = new MyThread("C");

        t1.start();
        t2.start();
        t3.start();
    }
}

打印結果爲:


由B計算,count=4
由B計算,count=3
由B計算,count=2
由B計算,count=1
由B計算,count=0
由C計算,count=4
由A計算,count=4
由A計算,count=3
由C計算,count=3
由A計算,count=2
由A計算,count=1
由A計算,count=0
由C計算,count=2
由C計算,count=1
由C計算,count=0

(1)共享數據的情況,意思就是多個線程訪問同一個變量

public class MyThread extends Thread {
    private int count = 5;


    public MyThread() {
        super();
    }
    public MyThread(String name) {
        super();
        this.setName(name);
    }

    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("由" + this.currentThread().getName() + "計算,count=" + count);
    }

    public static void main(String[] args) {
        Thread thread = new MyThread();

        Thread t1 = new Thread(thread, "A");
        Thread t2 = new Thread(thread, "B");
        Thread t3 = new Thread(thread, "C");
        Thread t4 = new Thread(thread, "D");
        Thread t5 = new Thread(thread, "E");


        t1.start();
        t2.start();
        t3.start();
        try {
            Thread.sleep(100);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        t4.start();
        t5.start();

    }
}

結果如下:由此可以得知產生了"非線程安全"問題.

由B計算,count=3
由C計算,count=2
由A計算,count=3
由E計算,count=1
由D計算,count=0

如果要結果正確,代碼改動如下(新添加一個synchronize關鍵字)

@Override
    public synchronized void run() {
        super.run();
        count--;
        System.out.println("由" + this.currentThread().getName() + "計算,count=" + count);
    }

通過在run方法前加入synchronize關鍵字,使多個線程在執行run方法時,以排隊的方式進行處理.當一個線程調用run前,先判斷run方法有沒有上鎖,如果上鎖,說明有其它線程正在調用run方法,必須等待其它線程對run方法調用結束後纔可以執行run方法.這樣也就實現了排隊調用run方法的目的,synchronize可以在任意對象及方法上加鎖,而加鎖的這段代碼稱爲"互斥區"或"臨界區".

    當一個線程想要執行同步方法裏面的代碼時,線程首先嚐試去拿這把鎖,如果能拿到這把鎖,那麼這個線程就可以執行synchronize裏面的代碼.如果不能拿到這把鎖,那麼這個線程就會不斷地去長度拿這把鎖,知道能拿到爲止.而且是多線程同時去爭搶這把鎖.

1.3 currentThread()方法

currentThread()方法可返回代碼正在被那個線程調用的信息.

public class CurrentThreadTest extends Thread{
    public CurrentThreadTest() {
        System.out.println("constructor is :" + currentThread().getName());
    }

    @Override
    public void run() {
        super.run();
        System.out.println("run is :" + currentThread().getName());
    }

    public static void main(String[] args) {
        Thread thread = new CurrentThreadTest();
        thread.start();// run is :thread-0
//        thread.run(); // run is :main
    }
}

稍微複雜一點的:

public class CountOperate extends Thread {
    public CountOperate() {
        System.out.println("CountOperate--begin");
        System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName());
        System.out.println("this.getName()="+this.getName());
        System.out.println("CountOperate--end");
    }

    @Override
    public void run() {
        System.out.println("run--begin");
        System.out.println("Thread.currentThread().getName()="+Thread.currentThread().getName());
        System.out.println("this.getName()="+this.getName());
        System.out.println("run--end");
    }

    public static void main(String[] args) {
        Thread thread = new Thread(new CountOperate());
        thread.setName("Demo");
        thread.start();
    }
}

打印結果如下:

CountOperate--begin
Thread.currentThread().getName()=main
this.getName()=Thread-0
CountOperate--end
run--begin
Thread.currentThread().getName()=Demo
this.getName()=Thread-0
run--end

1.4 isAlive()

    isAlive()的功能是判斷該線程是否處於活動狀態.活動狀態就是線程未停止.當線程處於正在運行或者準備開始運行的狀態,就認爲線程是存活的.   

public class IsAliveTest extends Thread{

    @Override
    public void run() {
        System.out.println("run: isAlive=" + this.isAlive());// true
    }

    public static void main(String[] args) {
        Thread thread = new IsAliveTest();
        System.out.println("begin: isAlive=" + thread.isAlive());// false
        thread.start();
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
        }
        System.out.println("end: isAlive=" + thread.isAlive());// false

    }
}

1.5 sleep()方法

    sleep()的作用是在指定的毫秒數內讓當前"正在執行的線程"休眠(暫停執行).這個"正在執行的線程"是指this.currentThread()返回的線程.   

public class MyThread1 extends Thread{

    @Override
    public void run() {
        try {
            System.out.println("run threadName = " + this.currentThread().getName() + " begin");// 1
            Thread.sleep(2000);
            System.out.println("run threadName = " + this.currentThread().getName() + " end");// 2
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }

    public static void main(String[] args) {
        MyThread1 myThread = new MyThread1();
        System.out.println("begin = " + System.currentTimeMillis());// 3
//        myThread.start();// 順序 3,4,1,2
        myThread.run();// 順序 3,1,2,4
        System.out.println("end = " + System.currentTimeMillis());// 4
    }
}

1.6 getId()方法 

    getId()方法的作用是取得線程的唯一標識.

    

    

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