java 多線程

進程 : 正在執行的程序稱作為一個進程。 進程負責了內存空間的劃分。

問題: windows號稱是多任務的操作系統,那么windows是同時運行多個應用程序嗎?

  • 從宏觀的角度: windows確實是在同時運行多個應用程序。
  • 從微觀角度: cpu是做了一個快速切換執行的動作,由于速度態度,所以我感覺不到在切換 而已。

線程: 線程在一個進程 中負責了代碼的執行,就是進程中一個執行路徑,

多線程: 在一個進程中有多個線程同時在執行不同的任務。

疑問 :線程負責了代碼 的執行,我們之前沒有學過線程,為什么代碼可以執行呢?
運行任何一個java程序,jvm在運行的時候都會創建一個main線程執行main方法中所有代碼。

一個java應用程序至少有幾個線程?

至少有兩個線程, 一個是主線程負責main方法代碼的執行,一個是垃圾回收器線程,負責了回收垃圾。

多線程的好處:
  1. 解決了一個進程能同時執行多個任務的問題。
  2. 提高了資源的利用率。
多線程 的弊端:
  1. 增加cpu的負擔。
  2. 降低了一個進程中線程的執行概率。
  3. 引發了線程安全 問題。
  4. 出現了死鎖現象。

如何創建多線程:

方式一:
  1. 自定義一個類繼承Thread類。
  2. 重寫Thread類的run方法 , 把自定義線程的任務代碼寫在run方法中
  3. 創建Thread的子類對象,并且調用start方法開啟線程

疑問: 重寫run方法的目的是什么?
每個線程都有自己的任務代碼,jvm創建的主線程的任務代碼就是main方法中的所有代碼, 自定義線程的任務代碼就寫在run方法中,自定義線程負責了run方法中代碼。

注意: 一個線程一旦開啟,那么線程就會執行run方法中的代碼,run方法千萬不能直接調用,直接調用run方法就相當調用了一個普通的方法而已并沒有開啟新的線程。

public class Demo1 extends Thread {
    
    @Override  //把自定義線程的任務代碼寫在run方法中。
    public void run() {
        for(int i  = 0 ; i < 100 ; i++){
            System.out.println("自定義線程:"+i);
        }
    }
    
    
    public static void main(String[] args) {
        //創建了自定義的線程對象。
        Demo1 d = new Demo1();
        //調用start方法啟動線程
        d.start();
        
        for(int i  = 0 ; i < 100 ; i++){
            System.out.println("main線程:"+i);
        }
    }

}

需求: 模擬QQ視頻與聊天同時在執行。



class TalkThread extends Thread{
    
    @Override
    public void run() {
        while(true){
            System.out.println("hi,你好!開視頻唄...");
        }
    }
}

class VideoThread extends Thread{
    
    @Override
    public void run() {
        while(true){
            System.out.println("視頻視頻....");
        }
    }
}

public class Demo2 {
    
    public static void main(String[] args) {
        TalkThread talkThread = new TalkThread();
        talkThread.start();
        VideoThread videoThread = new VideoThread();
        videoThread.start();
    }
}
方式二:
  1. 自定義一個類實現Runnable接口。
  2. 實現Runnable接口 的run方法,把自定義線程的任務定義在run方法上。
  3. 創建Runnable實現類對象。
  4. 創建Thread類 的對象,并且把Runnable實現類的對象作為實參傳遞。
  5. 調用Thread對象 的start方法開啟一個線程。

問題1: 請問Runnable實現類的對象是線程對象嗎?
Runnable實現類的對象并 不是一個線程對象,只不過是實現了Runnable接口 的對象而已。
只有是Thread或者是Thread的子類才是線程 對象。

問題2:為什么要把Runnable實現類的對象作為實參傳遞給Thread對象呢?作用是什么?
作用就是把Runnable實現類的對象的run方法作為了線程的任務代碼去執行了。

推薦使用: 第二種。 實現Runable接口的。
原因: 因為java單繼承 ,多實現的。


public class Demo3 implements Runnable{

    @Override
    public void run() {
        /*System.out.println("this:"+ this);
        System.out.println("當前線程:"+ Thread.currentThread());
        這個在實現Thread類中是一樣的,但是在這里是不一樣的,this是Runnable實現類
        但是當前線程是主方法中創建的Thread對象啟用的線程
        */
        for(int i = 0 ; i < 100 ; i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
    }
    
    public static void main(String[] args) {
        //創建Runnable實現類的對象
        Demo3 d = new Demo3();
        //創建Thread類的對象, 把Runnable實現類對象作為實參傳遞。
        Thread thread = new Thread(d,"狗娃");  //Thread類使用Target變量記錄了d對象,
        //調用thread對象的start方法開啟線程。
        thread.start();
        
        
        for(int i = 0 ; i < 100 ; i++){
            System.out.println(Thread.currentThread().getName()+":"+i);
        }
        
    } 
    
    /*
      Thread類 的run方法
      
     *  @Override
        public void run() {
            if (target != null) {
                target.run();  //就相當于Runnable實現類的對象的run方法作為了Thread對象的任務代碼了。
            }
        }
    */
}

線程常用的方法:

Thread(String name)     初始化線程的名字
setName(String name)    設置線程對象名
getName()             返回線程的名字
sleep()                 線程睡眠指定的毫秒數。 靜態的方法, 那個線程執行了sleep方法代碼那么就是那個線程睡眠。
currentThread()      返回當前的線程對象,該方法是一個靜態的方法, 注意: 那個線程執行了currentThread()代碼就返回那個線程 的對象。
getPriority()             返回當前線程對象的優先級   默認線程的優先級是5
setPriority(int newPriority) 設置線程的優先級    雖然設置了線程的優先級,但是具體的實現取決于底層的操作系統的實現(最大的優先級是10 ,最小的1 , 默認是5)。
    
public class Test07 extends Thread {
   
   public Test07(String name){
       super(name); //調用了Thread類的一個參數的構造方法.(設置線程名稱)
   }
   
   
   @Override
   public void run() {
       /*System.out.println("this:"+ this);
       System.out.println("當前線程對象:" + Thread.currentThread()); */
       
       for (int i = 0; i < 100 ; i++) {
           System.out.println(Thread.currentThread().getName()+":"+i);
           
           /*try {
               Thread.sleep(100);  //為什么在這里不能拋出異常,只能捕獲?? Thread類的run方法沒有拋出異常類型,所以子類不能拋出異常類型。
           } catch (InterruptedException e) {
               e.printStackTrace();
           } */
       }
       
       
   }
   
   
   public static void main(String[] args) throws InterruptedException {
       //創建了一個線程對象
       Test07 d = new Test07("狗娃");
       //d.sleep(1000); 雖然是d調用的sleep方法,但是該方法是在主線程中執行的,所以是主線程睡眠
       d.setPriority(10); //設置線程 的優先級。 優先級的數字越大,優先級越高  , 優先級的范圍是1~10
       d.start();
       
       for (int i = 0; i < 100 ; i++) {
           System.out.println(Thread.currentThread().getName()+":"+i);
       }
       
       /*
       System.out.println("自定義線程的優先級:"+d.getPriority());  //線程的優先級默認是5
       System.out.println("主線程的優先級:"+Thread.currentThread().getPriority());
       
       
       d.start();
       
       d.setName("鐵蛋"); //setName設置線程的名字
       d.start(); //開啟線程 
       
       Thread mainThread = Thread.currentThread();
       System.out.println("主線程的名字:"+ mainThread.getName());
   */
   }
}

需求: 模擬3個窗口同時在售50張 票 。

問題1 :為什么50張票被賣出了150次?

原因: 因為num是非靜態的,非靜態的成員變量數據是在每個對象中都會維護一份數據的,三個線程對象就會有三份。

解決方案:把num票數共享出來給三個線程對象使用。使用static修飾。

問題2: 出現了線程安全問題 ?

線程 安全問題的解決方案:sun提供了線程同步機制讓我們解決這類問題的。

java線程同步機制的方式:

    方式一:同步代碼塊
        
        同步代碼塊的格式:
            
            synchronized(鎖對象){
                需要被同步的代碼...
            }

同步代碼塊要注意事項:
1. 任意的一個對象都可以做為鎖對象。
2. 在同步代碼塊中調用了sleep方法并不是釋放鎖對象的。
3. 只有真正存在線程安全問題的時候才使用同步代碼塊,否則會降低效率的。
4. 多線程操作的鎖 對象必須 是唯一共享 的。否則無效。

需求: 一個銀行賬戶5000塊,兩夫妻一個拿著 存折,一個拿著卡,開始取錢比賽,每次只能取一千塊,要求不準出現線程安全問題。

    方式二:同步函數  :  同步函數就是使用synchronized修飾一個函數。

同步函數要注意的事項 :
    1. 如果是一個非靜態的同步函數的鎖 對象是this對象,如果是靜態的同步函數的鎖 對象是當前函數所屬的類的字節碼文件(class對象)。
    2. 同步函數的鎖對象是固定的,不能由你來指定 的。


推薦使用: 同步代碼塊。
    原因:
        1. 同步代碼塊的鎖對象可以由我們隨意指定,方便控制。同步函數的鎖對象是固定 的,不能由我們來指定。
        2. 同步代碼塊可以很方便控制需要被同步代碼的范圍,同步函數必須是整個函數 的所有代碼都被同步了。

*/

class SaleTicket extends Thread{

 static int num = 50;//票數  非靜態的成員變量,非靜態的成員變量數據是在每個對象中都會維護一份數據的。
 
 static Object o = new Object();

 public SaleTicket(String name) {
    super(name);
}

@Override
public void run() {
    while(true){
        //同步代碼塊
        synchronized ("鎖") { //靜態字符常量也可以,因為常量區只會存在一份    
            if(num>0){
                System.out.println(Thread.currentThread().getName()+"售出了第"+num+"號票");
                try {
                    Thread.sleep(100);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                num--;
            }else{
                System.out.println("售罄了..");
                break;
            }
        }
        
    }
}   

}

public class Demo4 {

public static void main(String[] args) {
    //創建三個線程對象,模擬三個窗口
    SaleTicket thread1 = new SaleTicket("窗口1");
    SaleTicket thread2 = new SaleTicket("窗口2");
    SaleTicket thread3 = new SaleTicket("窗口3");
    //開啟線程售票
    thread1.start();
    thread2.start();
    thread3.start();
    
}

}

/*
java中同步機制解決了線程安全問題,但是也同時引發死鎖現象。

死鎖現象:

死鎖現象出現 的根本原因:
1. 存在兩個或者兩個以上的線程。
2. 存在兩個或者兩個以上的共享資源。

死鎖現象的解決方案: 沒有方案。只能盡量避免發生而已。

*/

class DeadLock extends Thread{

public DeadLock(String name){
    super(name);
}


public void run() {
    if("張三".equals(Thread.currentThread().getName())){
        synchronized ("遙控器") {
            System.out.println("張三拿到了遙控器,準備 去拿電池!!");
            synchronized ("電池") {
                System.out.println("張三拿到了遙控器與電池了,開著空調爽歪歪的吹著...");
            }
        }
    }else if("狗娃".equals(Thread.currentThread().getName())){
        synchronized ("電池") { 
            System.out.println("狗娃拿到了電池,準備去拿遙控器!!");
            synchronized ("遙控器") {
                System.out.println("狗娃拿到了遙控器與電池了,開著空調爽歪歪的吹著...");
            }
        }
        
    }   
}

}

public class Demo2 {

public static void main(String[] args) {
    DeadLock thread1 = new DeadLock("張三");
    DeadLock thread2 = new DeadLock("狗娃");
    //開啟線程
    thread1.start();
    thread2.start();
}

}







最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容