JAVASE-多線程

Thread

多線程為我們解決了程序中需要并發(fā)執(zhí)行多個任務(wù)的操作,可以通過創(chuàng)建一個線程來負責(zé)執(zhí)行需要執(zhí)行的任務(wù)。創(chuàng)建線程的方式有兩種:
1:創(chuàng)建一個類來繼承Thread類,在類中重寫需要執(zhí)行對應(yīng)操作的run方法

class MyThread extends Thread {
   public void run(){
     for(int i=0;i<1000;i++){
       system.out.println(11111);
    }
 }
}
//利用造型創(chuàng)建Thread
public static void main(String[] args){
  Thread t1 = new MyThread();
}
t1.start();

需要注意的是,啟動線程需要調(diào)用Thread的start方法,而不是重寫的run方法,run方法中定義了該線程需要具體做的事情,當(dāng)start方法調(diào)用后,線程納入線程調(diào)度,線程調(diào)度會自動分配CPU時間片來執(zhí)行線程,線程運行時會自動調(diào)用run方法。另外,run方法是在start方法調(diào)用完畢后執(zhí)行的,而不是start方法運行過程中執(zhí)行。
這種創(chuàng)建方法可以匿名類來實現(xiàn),相對簡單

Thread t2 = new Thread(){
  public void run(){
    system.out.println("具體方法");
 }
}

這種線程創(chuàng)建方式由于以下兩點不足所以不推薦使用:
a,由于Java是單繼承的,所以當(dāng)當(dāng)前類繼承了Thread類時就不能繼承其他父類來實現(xiàn)重用,如果不繼承Thread就不能作為單一線程并發(fā),這里出現(xiàn)了繼承沖突。
b,由于線程類要執(zhí)行操作就必須重寫run方法,導(dǎo)致線程和執(zhí)行的任務(wù)出現(xiàn)了強耦合關(guān)系,該線程只能執(zhí)行該任務(wù),不利于我們后續(xù)對此線程的復(fù)用,要知道我們不可能有多少任務(wù)就開多少線程,要盡可能的保證效率的同時減少線程數(shù)量。
2:單獨創(chuàng)建Runnable接口并重寫run方法來單獨定義任務(wù),并通過Thread實例來實現(xiàn)Runnable接口,實現(xiàn)線程和任務(wù)的解耦

Runnable r1 = new Runnable(){
  public void run(){
    system.out.println("任務(wù)一");
  }
}
Runnable r2 = new Runnable(){
  public void run(){
    system.out.println("任務(wù)二");
  }
}
Thread t = new Thread(r1);
Thread t1 = new Thread(r2);
t.start();
t1.start();

獲取當(dāng)前線程
Java當(dāng)中所有的程序都是跑在線程上的,Java提供了一個靜態(tài)方法來獲取這個線程
static Thread currentThread()
在哪個方法中運行該方法就會獲取運行該方法的線程,main方法也是靠線程運行,其過程是:當(dāng)程序啟動時,操作系統(tǒng)創(chuàng)建一個進程來運行虛擬機,進程運行后會創(chuàng)建一個線程來運行main方法,進程中至少需要一個線程。

Thread t = Thread.currentThread();
System.out.println(t);//Thread[main,5,main]

獲取線程相關(guān)信息的API

Thread main = Thread.currentThread();
//線程ID
long id = main.getId();
//線程名
String name = main.getName();
//優(yōu)先級
int priority = main.getPriority();
//是否為守護線程
boolean isDaemon = main.isDaemon();
//是否活著
boolean isAlive = main.isAlive();
//是否被中斷
boolean isInterrupted = main.isInterrupted();

線程優(yōu)先級
線程不能干預(yù)線程調(diào)度的工作,即線程無法決定時間片分配給哪個線程,也不能決定分配的具體次數(shù),但可以通過更改線程優(yōu)先級來提高分配到CPU時間片的幾率,線程優(yōu)先級分為10個等級,最低為1,默認5,最高10,理論上優(yōu)先級越高獲取CPU時間片的幾率越大。

public static void main(String[] args) {
        new Thread(new Runnable(){
            
            @Override
            public void run() {
                for(int i = 0;i <10; i++){
                    Thread t = Thread.currentThread();
                    t.setPriority(Thread.MAX_PRIORITY);
                    System.out.println("max");
                }
            }
            
        }).start();
        new Thread(new Runnable(){

            @Override
            public void run() {
                for(int i = 0;i <10; i++){
                    Thread t = Thread.currentThread();
                    t.setPriority(Thread.NORM_PRIORITY);
                    System.out.println("normal");
                }
            }
            
        }).start();
        new Thread(new Runnable(){

            @Override
            public void run() {
                for(int i = 0;i <10; i++){
                    Thread t = Thread.currentThread();
                    t.setPriority(Thread.MAX_PRIORITY);
                    System.out.println("min");
                }
            }
            
        }).start();
    }

守護線程
守護線程又叫做后臺線程,當(dāng)一個進程中的所有前臺線程都結(jié)束時,進程結(jié)束前會強制結(jié)束后臺線程。比如Java中的GC就是一個典型的后臺線程,程序運行過程中GC會定時檢查堆中是否有垃圾可以清理,當(dāng)前臺線程都結(jié)束時GC也會停止工作。

Thread t = new Thread(){
  public void run(){
     System.out.println("這里為后臺線程任務(wù)");
  }
};
//在啟動線程前設(shè)置為后臺線程
t.setDaemon(true);
t.start();

多線程并發(fā)的安全問題

同步鎖

當(dāng)一個程序中有很多線程的時候,會產(chǎn)生多個線程同時訪問一個方法的現(xiàn)象,比如兩個人同時都想上廁所,就會產(chǎn)生沖突,而程序當(dāng)中解決這個問題的方法和現(xiàn)實當(dāng)中一樣,就是排隊,而排隊的先決條件就是給廁所上一個鎖,這樣即便有人想搶也進不去。
我們利用synchronized關(guān)鍵字來給需要的方法或代碼塊上鎖,例如一個方法被上鎖后就不允許多個線程同時執(zhí)行它,這個方法被稱為同步方法

class Bathroom {
  public synchronized void goToBathroom(){
    //假如一個人上廁所耗時5秒
    Thread.sleep(5000);
    System.out.println("廁所有人~");
  }
}
public static void main(String[] args){
  Bathroom bt = new Bathroom();
  Thread person1 = new Thread(){
    public void run(){
      bt.goToBathroom();
    }
  };
  Thread person2 = new Thread(){
    public void run(){
      bt.goToBathroom();
    }
  };
  person1.start();
  person2.start();
}

這兩個線程運行起來后線程調(diào)度會隨機分配時間片給他們,當(dāng)一個線程在5秒內(nèi)訪問方法的時候該方法就被上了鎖,其他線程無法訪問,只有訪問結(jié)束后其他線程才會訪問。這樣就起到了排隊的效果。

有效縮小同步鎖的范圍可以在保證安全的前提下提高并發(fā)效率
比如我們?nèi)ド虉鲑I衣服,先挑選喜歡的衣服然后拿去試衣間試衣服,這兩個過程我們沒必要都排隊,只有試衣服的時候才會排隊并且關(guān)門上鎖,在程序中可以這樣表示:

class Shopping{
  Thread t = Thread.currentThread();
  public void buy(){
     System.out.println(t+"正在挑選衣服~");
     Thread.sleep(5000);

     synchronized(this){
     System.out.println(t+"正在試衣服~請排隊~");
     Thread.sleep(5000);
  }
  System.out.println(t+"結(jié)賬離開~");
}

}
public class test{
  public static void main(String[] args){
    Shopping s = new Shopping();
    Thread person1 = new Thread(){
      public void run(){
        s.buy();
      }
    };
   Thread person2 = new Thread(){
      public void run(){
        s.buy();
      }
    };
    person1.start();
    person2.start();
  }
}

這樣做可以減少排隊時間,提高商場的運作效率,程序中同樣,當(dāng)許多線程同時要訪問一個方法而必須排隊時我們可以縮小同步范圍,只將方法中必須排隊執(zhí)行的代碼塊進行上鎖,而方法中其他代碼可以并發(fā)執(zhí)行,減少排隊時間。這里需要注意的是在給方法中代碼塊上鎖時要指定一個同步監(jiān)視器,通常情況下都可用this來指定,即當(dāng)前對象,要想同步代碼塊有效果必須保證多線程看到的鎖對象是同一個對象。本例中同一個對象指代為同一個商場實例,如果是不同的商場即不同的對象,也就不存在排隊或者搶的問題,因為它們根本不搭嘎~

靜態(tài)方法的同步
當(dāng)我們給一個靜態(tài)方法加鎖的時候,鎖對象就為當(dāng)前類的類對象,當(dāng)JVM加載一個類的時候,首先讀取該類的class文件,并同時創(chuàng)建一個類對象來保存該類的類信息,一個類有且只有一個類對象,所以靜態(tài)的同步方法一定有同步效果。

互斥鎖

互斥鎖是指最多只有一個線程可以持有的鎖。當(dāng)鎖對象相同時,如果該對象下有兩個方法或者兩段代碼以上都加了synchronized關(guān)鍵字,代表這兩個方法或者代碼互斥,互斥意味著當(dāng)一個線程解鎖并執(zhí)行代碼時另外一段加鎖的代碼其他線程無法解鎖,也就無法同時運行,只有當(dāng)該線程運行完畢歸還鎖后其他線程才能解鎖運行。這種情形通常出現(xiàn)在多線程訪問公共資源時,為了避免資源被篡改或丟失,僅允許一個線程進行訪問,而在訪問期間其他線程無法訪問。

class Test{
  public synchronized void methodA(){
    Thread t = Thread.currentThread();
    System.out.println(t+"正在執(zhí)行A方法~");
    Thread.sleep(5000);
    System.out.println("A方法執(zhí)行完畢!");
  }
  public synchronized void methodB(){
    Thread t = Thread.currentThread();
    System.out.println(t+"正在執(zhí)行B方法~");
    Thread.sleep(5000);
    System.out.println("B方法執(zhí)行完畢!");
  }
}

public class Lock{
  public static void main(String[] args){
    Test test = new Test();
    Thread t1 = new Thread(){
      public void run(){
        test.methodA();
      }
    };
    Thread t2 = new Thread(){
      public void run(){
        test.methodB();
      }
    };
    t1.start();
    t2.start();
  }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容