執行緒(一)
執行緒安全: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)