1. 程式人生 > 實用技巧 >執行緒(一)

執行緒(一)

執行緒安全:StringBuilder非執行緒 StringBuffer執行緒 / Vector執行緒 ArrayList非執行緒 / 快速迭代時不能有其他執行緒進行操作

程序:作業系統結構的基礎:是一個正在執行的程式,計算機中正在執行的程式例項

執行緒:執行緒(thread)是程序中某個單一順序的控制流,是程式執行的基本執行單元,任何一個程式中至少需要一個主執行緒

執行緒優缺點:
優點:程式執行思路步驟清晰,步驟分明
缺點:一次只能做一件事情,必須做完意見事後才能繼續做另一件事

可以使用執行緒讓多條業務邏輯同時處理成為可能!
使用執行緒的好處:非同步處理,簡化程式設計模型,提高CPU使用

執行緒同步:

只有單一的執行緒,按照既定的程式流依次執行程式碼

執行緒非同步:多個執行緒同時執行程式碼

執行緒的建立兩種方式:

繼承Thread類 :public class MyThread extends Thread{}

實現Runnable介面 :public class MyThread implements Runnable(){}

兩種方法都要重寫run方法,該方法是執行緒的核心方法,用於封裝執行緒要處理的業務內容

run方法是執行緒類中最重要的方法,該執行緒中所有需要單獨執行的業務邏輯都需在run方法中執行

public class MyThread extends Thread{
    
public void run(){ //執行緒執行的內容 } }

兩種方法的區別:
繼承自thread類是得到真正的一個執行緒物件,可以通過start方法開啟執行緒
實現runnable介面,僅僅是將執行緒中要實現邏輯進行了抽離,同時也便於多執行緒的併發處理。

啟用執行緒:使用start()方法啟動執行緒

線性遠端:run()方法不需要我們自己呼叫,當呼叫start()方法時將自動呼叫run方法,直到run方法執行完畢或人為停止或掛起

多執行緒的建立步驟:建立多個類繼承Thread類或實現Runnable介面

實現run方法

例項化執行緒類並呼叫start方法

如果要多個執行緒實現同樣的功能:
1.寫一個runnable中,封裝至不同的執行緒中(這種方式更方便)
2.繼承thread,多次new可以實現相同功能(這種方式不方便,但是執行緒內容不同的時候這種方法更方便)

執行緒的生命週期:執行緒和人一樣也會生老病死經理各個階段

主要經歷如下階段
  準備就緒:  new
  執行時 :  runnable //呼叫start後
  阻塞等待:   wait (blocked timed_waiting waiting)
  終止完成:  terminated

獲取執行緒的當前狀態: getState()
判斷執行緒的狀態: isAlive()

獲得執行緒的名稱:  getName()

控制執行緒的主要方法
  start() 啟動執行緒
  stop() 終止當前執行緒 (過時方法)
  suspend() 掛起執行緒
  resume() 繼續掛起的執行緒
  join() 等待執行緒執行完畢
  yield() 暫緩執行緒
  sleep() 執行緒等待
  wait() 執行緒等待
  notify() 喚醒執行緒

yeild、 join、suspend、sleep、wait等方法的呼叫,都可是程式進入阻塞等待狀態,若一致處於阻塞狀態,則成為執行緒死鎖
執行緒死鎖:執行緒處於阻塞狀態後無法恢復,該情形成為執行緒死鎖(會導致程式無法繼續進行,所以所有處於阻塞狀態的執行緒最後都應恢復執行,恢復後繼續進入執行時狀態)

例子:編寫兩個執行緒,並在各自執行緒中列印1-10的數值,觀察結果

通過繼承獲得的執行緒

/**
 * 自定義執行緒,通過繼承
 */
public class MyThread1 extends Thread{//繼承中有很多方法
    /**
     * 構造方法
     * 傳入執行緒的名稱
     * @param threadName
     */
    public MyThread1(String threadName){
        super(threadName);
     //構造方法中的狀態是new,等待就緒
//獲取執行緒的狀態 new System.out.println(this.getState()); //判斷執行緒是否存活 false,只有呼叫了start之後才會為true System.out.println(this.isAlive()); }
@Override
   //都要對run方法進行重寫
public void run() { //獲得執行緒的名稱 System.out.println("當前執行緒:"+getName()); System.out.println(this.getState()); System.out.println(this.isAlive());
     //迴圈列印
for(int i = 1; i <= 10; i++){ System.out.println("執行緒A:"+i); } } }

通過實現介面獲得的執行緒

/**
 * 使用Runable介面實現執行緒
 */
public class MyThread2 implements Runnable{//runnable中只有一個run方法

    @Override
  //都要對run方法進行重寫
public void run() {
     //進行迴圈列印
for(int i = 1; i <= 10; i++){ System.out.println("執行緒B:"+i); } } }

測試類

public class RunThead {

    /**
     * main方法的執行就會啟動一條主執行緒
     * 該主執行緒通常也將其稱為守護執行緒或幽靈執行緒
     * @param args
     */
    public static void main(String[] args) {
        System.out.println("程式開始執行");
        //例項化繼承了Thead執行緒
        MyThread1 th1 = new MyThread1("執行緒A");
        //開啟執行緒,不能直接訪問run方法,不然就不是開啟一個執行緒
        th1.start();
        try {
            //將執行緒加入執行緒佇列,使執行緒阻塞
            th1.join();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
     //若在外部列印狀態和是否存活,狀態則不一定,因為主執行緒和其餘執行緒是同時進行的,不一定哪個執行緒先結束
     //但是加了th1.join()方法後執行緒阻塞了以後,則會先將執行緒走完,再進行主執行緒後面的程式碼,則會看到結束後的狀態和不存活
System.out.println(
"main中的執行緒狀態:"+th1.getState()); System.out.println("main中的執行緒是否存活:"+th1.isAlive()); //執行緒如果處於結束狀態,無法再次啟動,如果需要在啟動,則需要重新建立
     
th1 = null;//賦值為null意思是這個執行緒結束以後要棄用了 th1 = new MyThread1("");//重新建立啟用 th1.start(); //例項化實現了Runnable介面的例項 MyThread2 runnable = new MyThread2();
     //如果實現介面後直接呼叫,則只能呼叫run方法,不能算一個執行緒
     //th2.run(); //可以將Runnable例項封裝至執行緒中,才是執行緒 Thread th2 = new Thread(runnable); th2.start();
     //由主執行緒進行執行 System.out.println("程式執行結束"); } }

例子:時間窗體實現長時間的走動

/**
 * 通過窗體實現時間的走動
 */
public class TimeFrame extends JFrame {
//都寫成屬性,等到用的時候再new出來
//標籤,用於呈現文字 private JLabel lbl; //開始按鈕 private JButton btnStart; //停止按鈕 private JButton btnStop;
   //掛起按鈕
private JButton btnSuspend;    //恢復按鈕 private JButton btnResume; //時間執行緒 private TimeThread thead;

   //構造方法
public TimeFrame(){ //設定窗體大小和位置 this.setBounds(600, 400, 600, 300); //設定窗體的關閉策略,關閉窗體時程式同時關掉 this.setDefaultCloseOperation(JFrame.DISPOSE_ON_CLOSE); //設定窗體的佈局 this.setLayout(null); //初始化元件 initCompoent(); } /** * 初始化元件 */ private void initCompoent(){
     //初始化標籤 lbl
= new JLabel();
     //再標籤中新增資料 lbl.setText(
"這是一個標籤,用於呈現文字");
     //設定標籤的大小和位置 lbl.setBounds(
100,100,400,25); //將標籤新增至窗體 this.add(lbl);
     //初始化按鈕 btnStart
= new JButton("start");
     //設定按鈕的大小和位置 btnStart.setBounds(
100, 150, 100, 25);
     //將按鈕新增至窗體
this.add(btnStart); //繫結按鈕的監聽器 btnStart.addActionListener(new ButtonStartListener());

     //相同的操作設定按鈕 btnStop
= new JButton("stop"); btnStop.setBounds(200, 150, 120, 25); this.add(btnStop); //繫結按鈕的監聽器 btnStop.addActionListener(new ButtonStopListener());

     //相同的操作設定按鈕 btnSuspend
= new JButton("suspend"); btnSuspend.setBounds(300, 150, 120, 25); this.add(btnSuspend); //繫結按鈕的監聽器 btnSuspend.addActionListener(new ButtonSuspendListener());

     //相同的方法設定按鈕 btnResume
= new JButton("resume"); btnResume.setBounds(400, 150, 120, 25); this.add(btnResume);
     //繫結按鈕的監聽器 btnResume.addActionListener(
new ButtonResumeListener()); } /** * start按鈕監聽器 */ public class ButtonStartListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { //判斷條件是為了按鈕只能操作一次,多次重複點選是無用的
       //執行緒已經開始且執行緒不存活了,則可以新建一個執行緒
//if(thead != null && !thead.isAlive()){
       // if(thead != null && thead.getState() == Thread.State.TERMINATED){ System.out.println("前一條執行緒已經結束,建立了新的執行緒"); //建立一條新的執行緒 thead = new TimeThread(); //啟動執行緒 thead.start(); } //第一次執行執行緒,也可以新建一個執行緒 else if(thead == null){ System.out.println("第一次建立新的執行緒"); //建立一條新的執行緒 thead = new TimeThread(); //啟動執行緒 thead.start(); } } } /** * Stop按鈕監聽器 */ public class ButtonStopListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { //如果執行緒已被建立並且處於執行時狀態 if(thead != null && thead.isAlive()){ //使執行緒停止,提前進入銷燬狀態 thead.stop(); } } } /** * Suspend按鈕監聽器 */ public class ButtonSuspendListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { System.out.println(thead.getState()); //如果執行緒處於執行時狀態(不能用isAlive判斷,因為阻塞的狀態下也是alive) if(thead != null && (thead.getState() == Thread.State.RUNNABLE || thead.getState() == Thread.State.TIMED_WAITING)){ //將執行緒掛起,執行緒進入阻塞狀態;處於suspend阻塞下的執行緒只能通過resume方法恢復 System.out.println("執行緒掛起"); thead.suspend(); } } } /** * Resume按鈕監聽器 */ public class ButtonResumeListener implements ActionListener{ @Override public void actionPerformed(ActionEvent e) { //對suspend執行緒進行恢復 if(thead != null){ thead.resume(); } } } /** * 用於重新整理時間的執行緒 */ public class TimeThread extends Thread{ private SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); @Override public void run() { while(true){ //獲取系統時間 Date date = new Date(); //將日期格式化並寫入標籤 lbl.setText(sdf.format(date)); try { //使執行緒休眠,進入到阻塞的狀態,當到達指定時間後,執行緒將自動恢復至執行時狀態
            //1000毫秒是1s Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } } } } public static void main(String[] args) { //例項化窗體 TimeFrame frame = new TimeFrame(); //顯示窗體 frame.setVisible(true); } }

stop方法需要慎用,可能會引發多執行緒的資料安全問題,多執行緒使用過程中如果產生了併發訪問(多個執行緒對同一個物件進行了訪問:比如A執行緒往C集合中新增資料,同時B執行緒也要往C集合中新增資料),需要用到執行緒鎖(物件鎖)

比如:A執行緒要訪問C物件進行資料處理並將該C物件進行了鎖定,另一個執行緒B也要訪問C物件,但是需要等待A執行緒完成計算後,根據計算的結果再接著處理。此時如果A執行緒執行了stop方法,該方法將會釋放物件鎖,B執行緒此時會提前訪問到C物件,B執行緒接下來的資料處理可能會出現問題

stop方法再多執行緒開發過程中,如果沒有涉及到併發處理的情形,是沒有隱患問題的

suspend將執行緒掛起,執行緒會進去阻塞狀態,被掛起的執行緒只能同構resume進行恢復,兩者必須一起進行使用
suspend方法可能會引起死鎖,使用suspend方法呼叫的執行緒必須要通過resume方法才能恢復(圖形的suspend可以通過非同步呼叫resume,但是如果不是圖形狀態,再本執行緒中suspend後無法對自身呼叫resume方法,就會無限阻塞引起死鎖。除非用main方法主執行緒進行恢復,主執行緒中通過呼叫resume)