第一章 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)行效率大大提升远寸。
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和susped
、resume
一樣都是作廢過期的方法。
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停止線程
interrupt
與return
結(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ú)占
在使用
suspend
與resume
方法時(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)-不同步
在使用
suspend
與resume
方法是容易出現(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();
}
}
}