《JAVA:從入門到精通》part 21

二十四条舔、多線程基礎(chǔ)

1. 線程簡介

  • 世間萬物可以同一時間內(nèi)完成多件事堆巧,例如人可以同時看書和聽歌虚婿,這種思想運用在JAVA中被稱為并發(fā),而將并發(fā)完成的每一件事情稱為線程插勤。

  • 在JAVA中沽瘦,并發(fā)機制非常重要革骨,但并不是所有的程序語言都支持線程。在以往的程序中析恋,多以一個任務(wù)完成后再進行下一個項目的模式進行開發(fā)良哲,這樣下一個任務(wù)的開始必須等待前一個任務(wù)的結(jié)束。JAVA語言提供了并發(fā)機制助隧,程序員可以在程序中執(zhí)行多個線程筑凫,每一個線程完成一個功能,并與其他線程并發(fā)執(zhí)行并村,這種機制被稱為多線程巍实。

  • Windows操作系統(tǒng)是多任務(wù)操作系統(tǒng),它以進程為單位橘霎。一個進程是一個包含有自身地址的程序蔫浆,每個獨立執(zhí)行的程序被稱為進程殖属,也就是正在執(zhí)行的程序姐叁。系統(tǒng)可以分配給每個進程一段有限的使用CPU的時間(也可以稱為CPU時間),CPU在這段時間中執(zhí)行某個進程洗显,然后下一個時間片又跳至另一個進程中去執(zhí)行外潜。由于CPU轉(zhuǎn)換較快,所以使得每個進程好像是同時執(zhí)行一樣挠唆。


    線程在電腦中的運行方式
  • 一個線程則是進程中的執(zhí)行流程处窥,一個進程中可以同時包括多個線程,每個線程也可以得到一小段程序的執(zhí)行時間玄组,這樣一個進程就可以具有多個并發(fā)執(zhí)行的線程滔驾。在單線程中,程序代碼按照調(diào)用順序依次往下執(zhí)行俄讹,如果需要一個進程同時完成多段代碼的操作哆致,就需要多線程。

2. 實現(xiàn)線程的兩種方式

  • 在JAVA中主要提供兩種方式實現(xiàn)線程患膛,分別為繼承java.lang.Thread類與實現(xiàn)java.lang.Runnable接口摊阀。

繼承Thread類

  • Thread類是java.lang包中的一個類,從這個類中Thre實例化的對象代表線程踪蹬,若要啟動一個新線程則需要建立Thread實例胞此。Thread類中常用的兩個構(gòu)造方法如下:
public Thread():創(chuàng)建一個新的線程對象
public Thread(String threadName):創(chuàng)建一個名稱為threadName的線程對象
  • 完成線程真正功能的代碼放在類的run()方法中,當一個類繼承Thread類后跃捣,就可以在該類中覆蓋run()方法漱牵,將實現(xiàn)該線程功能的代碼寫入run()方法中,然后同時調(diào)用Thread類中的start()方法執(zhí)行線程疚漆,也就是調(diào)用run()方法酣胀。

下面看一個案例:

  • 在控制臺處打印出數(shù)字蚊惯,先使用單線程方式打印。
package com.lzw;

public class dxc1
{
    public static void main(String[] args)
    {
        Thread a=new threadA();
        a.start();
    }
}
class threadA extends Thread
{   @Override
    public void run()
    {
        for (int i=0;i<100;i++)
        {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        }
    }

運行結(jié)果:

  • 由運行結(jié)果可以看到灵临,單線程工作時與之前的效果完全一致截型,也是一個數(shù)字一個數(shù)字地打印出來。下面使用多線程工作方式在控制臺處打印出數(shù)字和字母:
package com.lzw;

public class dxc1
{
    public static void main(String[] args)
    {
        Thread a=new threadA();
        a.start();
        Thread b=new threadB();
        b.start();
    }
}
class threadA extends Thread
{   @Override
    public void run()
    {
        for (int i=0;i<100;i++)
        {
            System.out.println(i);
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
        }
    }
class threadB extends Thread
{   @Override
public void run()
{
    for (char j='a';j<'z';j++)
    {
        System.out.println(j);
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
}

運行結(jié)果:

  • 由運行結(jié)果可以看到儒溉,使用了多線程(該例子使用了兩個線程)之后宦焦,控制臺處的數(shù)字和字母是同時打印出來的。

實現(xiàn)Runnable接口

  • 到目前為止顿涣,線程都是通過繼承Thread類來創(chuàng)建的波闹,如果需要繼承其他類,而且還要使當前類實現(xiàn)多線程涛碑,那么可以通過Runnable接口來實現(xiàn)精堕。例如,一個擴展JFrame類的GUI程序不可能再繼承Thread類蒲障,因為JAVA語言不支持多繼承歹篓,這時候就需要Runnable接口使其具有使用多線程的功能。


    實現(xiàn)Runnable接口創(chuàng)建線程
  • 創(chuàng)建一個繼承JFrame類的類揉阎,實現(xiàn)圖標移動的功能庄撮,其中使用了Swing與線程結(jié)合的技術(shù):
package lianxi1;
import java.awt.*;
import java.net.*;
import javax.swing.*;
public class SwingAndThread extends JFrame {
    private static final long serialVersionUID = 1L;
    private JLabel jl = new JLabel(); // 聲明JLabel對象
    private static Thread t; // 聲明線程對象
    private int count = 0; // 聲明計數(shù)變量
    private Container container = getContentPane(); // 聲明容器

    public SwingAndThread() {
        setBounds(300, 200, 250, 100); // 絕對定位窗體大小與位置
        container.setLayout(null); // 使窗體不使用任何布局管理器
        URL url = SwingAndThread.class.getResource("/1.gif"); // 獲取圖片的URL
        Icon icon = new ImageIcon(url); // 實例化一個Icon
        jl.setIcon(icon); // 將圖標放置在標簽中
        // 設(shè)置圖片在標簽的最左方
        jl.setHorizontalAlignment(SwingConstants.LEFT);
        jl.setBounds(10, 10, 200, 50); // 設(shè)置標簽的位置與大小
        jl.setOpaque(true);
        t = new Thread(new Runnable() { // 定義匿名內(nèi)部類,該類實現(xiàn)Runnable接口
            public void run() { // 重寫run()方法
                while (count <= 200) { // 設(shè)置循環(huán)條件
                    // 將標簽的橫坐標用變量表示
                    jl.setBounds(count, 10, 200, 50);
                    try {
                        Thread.sleep(1000); // 使線程休眠1000毫秒
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    count += 4; // 使橫坐標每次增加4
                    if (count == 200) {
                        // 當圖標到達標簽的最右邊毙籽,使其回到標簽最左邊
                        count = 10;
                    }
                }
            }
        });
        t.start(); // 啟動線程
        container.add(jl); // 將標簽添加到容器中
        setVisible(true); // 使窗體可視
        // 設(shè)置窗體的關(guān)閉方式
        setDefaultCloseOperation(WindowConstants.DISPOSE_ON_CLOSE);
    }
    public static void main(String[] args) {
        new SwingAndThread(); // 實例化一個SwingAndThread對象
    }
}

運行結(jié)果:

  • 在本實例中洞斯,為了使圖標具有滾動功能,需要在類的構(gòu)造方法中創(chuàng)建Thread實例坑赡。在創(chuàng)建該類的同時需要Runnable對象作為Thread類構(gòu)造方法的參數(shù)烙如,然后使用內(nèi)部類形式實現(xiàn)run()方法。在run()方法中主要循環(huán)圖標的橫坐標位置毅否,當圖標橫坐標到達標簽最右邊時亚铁,再次將圖標的橫坐標置于滾動的初始位置。

3. 線程的生命周期

  • 線程具有生命周期搀突,其中包含了七種狀態(tài)刀闷、分別為出生狀態(tài)、就緒狀態(tài)仰迁、運行狀態(tài)甸昏、等待狀態(tài)、休眠狀態(tài)徐许、阻塞狀態(tài)和死亡狀態(tài)施蜜。出生狀態(tài)就是線程被創(chuàng)建時處于的狀態(tài),在用戶使用該線程實例調(diào)用start()方法之前線程都處于出生狀態(tài)雌隅;當用戶調(diào)用start()方法后翻默,線程就處于就緒狀態(tài)缸沃;當線程得到系統(tǒng)資源后就進入運行狀態(tài)。一旦線程進入可執(zhí)行狀態(tài)修械,它就會在就緒與運行狀態(tài)下轉(zhuǎn)換趾牧,同時也有可能進入等待、休眠肯污、阻塞或死亡狀態(tài)翘单。


    線程的生命周期

4. 操作線程的方法

線程的休眠

  • 一種能控制線程行為的方法是調(diào)用sleep()方法,sleep()方法需要一個參數(shù)用于指定該線程休眠的時間蹦渣,該時間以毫秒為單位哄芜。實際上線程的休眠的作用相當于暫停。
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
  • 由于sleep()方法的執(zhí)行有可能拋出InterruptedException異常柬唯,所以將sleep()方法放在try-catch塊中认臊。

線程的加入

  • 如果當前某程序為多線程程序,假設(shè)存在一個線程A锄奢,現(xiàn)在需要插入線程B失晴,并要求線程B先執(zhí)行完畢,然后再繼續(xù)執(zhí)行線程A斟薇,此時可以使用Thread類中的join()方法來完成师坎。
  • 創(chuàng)建一個繼承JFrame類的類恕酸,該實例包括兩個進度條堪滨,進度條的進度由線程來控制,通過使用join()方法使上面的進度條必須等待下面的進度條完成后才可以繼續(xù)蕊温。
package lianxi1;
import java.awt.*;
import javax.swing.*;
public class JoinTest extends JFrame {
    private static final long serialVersionUID = 1L;
    private Thread threadA; // 定義兩個線程
    private Thread threadB;
    final JProgressBar progressBar = new JProgressBar(); // 定義兩個進度條組件
    final JProgressBar progressBar2 = new JProgressBar();
    int count = 0;
    public static void main(String[] args) {
        init(new JoinTest(), 100, 100);
    }
    public JoinTest() {
        super();
        // 將進度條設(shè)置在窗體最北面
        getContentPane().add(progressBar, BorderLayout.NORTH);
        // 將進度條設(shè)置在窗體最南面
        getContentPane().add(progressBar2, BorderLayout.SOUTH);
        progressBar.setStringPainted(true); // 設(shè)置進度條顯示數(shù)字字符
        progressBar2.setStringPainted(true);
        // 使用匿名內(nèi)部類形式初始化Thread實例子
        threadA = new Thread(new Runnable() {
            int count = 0;
            public void run() { // 重寫run()方法
                while (true) {
                    progressBar.setValue(++count); // 設(shè)置進度條的當前值
                    try {
                        Thread.sleep(100); // 使線程A休眠100毫秒
                        threadB.join(); // 使線程B調(diào)用join()方法
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        });
        threadA.start(); // 啟動線程A
        threadB = new Thread(new Runnable() {
            int count = 0;

            public void run() {
                while (true) {
                    progressBar2.setValue(++count); // 設(shè)置進度條的當前值
                    try {
                        Thread.sleep(100); // 使線程B休眠100毫秒
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    if (count == 100) // 當count變量增長為100時
                        break; // 跳出循環(huán)
                }
            }
        });
        threadB.start(); // 啟動線程B
    }
    // 設(shè)置窗體各種屬性方法
    public static void init(JFrame frame, int width, int height) {
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(width, height);
        frame.setVisible(true);
    }
}

運行結(jié)果:

  • 這個例子創(chuàng)建了兩個線程袱箱,這兩個線程分別負責進度條的滾動。在線程A的run()方法中使線程B的對象調(diào)用join()方法义矛,而join()方法使當前運行線程暫停发笔,直到調(diào)用join()方法的線程執(zhí)行完畢后再執(zhí)行,所以線程A等待線程B執(zhí)行完畢后再開始執(zhí)行凉翻,即下面的進度條滾動完畢后上面的進度條才開始滾動了讨。

線程的中斷

  • 使用Thread類的interrupt()方法使線程離開run()方法,同時結(jié)束線程制轰,但是線程會拋出InterruputedException異常前计,用戶可以在處理異常時完成線程的中斷業(yè)務(wù)處理。
package lianxi1;
import java.awt.*;
import javax.swing.*;
public class InterruptedSwing extends JFrame {
    private static final long serialVersionUID = 1L;
    Thread thread;
    public static void main(String[] args) {
        init(new InterruptedSwing(), 100, 100);
    }
    public InterruptedSwing() {
        super();
        final JProgressBar progressBar = new JProgressBar(); // 創(chuàng)建進度條
        // 將進度條放置在窗體合適位置
        getContentPane().add(progressBar, BorderLayout.NORTH);
        progressBar.setStringPainted(true); // 設(shè)置進度條上顯示數(shù)字
        thread = new Thread(new Runnable() {
            int count = 0;
            public void run() {
                while (true) {
                    progressBar.setValue(++count); // 設(shè)置進度條的當前值
                    try {
                        Thread.sleep(1000); // 使線程休眠1000豪秒
                        // 捕捉InterruptedException異常
                    } catch (InterruptedException e) {
                        System.out.println("當前線程序被中斷");
                        break;
                    }
                }
            }
        });
        thread.start(); // 啟動線程
        thread.interrupt(); // 中斷線程
    }

    public static void init(JFrame frame, int width, int height) {
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.setSize(width, height);
        frame.setVisible(true);
    }

}

運行結(jié)果:

5. 線程同步

  • 在單線程程序中垃杖,每次只能做一件事男杈,后面的事情需要等待前面的事情完成之后才可以進行,但是如果使用多線程调俘,就會發(fā)生兩個線程搶占資源的問題伶棒,甚至在某些情況會產(chǎn)生臟數(shù)據(jù)旺垒。所以在多線程編程過程中需要防止這些資源訪問的沖突肤无。JAVA提供了線程同步的機制來防止資源訪問的沖突先蒋。

線程安全

  • 如果線程不同步的話就會造成線程的不安全宛渐,例如火車票售賣系統(tǒng)鞭达,如果造成線程的不安全,則可能會出現(xiàn)車票數(shù)為負數(shù)的情況:
package lianxi1;
public class ThreadSafeTest implements Runnable {
    int num = 10; // 設(shè)置當前總票數(shù)

    public void run() {
        while (true) {
            if (num > 0) {
                try {
                    Thread.sleep(100);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                System.out.println("剩余票數(shù)" + num--);
            }
        }
    }

    public static void main(String[] args) {
        ThreadSafeTest t = new ThreadSafeTest(); // 實例化類對象
        Thread tA = new Thread(t); // 以該類對象分別實例化4個線程
        Thread tB = new Thread(t);
        Thread tC = new Thread(t);
        Thread tD = new Thread(t);
        tA.start(); // 分別啟動線程
        tB.start();
        tC.start();
        tD.start();
    }
}

運行結(jié)果:
線程同步之前的結(jié)果

  • 可以看到剩余車票數(shù)出現(xiàn)了負數(shù)的情況皇忿,這就出現(xiàn)了線程不安全的情況畴蹭。

線程同步機制

  • 在JAVA中提供了同步機制,可以有效地防止資源沖突鳍烁。同步機制用synchronized關(guān)鍵字叨襟。
package lianxi1;
public class ThreadSafeTest implements Runnable {
    int num = 10;

    public void run() {
        while (true) {
            synchronized ("") {
                if (num > 0) {
                    try {
                        Thread.sleep(1000);
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                    System.out.println("剩余票數(shù)" + --num);
                }
            }
        }
    }

    public static void main(String[] args) {
        ThreadSafeTest t = new ThreadSafeTest();
        Thread tA = new Thread(t);
        Thread tB = new Thread(t);
        Thread tC = new Thread(t);
        Thread tD = new Thread(t);
        tA.start();
        tB.start();
        tC.start();
        tD.start();
    }
}

運行結(jié)果:
線程同步之后的結(jié)果

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市幔荒,隨后出現(xiàn)的幾起案子糊闽,更是在濱河造成了極大的恐慌,老刑警劉巖爹梁,帶你破解...
    沈念sama閱讀 217,542評論 6 504
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件右犹,死亡現(xiàn)場離奇詭異,居然都是意外死亡姚垃,警方通過查閱死者的電腦和手機念链,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,822評論 3 394
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來积糯,“玉大人掂墓,你說我怎么就攤上這事】闯桑” “怎么了君编?”我有些...
    開封第一講書人閱讀 163,912評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長川慌。 經(jīng)常有香客問我吃嘿,道長,這世上最難降的妖魔是什么梦重? 我笑而不...
    開封第一講書人閱讀 58,449評論 1 293
  • 正文 為了忘掉前任兑燥,我火速辦了婚禮,結(jié)果婚禮上忍饰,老公的妹妹穿的比我還像新娘贪嫂。我一直安慰自己,他們只是感情好艾蓝,可當我...
    茶點故事閱讀 67,500評論 6 392
  • 文/花漫 我一把揭開白布力崇。 她就那樣靜靜地躺著斗塘,像睡著了一般。 火紅的嫁衣襯著肌膚如雪亮靴。 梳的紋絲不亂的頭發(fā)上馍盟,一...
    開封第一講書人閱讀 51,370評論 1 302
  • 那天,我揣著相機與錄音茧吊,去河邊找鬼贞岭。 笑死,一個胖子當著我的面吹牛搓侄,可吹牛的內(nèi)容都是我干的瞄桨。 我是一名探鬼主播,決...
    沈念sama閱讀 40,193評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼讶踪,長吁一口氣:“原來是場噩夢啊……” “哼芯侥!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起乳讥,我...
    開封第一講書人閱讀 39,074評論 0 276
  • 序言:老撾萬榮一對情侶失蹤柱查,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后云石,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體唉工,經(jīng)...
    沈念sama閱讀 45,505評論 1 314
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,722評論 3 335
  • 正文 我和宋清朗相戀三年汹忠,在試婚紗的時候發(fā)現(xiàn)自己被綠了淋硝。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,841評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡错维,死狀恐怖奖地,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情赋焕,我是刑警寧澤,帶...
    沈念sama閱讀 35,569評論 5 345
  • 正文 年R本政府宣布仰楚,位于F島的核電站隆判,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏僧界。R本人自食惡果不足惜侨嘀,卻給世界環(huán)境...
    茶點故事閱讀 41,168評論 3 328
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望捂襟。 院中可真熱鬧咬腕,春花似錦、人聲如沸葬荷。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,783評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至举反,卻和暖如春懊直,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背火鼻。 一陣腳步聲響...
    開封第一講書人閱讀 32,918評論 1 269
  • 我被黑心中介騙來泰國打工室囊, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人魁索。 一個月前我還...
    沈念sama閱讀 47,962評論 2 370
  • 正文 我出身青樓融撞,卻偏偏與公主長得像,于是被迫代替她去往敵國和親粗蔚。 傳聞我的和親對象是個殘疾皇子懦铺,可洞房花燭夜當晚...
    茶點故事閱讀 44,781評論 2 354

推薦閱讀更多精彩內(nèi)容

  • Java多線程學(xué)習(xí) [-] 一擴展javalangThread類 二實現(xiàn)javalangRunnable接口 三T...
    影馳閱讀 2,957評論 1 18
  • 本文主要講了java中多線程的使用方法、線程同步支鸡、線程數(shù)據(jù)傳遞冬念、線程狀態(tài)及相應(yīng)的一些線程函數(shù)用法、概述等牧挣。 首先講...
    李欣陽閱讀 2,454評論 1 15
  • 整理來自互聯(lián)網(wǎng) 1急前,JDK:Java Development Kit,java的開發(fā)和運行環(huán)境瀑构,java的開發(fā)工具...
    Ncompass閱讀 1,538評論 0 6
  • 一:java概述: 1裆针,JDK:Java Development Kit,java的開發(fā)和運行環(huán)境寺晌,java的開發(fā)...
    慕容小偉閱讀 1,788評論 0 10
  • 一:java概述:1世吨,JDK:Java Development Kit,java的開發(fā)和運行環(huán)境呻征,java的開發(fā)工...
    ZaneInTheSun閱讀 2,650評論 0 11