#在千鋒“逆戰”學習第28天#多線程、Question12

多線程

什麼是進程
程序是靜止的,只有程序運行時才能稱爲進程。

單核CPU在任何時間點上只能運行一個進程,宏觀並行,微觀串行。

什麼是線程
線程,又稱輕量級進程,程序中的一個順序控制流程,同時也是CPU的基本調度單位。進程由多個線程組成,彼此間完成不同的工作,交替執行,稱爲多線程。

線程的組成

任何一個線程都有基本的組成部分:

CPU時間片:操作系統(OS)會爲每個線程分配執行時間

運行數據:
	堆空間:存儲線程需使用的對象,多個線程可共享堆中的對象
	棧空間:存儲線程需要使用的局部變量,每個線程都擁有獨立的棧

線程的邏輯代碼			

創建線程

創建線程的第一種方式:
1、繼承Theard類
2、覆蓋run()方法
3、創建子類對象
4、調用start()方法

創建線程的第二種方式:
1、實現Runnable接口
2、覆蓋run()方法
3、創建實現類對象
4、創建線程對象(注意需要將創建的實現類對象傳入線程對象)
5、調用start()方法

常見方法

休眠
public static void sleep(long millis)
當前線程主動休眠millis毫秒

放棄
public static void yield()
當前線程主動放棄時間片,回到就緒狀態,競爭下一次時間片

結合
public final void join()
允許其他線程加入到當前線程(待加入線程結束後再運行當前線程)

線程的狀態
在這裏插入圖片描述

線程的安全問題
在這裏插入圖片描述
線程不安全:
當多線程併發訪問臨界資源時,如果破壞原子操作,可能會造成數據不一致
臨界資源:共享資源(同一對象),一次僅允許一個線程使用,纔可確保其正確性
原子操作:不可分割的多步操作,被視作一個整體,其順序和步驟不可打亂或缺省

同步方式(1)
同步代碼塊:
synchronized(臨界資源對象){//對臨界對象資源加鎖
代碼//原子操作
}

注:每個對象都有一個互斥鎖標記,用來分配給線程的
只有擁有互斥鎖標記的線程,才能進入該對象加鎖的同步代碼塊
線程退出同步代碼塊時,會釋放相應的互斥鎖標記

同步方式(2)
同步代碼塊:
synchronized 返回值類型 方法名稱(形參列表0){//對當前對象(this)加鎖
//代碼(原子操作)
}

注:
只有擁有對象互斥鎖標記的線程,才能進入該對象加鎖的同步方法中
線程退出同步方法時,會釋放相應的互斥鎖標記

----------------------------------------------------------------------------------作業分割線

  1. 一個單 CPU 的機器,如何同時執行多個線程?請簡述其原理
    答:單核CPU在任何時間點只能處理一個線程,同時執行多個線程是因爲CPU快速的在各個線程之間切換,宏觀上是並行的,微觀是串行的。

  2. (線程的創建)有以下代碼

public class Example implements Runnable {
public void run() {
while(true) { } }
public static void main(String args[]) {
Example ex1 = new Example();
Example ex2 = new Example();
Example ex3 = new Example();
ex1.run();
ex2.run();
ex3.run();
} }

選擇正確答案:
A. 代碼編譯失敗,因爲 ex2.run()無法獲得執行
B. 代碼編譯成功,存在 3 個可運行的線程
C. 代碼編譯成功,存在 1 個可運行的線程

答:C

  1. (線程的創建)有以下代碼
class Example implements Runnable {
public static void main(String args[]) {
Thread t = new Thread(new Example());
t.start();
}
public void run(int limit) {
for (int x = 0; x<limit; x++) {
System.out.println(x);
} } }

選擇正確答案:
A. 打印輸出,從 0 至 limit
B. 無內容輸出,因爲沒有明確調用 run()方法。
C. 代碼編譯失敗,因爲沒有正確實現 Runnable 接口
D. 代碼編譯失敗,如果聲明類爲抽象類,可使代碼編譯成功。
E. 代碼編譯失敗,如果去掉 implements Runnable,可使代碼編譯成功。

答:C
沒有覆蓋run方法,而是方法重載。

  1. (sleep 方法)有如下代碼
class Example {
public static void main(String args[]) {
Thread.sleep(3000);
System.out.println(“sleep”);
} }

選擇正確答案:
A. 編譯出錯
B. 運行時異常
C. 正常編譯運行,輸出 sleep
D. 正常編譯運行,但沒有內容輸出

答:A
需要在類中聲明InterruptedException異常

  1. (線程的創建)創建兩個線程,要求如下:
    I. 一個線程輸出 100 個1到26,另一個線程輸出 100 個 A~Z
    II. 一個線程使用繼承 Thread 類的寫法,另一個線程使用實現 Runnable 接口的寫法。
    在這裏插入圖片描述

  2. (線程的同步)有如下代碼

class MyThread1 extends Thread{
Object lock;
public MyThread1(Object lock){
this.lock = lock;
}
public void run(){
synchronized(lock){ //1
for(int i = 0; i<=10; i++){
try{
Thread.sleep( (int)(Math.random()*1000) );
}catch(Exception e){}
System.out.println(“$$$”);
} } } }
class MyThread2 extends Thread{
Object lock;
public MyThread2(Object lock){
this.lock = lock;
}
public void run(){
synchronized(lock){ //2
for(int i = 0; i<=10; i++){
try{
Thread.sleep((int)(Math.random()*1000) );
}catch(Exception e){
}
System.out.println(“###”);
} } } }
public class TestMyThread{
public static void main(String args[]){
Object lock = new Object();
Thread t1 = new MyThread1(lock);
Thread t2 = new MyThread2(lock);
t1.start();
t2.start();
} }

問:在//1 和//2 處加上的 synchronized 起什麼作用?如果不加 synchronized,運行程序有什麼不同的
地方?

答:起到讓其中一個線程執行完再執行另外一個線程的作用,加了sychronized會連續輸出11個相同的符號之後再輸出11個另外的符號,而不加鎖的話就會兩個線程互相競爭時間片,穿插輸出兩種符號各11個。

  1. (線程同步)有如下代碼
class MyThread extends Thread{
private String data;
public void run(){
synchronized(data){
for(int i = 0; i<10; i++){
try{
Thread.sleep((int)(Math.random()*1000));
}catch(Exception e){
}
System.out.println(data);
} } } }
public class TestMyThread {
public static void main(String args[]){
Thread t1 = new MyThread(“hello”);
Thread t2 = new MyThread(“world”);
t1.start();
t2.start();
} }

問:上述代碼輸出的結果是什麼?
A. 先輸出 100 個 hello,然後是 100 個 world
B. 先輸出 100 個 world,然後是 100 個 hello
C. 線程不同步,因此交替輸出 hello 和 world

答:選C
因爲同步鎖的對象不同,所以線程不同步,交替輸出。

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