初識(shí)Java多線程

第一章 Java多線程技能

1)線程的啟動(dòng)晶渠;
2)如何使線程暫停搔预;
3)線程的優(yōu)先級(jí)资锰;
4)線程安全相關(guān)的問題

1.1 進(jìn)程和多線程的概念及多線程的優(yōu)點(diǎn)

進(jìn)程:受操作系統(tǒng)管理的基本運(yùn)行單元首昔。

線程:進(jìn)程中獨(dú)立運(yùn)行的子任務(wù)寡喝。

多線程的優(yōu)點(diǎn):
在圖1-1-1中,任務(wù)1與任務(wù)2是兩個(gè)完全獨(dú)立勒奇,互不相關(guān)的任務(wù)预鬓,任務(wù)1在等待遠(yuǎn)程服務(wù)器返回?cái)?shù)據(jù),這時(shí)CPU一直“空運(yùn)行”赊颠,必須要等到任務(wù)1執(zhí)行完畢后才執(zhí)行任務(wù)2格二。以上概括了單任務(wù)的特點(diǎn):排隊(duì)執(zhí)行,也就是同步巨税。這就是單任務(wù)環(huán)境的缺點(diǎn)蟋定,CPU利用率大幅降低。
在圖1-1-2可以發(fā)現(xiàn)草添,CPU完全可以再任務(wù)1與任務(wù)2之間來回切換驶兜,使任務(wù)2不必等待任務(wù)1執(zhí)行完畢再進(jìn)行,系統(tǒng)的運(yùn)行效率大大提升远寸。

image

1.2 使用多線程

實(shí)現(xiàn)多線程的的方式有兩種浪讳,一種是繼承Thread類,另一種是實(shí)現(xiàn)Runnable接口腻窒。

1.2.1 繼承Thread類

public class Thread implements Runnable{}

使用繼承Thread類的方式來創(chuàng)建多線程時(shí)懊烤,最大的局限性就是不支持多繼承(Java語言的特點(diǎn)就是單根繼承)。下面使用第一種方式灶芝,繼承Thread類郑原。

public class MyThread extends Thread {
    @Override
    public void run() {
        super.run();
        System.out.println("MyThread");
    }
}
public class Run{
    public static void main(String[] args) {
        MyThread myThread =new MyThread();
        myThread.start();
        System.out.println("運(yùn)行結(jié)束");
    }
}
運(yùn)行結(jié)束
MyThread

從執(zhí)行結(jié)果來看唉韭,MyThread類中run方法執(zhí)行的時(shí)間較晚,這也說明在使用多線程技術(shù)時(shí)犯犁,代碼的運(yùn)行結(jié)果與代碼的執(zhí)行順序或調(diào)用順序是無關(guān)的属愤。線程是一個(gè)子任務(wù),CPU以不確定的方式酸役,或者說是以隨機(jī)的時(shí)間來調(diào)用線程中的run方法住诸,所以就會(huì)出現(xiàn)如上結(jié)果。 如果多次調(diào)用start()涣澡,則會(huì)出現(xiàn) Exception in thread "main" java.lang.IllegalThreadStateException贱呐。

上面介紹了線程調(diào)用的隨機(jī)性,下面我們?cè)傺菔揪€程的隨機(jī)性入桂,如下奄薇。

public class MyThread extends Thread{
    @Override
    public void run() {
        try {
            for (int i = 0; i < 5; i++) {
                int time =(int) (Math.random() * 1000);
                Thread.sleep(time);
                System.out.println("run=" + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run{
    public static void main(String[] args) {
        try {
            MyThread myThread =new MyThread();
            myThread.setName("MyThread");
            myThread.start();
            for (int i = 0; i < 5; i++) {
                int time =(int) (Math.random() * 1000);
                Thread.sleep(time);
                System.out.println("main=" + Thread.currentThread().getName());
            }
        } catch (InterruptedException e) {
            e.fillInStackTrace();
        }
    }
}

run=MyThread
main=main
main=main
run=MyThread
run=MyThread
main=main
run=MyThread
run=MyThread
main=main
main=main

繼承Thread,重寫run方法事格,調(diào)用start方法執(zhí)行線程的流程:調(diào)用Thread類中的start方法通知線程規(guī)劃器此線程已經(jīng)準(zhǔn)備就緒惕艳,等待調(diào)用線程對(duì)象的run方法。這個(gè)過程其實(shí)就是讓系統(tǒng)安排一個(gè)時(shí)間來調(diào)用Thread中的run方法驹愚,使線程得到運(yùn)行远搪,線程為異步執(zhí)行;如果直接調(diào)用Thread.run()逢捺,那么此線程對(duì)象并不交給“線程規(guī)劃器”來處理谁鳍,而是由main主線程來調(diào)用run(),也就是同步執(zhí)行劫瞳。

1.2.2 實(shí)現(xiàn)Runnable接口

如果預(yù)創(chuàng)建的線程類已經(jīng)有一個(gè)父類了倘潜,這時(shí)候就不能再繼承Thread了,因?yàn)镴ava不支持多繼承志于,所以就需要實(shí)現(xiàn)Runnbale接口來應(yīng)對(duì)這種情況涮因。 如何使用Runnable接口呢,那么就要看一下Thread的構(gòu)造函數(shù)了伺绽。

public Thread(){}
public Thread(Runnable target){}
public Thread(ThreadGroup group, Runnable target){}
public Thread(String name){}
public Thread(ThreadGroup group, String name){}
public Thread(Runnable target, String name){}
public Thread(ThreadGroup group, Runnable target, String name){}
public Thread(ThreadGroup group, Runnable target, String name, long stackSize){}

在Thread類的8個(gè)構(gòu)造函數(shù)中养泡,有兩個(gè)個(gè)構(gòu)造函數(shù)

Thread(Runnable target)
Thread(Runnable target, String name)
public class MyRunnable implements Runnable{
    @Override
    public void run() {
        System.out.println("running");
    }
}
public class RunMyRunnable{
    public static void main(String[] args) {
        Thread thread  =new Thread(new MyRunnable());
        System.out.println(thread.getName());
        thread.start();
        Thread thread2  =new Thread(new MyRunnable(),"customize name");
        System.out.println(thread2.getName());
        thread2.start();
        System.out.println("運(yùn)行結(jié)束");
    }
}

Thread-0
customize name
運(yùn)行結(jié)束
running
running

1.2.3 實(shí)例變量與線程安全

自定義線程類中的實(shí)例變量針對(duì)其他線程有共享不共享之分,這在多線程之間進(jìn)行交互時(shí)是很重要的一個(gè)技術(shù)點(diǎn)奈应。

1.2.3.1 不共享數(shù)據(jù)的情況

public class MyThread extends Thread{
    private int count =5;
    public MyThread(String name) {
        this.setName(name);
    }
    @Override
    public void run() {
        super.run();
        while (count >0) {
            count--;
            System.out.println("由" +this.currentThread().getName() +"計(jì)算澜掩,count=" +count);
        }
    }
}
public class Run{
    public static void main(String[] args) {
        MyThread myThreadA =new MyThread("myThreadA");
        MyThread myThreadB =new MyThread("myThreadB");
        MyThread myThreadC =new MyThread("myThreadC");
        myThreadA.start();
        myThreadB.start();
        myThreadC.start();
    }
}
由myThreadA計(jì)算,count=4
由myThreadC計(jì)算杖挣,count=4
由myThreadC計(jì)算肩榕,count=3
由myThreadC計(jì)算,count=2
由myThreadC計(jì)算惩妇,count=1
由myThreadC計(jì)算株汉,count=0
由myThreadA計(jì)算筐乳,count=3
由myThreadA計(jì)算,count=2
由myThreadA計(jì)算郎逃,count=1
由myThreadA計(jì)算哥童,count=0
由myThreadB計(jì)算挺份,count=4
由myThreadB計(jì)算褒翰,count=3
由myThreadB計(jì)算,count=2
由myThreadB計(jì)算匀泊,count=1
由myThreadB計(jì)算优训,count=0

1.2.3.2 共享數(shù)據(jù)的情況

共享數(shù)據(jù)是指多個(gè)線程同時(shí)訪問同一個(gè)變量。

public class MyThread extends Thread{
    private int count =5;
    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("由" +this.currentThread().getName() +"計(jì)算各聘,count=" +count);
    }
}
public class Run{
    public static void main(String[] args) {
        MyThread myThreadA =new MyThread();
        Thread a =new Thread(myThreadA,"A");
        Thread b =new Thread(myThreadA,"B");
        Thread c =new Thread(myThreadA,"C");
        Thread d =new Thread(myThreadA,"D");
        Thread e =new Thread(myThreadA,"E");
        a.start();
        b.start();
        c.start();
        d.start();
        e.start();
    }
}
由C計(jì)算揣非,count=3
由B計(jì)算,count=3
由A計(jì)算躲因,count=2
由E計(jì)算早敬,count=1
由D計(jì)算,count=0

線程C和B打印出的count值都是3大脉,說明A和B同時(shí)獲取到count并進(jìn)行--的操作搞监,產(chǎn)生了非線程安全問題。count--的操作分成三步:
1)取得原有count值镰矿;
2)計(jì)算count-1琐驴;
3)對(duì)count賦值。
如果多個(gè)線程同時(shí)訪問秤标,一定會(huì)出現(xiàn)非線程安全問題绝淡。

那么如何解決上面的線程安全問題呢?這時(shí)就需要使多個(gè)線程之間進(jìn)行同步苍姜。更改MyThread類代碼如下:

public class MyThread extends Thread{
    private int count =5;
    @Override
    synchronized public void run() {
        super.run();
        count--;
        System.out.println("由" +this.currentThread().getName() +"計(jì)算牢酵,count=" +count);
    }
}
由A計(jì)算,count=4
由E計(jì)算衙猪,count=3
由B計(jì)算馍乙,count=2
由D計(jì)算,count=1
由C計(jì)算屈嗤,count=0

運(yùn)行之后潘拨,執(zhí)行結(jié)果與預(yù)期一致。通過在run方法前加入synchronized饶号,使多個(gè)線程在執(zhí)行run方法時(shí)铁追,以排隊(duì)的方式進(jìn)行處理。當(dāng)一個(gè)線程調(diào)用run前茫船,先判斷run方法有沒有被上鎖琅束,如果上鎖扭屁,說明有其他線程正在調(diào)用run方法,必須等其他線程對(duì)run方法調(diào)用結(jié)束之后才可以執(zhí)行涩禀。這樣也就實(shí)現(xiàn)了排隊(duì)調(diào)用run方法的目的料滥。synchronized可以在任意對(duì)象及方法上加鎖,而加鎖的這段代碼成為互斥區(qū)臨界區(qū)艾船。

當(dāng)一個(gè)線程想要執(zhí)行同步方法里面的代碼時(shí)葵腹,線程首先嘗試去拿這把鎖,如果能拿到這把鎖屿岂,那么這個(gè)線程就可以執(zhí)行synchronized里面的代碼践宴。如果不能獲取這把鎖,那么這個(gè)線程就會(huì)不斷地嘗試拿這把鎖爷怀,直到拿到為止阻肩。

非線程安全主要是指多個(gè)線程對(duì)同一對(duì)象中的同一實(shí)例變量進(jìn)行操作時(shí),會(huì)出現(xiàn)值被更改运授,值不同步的情況烤惊,進(jìn)而影響程序的執(zhí)行流程。

下面來實(shí)現(xiàn)一個(gè)非線程安全導(dǎo)致的后果吁朦。

public class LoginServlet{
    private static StringuserNameRef;
    private static StringpasswordRef;
    public static void login(String userName, String password) {
        userNameRef = userName;
        try {
            if (userName.equals("a")) {
                Thread.sleep(5000);
            }
            passwordRef = password;
            System.out.println("當(dāng)前的用戶名:" +userNameRef +" 密碼:" +passwordRef);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

public class ALogin extends Thread{
    @Override
    public void run() {
        LoginServlet.login("a", "aa");
    }
}
public class BLogin extends Thread{
    @Override
    public void run() {
        LoginServlet.login("b", "bb");
    }
}
public class DoLogin{
    public static void main(String[] args) {
        ALogin a =new ALogin();
        a.start();
        BLogin b =new BLogin();
        b.start();
    }
}
當(dāng)前的用戶名:b 密碼:bb
當(dāng)前的用戶名:b 密碼:aa

解決這個(gè)非線程安全的方法也是使用synchronized柒室,更改代碼如下:

public class LoginServlet{
    private static StringuserNameRef;
    private static StringpasswordRef;
    synchronized public static void login(String userName, String password) {
        userNameRef = userName;
        try {
            if (userName.equals("a")) {
                Thread.sleep(5000);
            }
            passwordRef = password;
            System.out.println("當(dāng)前的用戶名:" +userNameRef +" 密碼:" +passwordRef);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
當(dāng)前的用戶名:a 密碼:aa
當(dāng)前的用戶名:b 密碼:bb

1.3 currentThread()

currentThread方法可返回代碼段正在被哪個(gè)線程調(diào)用的信息。

1.4 isAlive()

判斷當(dāng)前線程是否處于活動(dòng)狀態(tài)喇完÷啄啵活動(dòng)狀態(tài)就是線程已經(jīng)啟動(dòng)且尚未終止,線程處于正在運(yùn)行或準(zhǔn)備開始運(yùn)行的狀態(tài)锦溪,就認(rèn)為線程是“存活的”不脯。

1.5 sleep()

在指定得毫秒數(shù)內(nèi)讓當(dāng)前“正在執(zhí)行的線程”休眠(暫停執(zhí)行)。這個(gè)“正在執(zhí)行的線程”是指this.currentThread()返回的線程刻诊。

1.6 getId()

getId()方法的作用是取得線程的唯一標(biāo)識(shí)防楷。

1.7 停止線程

停止一個(gè)線程意味著在線程處理完任務(wù)之前停掉正在做的操作。雖然看起來很簡單则涯,但是必須做好防范措施复局,以便達(dá)到預(yù)期效果。
在Java中有三種方法可以終止正在運(yùn)行的線程:
1)使用退出標(biāo)志粟判,使線程正常退出亿昏,也就是當(dāng)run方法完成后線程終止。
2)使用stop方法(已經(jīng)被作廢)強(qiáng)行終止線程档礁。但是不推薦使用此方法角钩,因?yàn)?code>stop和suspedresume一樣都是作廢過期的方法。
3)使用interrupt方法中斷線程递礼。

1.7.1 停止不了的線程

interrupt方法僅是在當(dāng)前線程中打了一個(gè)停止的標(biāo)記惨险,并不是真的停止線程。

public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        for (int i =0; i <500000; i++) {
            System.out.println("i=" + i);
        }
    }
}
public class Run{
    public static void main(String[] args) {
        MyThread thread =new MyThread();
        thread.start();
        thread.interrupt();
    }
}

從運(yùn)行結(jié)果來看脊髓,調(diào)用interrupt方法并沒有停止線程辫愉。

1.7.2 判斷線程是否是停止?fàn)顟B(tài)

1)this.interrupted():測(cè)試當(dāng)前線程是否已經(jīng)中斷,執(zhí)行后具有將狀態(tài)標(biāo)志清除為false的功能将硝。
2)this.isInterrupted():測(cè)試當(dāng)前線程是否已經(jīng)中斷恭朗,不清除狀態(tài)標(biāo)志。

1.7.3 能停止的線程-異常法

public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        for (int i =0; i <500000; i++) {
            if (this.isInterrupted()) {
                System.out.println("停止?fàn)顟B(tài) 退出");
                break;
            }
            System.out.println("i=" + i);
        }
    }
}
public class Run{
    public static void main(String[] args) {
        try {
            MyThread thread =new MyThread();
            thread.start();
            Thread.sleep(2000);
            thread.interrupt();
        } catch (Exception e) {
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end");
    }
}
i=408980
i=408981
i=408982
i=408983
i=408984
end
停止?fàn)顟B(tài) 退出

上面示例代碼雖然停止了線程但如果for下面還有語句袋哼,還是會(huì)運(yùn)行的冀墨,該如何解決語句繼續(xù)運(yùn)行的問題呢 ?

public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        try {
            for (int i =0; i <500000; i++) {
                if (this.isInterrupted()) {
                    System.out.println("停止?fàn)顟B(tài) 退出");
                    throw new InterruptedException();
                }
                System.out.println("i=" + i);
            }
            System.out.println("for之后運(yùn)行涛贯,線程并未停止");
        } catch (Exception e) {
            System.out.println("MyThread catch");
            e.printStackTrace();
        }
    }
}
public class Run{
    public static void main(String[] args) {
        try {
            MyThread thread =new MyThread();
            thread.start();
            Thread.sleep(2000);
            thread.interrupt();
        } catch (Exception e) {
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end");
    }
}
i=410919
i=410920
i=410921
end
停止?fàn)顟B(tài) 退出
MyThread catch
java.lang.InterruptedException

1.7.4 在沉睡中停止

如果線程在sleep狀態(tài)下停止線程,會(huì)是什么效果呢蔚出?

public class MyThread extends Thread{
    @Override
    public void run() {
        super.run();
        try {
            System.out.println("run begin");
            Thread.sleep(20000);
            System.out.println("run end");
        } catch (InterruptedException e) {
            System.out.println("在沉睡中被停止弟翘!進(jìn)入catch!" +this.isInterrupted());
            e.printStackTrace();
        }
    }
}
public class Run{
    public static void main(String[] args) {
        try {
            MyThread thread =new MyThread();
            thread.start();
            Thread.sleep(200);
            thread.interrupt();
        } catch (Exception e) {
            System.out.println("main catch");
            e.printStackTrace();
        }
        System.out.println("end");
    }
}
run begin
end
在沉睡中被停止!進(jìn)入catch!false
java.lang.InterruptedException: sleep interrupted

sleep狀態(tài)下骄酗,停止某一線程稀余,會(huì)進(jìn)入catch語句,并清除停止?fàn)顟B(tài)值趋翻,使之變成false睛琳。

1.7.5 能停止的線程-暴力停止

使用stop方法停止線程則是非常暴力的。

public class MyThread extends Thread{
    private int i =0;
    @Override
    public void run() {
        try {
            while (true) {
                i++;
                System.out.println("i=" +i);
                Thread.sleep(1000);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}
public class Run{
    public static void main(String[] args) {
        try {
            MyThread thread =new MyThread();
            thread.start();
            Thread.sleep(8000);
            thread.stop();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
i=1
i=2
i=3
i=4
i=5
i=6
i=7
i=8
i=9

1.7.6 stop()與java.lang.ThreadDeath異常

調(diào)用stop()時(shí)會(huì)拋出java.lang.ThreadDeath異常踏烙。該方法已經(jīng)被作廢师骗,因?yàn)槿绻麖?qiáng)制讓線程停止則有可能使一些清理性的工作得不到完成。另外一個(gè)情況就是對(duì)鎖定的對(duì)象進(jìn)行了解鎖讨惩,導(dǎo)致數(shù)據(jù)得不到同步的處理辟癌,出現(xiàn)數(shù)據(jù)不一致的問題。

1.7.7 釋放鎖的不良后果

使用stop()釋放鎖將會(huì)給數(shù)據(jù)造成不一致性的結(jié)果荐捻。如果出現(xiàn)這樣的情況黍少,程序處理的數(shù)據(jù)就有可能遭到破壞,最終導(dǎo)致程序執(zhí)行流程錯(cuò)誤处面,一定要特別注意厂置。

public class SynchronizedObject{
    private String userName ="a";
    private String password ="a";
    public String get UserName() {
        return userName;
    }
    public void setUserName(String userName) {
        this.userName = userName;
    }
    public String getPassword() {
        return password;
    }
    public void setPassword(String password) {
        this.password = password;
    }
    public void printString(String userName, String password) {
        try {
            this.userName = userName;
            Thread.sleep(100000);
            this.password = password;
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class MyThread extends Thread{
    private SynchronizedObject syncObject;
    public MyThread(SynchronizedObject syncObject) {
        this.syncObject = syncObject;
    }
    @Override
    public void run() {
        syncObject.printString("b", "bbb");
    }
}
public class Run{
    public static void main(String[] args) {
        try {
            SynchronizedObject syncObject =new SynchronizedObject();
            MyThread thread =new MyThread(syncObject);
            thread.start();
            Thread.sleep(8000);
            thread.stop();
            System.out.println(syncObject.getUserName() +"  " +syncObject.getPassword());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
b a

1.7.8 使用return停止線程

interruptreturn結(jié)合使用也能實(shí)現(xiàn)停止線程的效果。

public class MyThread extends Thread{
    @Override
    public void run() {
        while (true) {
            if (this.isInterrupted()) {
                System.out.println("stop");
                return;
            }
            System.out.println("current time=" + System.currentTimeMillis());
        }
    }
}
public class Run{
    public static void main(String[] args) {
        try {
            MyThread thread =new MyThread();
            thread.start();
            Thread.sleep(8000);
            thread.interrupt();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
current time=1587436071260
current time=1587436071260
stop

不過還是建議使用拋異常的方法來實(shí)現(xiàn)線程的停止魂角,因?yàn)樵赾atch塊中還可以講一場(chǎng)向上拋昵济,是線程停止的時(shí)間得以傳播。

1.8 暫停線程

暫停線程意味著此線程還可以恢復(fù)運(yùn)行,在Java多線程中砸紊,可以使用suspend()暫停線程传于,使用resume()恢復(fù)線程的執(zhí)行。

1.8.1 suispend與resume方法的使用

public class MyThread extends Thread{
    private long i =0;
    public long getI() {
        return i;   
    }
    public void setI(long i) {
        this.i = i;
    }
    @Override
    public void run() {
        while (true) {
            i++;
        }
    }
}
public class Run{
    public static void main(String[] args) {
        try {
            MyThread thread =new MyThread();
            thread.start();
            Thread.sleep(5000);
            // 暫停線程
            thread.suspend();
            System.out.println("A=" + System.currentTimeMillis() +" i=" + thread.getI());
            Thread.sleep(5000);
            System.out.println("A=" + System.currentTimeMillis() +" i=" + thread.getI());
            // 恢復(fù)
            thread.resume();
            Thread.sleep(5000);
            // 繼續(xù)暫停
            thread.suspend();
            System.out.println("B=" + System.currentTimeMillis() +" i=" + thread.getI());
            Thread.sleep(5000);
            System.out.println("B=" + System.currentTimeMillis() +" i=" + thread.getI());
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

A=1587436765560 i=2885201074
A=1587436770560 i=2885201074
B=1587436775560 i=5820785533
B=1587436780560 i=5820785533

1.8.2 suspend與resume方法的缺點(diǎn)-獨(dú)占

在使用suspendresume方法時(shí)醉顽,如果使用不當(dāng)沼溜,極易造成公共的同步對(duì)象的獨(dú)占,是其他線程無法訪問公共同步對(duì)象游添。

public class SynchronizedObject{
    synchronized public void printString() {
        System.out.println("begin");
        if (Thread.currentThread().getName().equals("a")) {
            System.out.println("a線程一直suspend···");
            Thread.currentThread().suspend();
        }
        System.out.println("end");
    }
}
public class Run{
    public static void main(String[] args) {
        try {
            final SynchronizedObject syncObject =new SynchronizedObject();
            Thread thread =new Thread(new Runnable(){
                @Override
                public void run() {
                    syncObject.printString();
                }
            });
            thread.setName("a");
            thread.start();
            Thread.sleep(1000);
            Thread thread2 =new Thread(new Runnable(){
                @Override
                public void run() {
                    System.out.println("thread2 begin");
                    syncObject.printString();
                }
            });
            thread2.start();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
begin
a線程一直suspend···
thread2 begin

還有另外一種獨(dú)占鎖的情況也要格外注意系草,稍有不慎就會(huì)進(jìn)坑。

public class MyThread extends Thread{
    private long i =0;
    public long getI() {
        return i;
    }
    public void setI(long i) {
        this.i = i;
    }
    @Override
    public void run() {
        while (true) {
            i++;
            System.out.println(i);
        }
    }
}
public class Run{
    public static void main(String[] args) {
        try {
            MyThread myThread =new MyThread();
            myThread.start();
            Thread.sleep(1000);
            myThread.suspend();
            System.out.println("main end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}

程序不會(huì)再打印“main end”唆涝,因?yàn)楫?dāng)程序運(yùn)行到println()方法停止時(shí)找都,同步鎖未釋放。println()源碼如下:

public void println(String x) {
    synchronized (this) {
        print(x);
        newLine();
    }
}

1.8.3 suspend與resume方法的缺點(diǎn)-不同步

在使用suspendresume方法是容易出現(xiàn)因線程暫停而導(dǎo)致數(shù)據(jù)不同步的情況廊酣。

public class MyObject{
    private String userName ="1";
    private String password ="11";
    public void setValue(String userName, String password) {
        this.userName = userName;
        if ("a".equals(Thread.currentThread().getName())) {
            System.out.println("線程a暫停");
            Thread.currentThread().suspend();
        }
        this.password = password;
    }

    public void print() {
        System.out.println(userName +"  " +password);
    }
}
public class Run{
    public static void main(String[] args) throws InterruptedException{
        final MyObject myObject =new MyObject();
        Thread thread =new Thread(new Runnable(){
            @Override
            public void run() {
                myObject.setValue("lala", "lalala");
            }
        });
        thread.setName("a");
        thread.start();
        Thread.sleep(1000);
        new Thread(new Runnable(){
            @Override
            public void run() {
                myObject.print();
            }
        }).start();
    }
}
線程a暫停
lala  11

1.9 yield方法

yield方法的作用是放棄當(dāng)前的CPU資源能耻,將它讓給其他任務(wù)去占用CPU執(zhí)行時(shí)間。單放棄的時(shí)間不確定亡驰,有可能剛剛放棄晓猛,馬上又獲得CPU時(shí)間片。

public class MyThread extends Thread{
    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        int count =0;
        for (int i =0; i <50000000; i++) {
            // Thread.yield();
            count = count + i +1;
        }
        long endTime = System.currentTimeMillis();
        System.out.println("用時(shí):" +(endTime - beginTime) +"毫秒凡辱!");
    }
}
public class Run{
    public static void main(String[] args)  {
        MyThread myThread =new MyThread();
        myThread.start();
    }
}
用時(shí):19毫秒戒职!

取消Thread.yield();注釋,再次運(yùn)行結(jié)果如下所示:

用時(shí):11723毫秒透乾!

1.10 線程的優(yōu)先級(jí)

操作系統(tǒng)中洪燥,線程和一劃分優(yōu)先級(jí),優(yōu)先級(jí)較高的線程得到的CPU資源較多乳乌,也就是CPU有限制性優(yōu)先級(jí)較高的線程對(duì)象中的任務(wù)捧韵。設(shè)置線程優(yōu)先級(jí)有助于幫線程規(guī)劃器確定下一次選擇哪一個(gè)線程來優(yōu)先執(zhí)行。設(shè)置線程優(yōu)先級(jí)使用setPriority()钦扭,源碼如下:

public final void setPriority(int newPriority) {
    ThreadGroup g;
    checkAccess();
    if (newPriority >MAX_PRIORITY || newPriority
        throw new IllegalArgumentException();
    }
    if((g = getThreadGroup()) !=null) {
        if (newPriority > g.getMaxPriority()) {
            newPriority = g.getMaxPriority();
        }
        setPriority0(priority = newPriority);
    }
}

在Java中纫版,線程的優(yōu)先級(jí)分為1至10,這十個(gè)等級(jí)客情,如果小于或大于10其弊,則拋出異常 throw new IllegalArgumentException()

JDK中使用三個(gè)常量來預(yù)定義優(yōu)先級(jí)的值膀斋,代碼如下:

/**
* The minimum priority that a thread can have.
*/
public final static int MIN_PRIORITY =1;
/**
* The default priority that is assigned to a thread.
*/
public final static int NORM_PRIORITY =5;
/**
* The maximum priority that a thread can have.
*/
public final static int MAX_PRIORITY =10;

1.10.1 線程優(yōu)先級(jí)的繼承特性

在Java中梭伐,線程的優(yōu)先級(jí)具有繼承性,比如A線程啟動(dòng)B線程仰担,則B線程的優(yōu)先級(jí)與A是一樣的糊识。


public class MyThread2 extends Thread{
    @Override
    public void run() {
        System.out.println("myThread2 run priority="+this.getPriority());
    }
}
public class MyThread1 extends Thread{
    @Override
    public void run() {
        System.out.println("myThread1 run priority="+this.getPriority());
        MyThread2 thread2 =new MyThread2();
        thread2.start();
    }
}
public class Run{
    public static void main(String[] args)  {
        System.out.println("main thread begin priority="+Thread.currentThread().getPriority());
        Thread.currentThread().setPriority(6);
        System.out.println("main thread end priority="+Thread.currentThread().getPriority());
        MyThread1 myThread1 =new MyThread1();
        myThread1.start();
    }
}
main thread begin priority=5
main thread end priority=6
myThread1 run priority=6
myThread2 run priority=6

1.10.2 優(yōu)先級(jí)具有規(guī)則性

public class MyThread1 extends Thread{
    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        for (int i =0; i <100; i++) {
            Random random =new Random();
            random.nextInt();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("★★★★★ thread1 use time=" +(endTime - beginTime));
    }
}
public class MyThread2 extends Thread{
    @Override
    public void run() {
        long beginTime = System.currentTimeMillis();
        for (int i =0; i <100; i++) {
            Random random =new Random();
            random.nextInt();
        }
        long endTime = System.currentTimeMillis();
        System.out.println("☆☆☆☆☆ thread2 use time=" +(endTime - beginTime));
    }
}
public class Run{
    public static void main(String[] args) {
        for (int i =0; i <5; i++) {
            MyThread1 thread1 =new MyThread1();
            thread1.start();
            MyThread2 thread2 =new MyThread2();
            thread2.setPriority(6);
            thread2.start();
        }
    }
}

第一次執(zhí)行結(jié)果:

☆☆☆☆☆ thread2 use time=0
★★★★★ thread1 use time=0
★★★★★ thread1 use time=0
☆☆☆☆☆ thread2 use time=0
☆☆☆☆☆ thread2 use time=0
★★★★★ thread1 use time=0
☆☆☆☆☆ thread2 use time=0
★★★★★ thread1 use time=4
★★★★★ thread1 use time=3
☆☆☆☆☆ thread2 use time=4

第二次執(zhí)行結(jié)果:

★★★★★ thread1 use time=0
★★★★★ thread1 use time=0
★★★★★ thread1 use time=1
★★★★★ thread1 use time=0
☆☆☆☆☆ thread2 use time=0
★★★★★ thread1 use time=2
☆☆☆☆☆ thread2 use time=1
☆☆☆☆☆ thread2 use time=0
☆☆☆☆☆ thread2 use time=0
☆☆☆☆☆ thread2 use time=0

根據(jù)以上時(shí)長可以得出,不要把線程的優(yōu)先級(jí)與運(yùn)行結(jié)果的順序作為衡量的標(biāo)準(zhǔn),優(yōu)先級(jí)較高的線程不一定每次都先執(zhí)行完run()方法中的任務(wù)赂苗。他們的關(guān)系具有不確定性隨機(jī)性愉耙。

1.11 守護(hù)線程

Java中有兩種線程,一種是用戶線程拌滋,一種是守護(hù)線程朴沿。
當(dāng)進(jìn)程中不存在費(fèi)守護(hù)線程了,則守護(hù)線程自動(dòng)銷毀败砂。典型的守護(hù)線程就是GC(垃圾回收器)赌渣。任何一個(gè)守護(hù)線程都是整個(gè)JVM中所有費(fèi)守護(hù)線程的“保姆”,只要當(dāng)前JVM實(shí)例中存在任何一個(gè)非守護(hù)線程沒有結(jié)束昌犹,守護(hù)線程就在工作坚芜,當(dāng)最后一個(gè)非守護(hù)線程結(jié)束時(shí),守護(hù)線程才隨著JVM一同結(jié)束工作斜姥。

public class MyThread extends Thread{
    private long i =0;
    @Override
    public void run() {
        try {
            while (true) {
                i++;
                System.out.println("i=" +i);
                Thread.sleep(1000);
            }
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
public class Run{
    public static void main(String[] args) {
        try {
            MyThread myThread =new MyThread();
            myThread.setDaemon(true);
            myThread.start();
            Thread.sleep(5000);
            System.out.println("main thread end, myThread end");
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
    }
}
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末鸿竖,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子疾渴,更是在濱河造成了極大的恐慌千贯,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,640評(píng)論 6 507
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搞坝,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡魁袜,警方通過查閱死者的電腦和手機(jī)桩撮,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,254評(píng)論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來峰弹,“玉大人店量,你說我怎么就攤上這事【铣剩” “怎么了融师?”我有些...
    開封第一講書人閱讀 165,011評(píng)論 0 355
  • 文/不壞的土叔 我叫張陵,是天一觀的道長蚁吝。 經(jīng)常有香客問我旱爆,道長,這世上最難降的妖魔是什么窘茁? 我笑而不...
    開封第一講書人閱讀 58,755評(píng)論 1 294
  • 正文 為了忘掉前任怀伦,我火速辦了婚禮,結(jié)果婚禮上山林,老公的妹妹穿的比我還像新娘房待。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,774評(píng)論 6 392
  • 文/花漫 我一把揭開白布桑孩。 她就那樣靜靜地躺著拜鹤,像睡著了一般。 火紅的嫁衣襯著肌膚如雪流椒。 梳的紋絲不亂的頭發(fā)上敏簿,一...
    開封第一講書人閱讀 51,610評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音镣隶,去河邊找鬼极谊。 笑死,一個(gè)胖子當(dāng)著我的面吹牛安岂,可吹牛的內(nèi)容都是我干的轻猖。 我是一名探鬼主播,決...
    沈念sama閱讀 40,352評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼域那,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼咙边!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起次员,我...
    開封第一講書人閱讀 39,257評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤败许,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后淑蔚,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體市殷,經(jīng)...
    沈念sama閱讀 45,717評(píng)論 1 315
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,894評(píng)論 3 336
  • 正文 我和宋清朗相戀三年刹衫,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了醋寝。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,021評(píng)論 1 350
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡带迟,死狀恐怖音羞,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情仓犬,我是刑警寧澤嗅绰,帶...
    沈念sama閱讀 35,735評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站搀继,受9級(jí)特大地震影響窘面,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜律歼,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,354評(píng)論 3 330
  • 文/蒙蒙 一民镜、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧险毁,春花似錦制圈、人聲如沸们童。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,936評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽慧库。三九已至,卻和暖如春馋嗜,著一層夾襖步出監(jiān)牢的瞬間碾篡,已是汗流浹背厢蒜。 一陣腳步聲響...
    開封第一講書人閱讀 33,054評(píng)論 1 270
  • 我被黑心中介騙來泰國打工仲翎, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留逞泄,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,224評(píng)論 3 371
  • 正文 我出身青樓眯停,卻偏偏與公主長得像济舆,于是被迫代替她去往敵國和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子莺债,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,974評(píng)論 2 355

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