線程生命周期
NEW:創(chuàng)建了一個(gè)線程對(duì)象,但是還沒(méi)有調(diào)用start()方法慨飘。此時(shí)稱為初始狀態(tài)NEW
RUNNABLE:調(diào)用了start()方法后某残,進(jìn)入就緒狀態(tài),此時(shí)已經(jīng)進(jìn)入run()方法
BLOCKED:如果run()方法中有用synchronized包含的代碼塊蹈丸,則需要等其他線程釋放鎖對(duì)象才能進(jìn)入synchronized代碼塊成黄。
BLOCKED狀態(tài)是不能被interrupt打斷的呐芥,只有其他線程釋放鎖之后儡遮,才能回到RUNNABLE狀態(tài)柬脸。
WAITING:如果線程主動(dòng)調(diào)用wait()或join()方法,則進(jìn)入WAITING狀態(tài)囚似,
直到j(luò)oin()的線程返回闻伶;
或者wait()的對(duì)象執(zhí)行notify()或notifyAll()方法被喚醒滨攻;
或者其他線程調(diào)用interrupt()方法。
如果沒(méi)有其它線程喚醒蓝翰,WATING狀態(tài)會(huì)無(wú)限期等待下去光绕,形成死鎖。
TIMED_WAITING:如果線程主動(dòng)調(diào)用sleep(long millis)畜份,wait(long millis)诞帐,或join(long millis)方法,則進(jìn)入TIMED_WAITING狀態(tài)爆雹,
直到超時(shí)返回或join()的線程返回停蕉;
或者wait()的對(duì)象執(zhí)行notify()或notifyAll()方法被喚醒;
或者其他線程調(diào)用interrupt()方法钙态。
TERMINATED:如果run()方法執(zhí)行完畢慧起,或者線程異常退出,進(jìn)入TERMINATED狀態(tài)册倒。進(jìn)入此狀態(tài)后不能再調(diào)用start()方法蚓挤。
線程等待
- sleep()和wait()
sleep()沒(méi)有鎖的概念,wait()有驻子。wait()方法會(huì)釋放線程本身持有的鎖屈尼。
sleep()方法會(huì)進(jìn)入TIMED_WAITING,直到超時(shí)返回拴孤,跟鎖沒(méi)有關(guān)系脾歧,如果sleep()方法被synchronized代碼塊包含,線程本身持有的鎖不會(huì)釋放演熟。
不帶參數(shù)的wait()方法會(huì)進(jìn)入WAITING狀態(tài)鞭执,無(wú)限期等待,直到滿足條件芒粹,或者interrupt()方法被調(diào)用兄纺。
帶參數(shù)的wait(long millis)方法會(huì)進(jìn)入TIMED_WAITING狀態(tài),能實(shí)現(xiàn)超時(shí)返回化漆。 - sleep()和join()
sleep()和join()都沒(méi)有鎖的概念估脆,只是等待返回的條件不一樣。
不帶參數(shù)的join()方法會(huì)進(jìn)入WAITING狀態(tài)座云,無(wú)限期等待疙赠,直到該線程返回付材,或者interrupt()方法被調(diào)用。
帶參數(shù)的join(long millis)方法會(huì)進(jìn)入TIMED_WAITING狀態(tài)圃阳,能實(shí)現(xiàn)超時(shí)返回厌衔。 - sleep()和yield()
sleep()和yield()都沒(méi)有鎖的概念。
yield()方法不會(huì)改變線程的狀態(tài)捍岳,只是讓出CPU使用權(quán)富寿。有可能下一次CPU會(huì)立即執(zhí)行。
sleep()方法會(huì)讓出CPU使用權(quán)锣夹,直到超時(shí)返回页徐。 - sleep()和suspend()
suspend()方法不會(huì)改變線程狀態(tài),依然是RUNNABLE银萍,但是被暫停了不會(huì)執(zhí)行泞坦,只有通過(guò)resume()方法能夠喚醒。suspend()和resume()已經(jīng)不推薦使用了砖顷。
線程安全
多線程并發(fā)可能會(huì)遇到的問(wèn)題是同樣的輸入,每次的輸出不一樣赃梧,需要了解Java內(nèi)存模型(JMM)的幾個(gè)概念:
- 原子性:操作中途是不能被其他線程打斷的滤蝠;
- 可見(jiàn)性:一個(gè)線程對(duì)共享變量的修改,其他線程是立即可見(jiàn)的授嘀;
-
有序性:在多核處理器的環(huán)境下物咳,代碼的執(zhí)行順序是沒(méi)保障的,需要其他條件來(lái)保證蹄皱。
在Java內(nèi)存模型中览闰,允許編譯器和處理器對(duì)指令進(jìn)行重排序。重排序過(guò)程不會(huì)影響到單線程程序的執(zhí)行巷折,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性压鉴。
為了實(shí)現(xiàn)操作的原子性,可見(jiàn)性和有序性锻拘,通常會(huì)用到鎖油吭。
Java提供了synchronized關(guān)鍵字來(lái)實(shí)現(xiàn)內(nèi)部鎖。被 synchronized 關(guān)鍵字修飾的方法和代碼塊叫同步方法和同步代碼塊署拟。
- synchronized 方法
鎖的對(duì)象是每個(gè)類實(shí)例婉宰,只有兩個(gè)線程對(duì)同一個(gè)類實(shí)例的synchronized方法進(jìn)行訪問(wèn)才會(huì)競(jìng)爭(zhēng)鎖資源,分別對(duì)兩個(gè)實(shí)例的同一個(gè)synchronized方法訪問(wèn)是不影響的推穷。
例如下面這個(gè)例子:
當(dāng)兩個(gè)線程分別去訪問(wèn)同個(gè)對(duì)象的兩個(gè)synchronized方法時(shí)心包,就可能出現(xiàn)時(shí)序問(wèn)題。
如果count()方法先執(zhí)行馒铃,兩個(gè)線程都能夠正常運(yùn)行蟹腾。
如果waitCount()方法先執(zhí)行痕惋,就會(huì)出現(xiàn)死鎖,waitCount線程進(jìn)入死循環(huán)岭佳,一直在TIMED_WAITING狀態(tài)血巍,count線程沒(méi)有獲取到對(duì)象鎖,一直在BLOCKED狀態(tài)珊随。
public class ThreadTest {
private volatile int count = 0;
public synchronized void count() {
for (int i = 0 ; i < 10; i++) {
count++;
}
System.out.println(Thread.currentThread().getName() + " count=" + count);
}
public synchronized void waitCount() {
System.out.println(Thread.currentThread().getName() + " wait start");
while (count < 10) {
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println(Thread.currentThread().getName() + " wait finish");
}
}
ThreadTest test = new ThreadTest();
Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
test.waitCount();
}
}, "thread1");
Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
test.count();
}
}, "thread2");
synchronized方法保證了操作的原子性述寡,所以synchronized內(nèi)部最好不要對(duì)共享變量使用一些while判斷,防止出現(xiàn)死循環(huán)叶洞。
- synchronized代碼塊
synchronized代碼塊跟synchronized方法是類似的鲫凶,只不過(guò)可以指定鎖的對(duì)象,使用比較靈活衩辟,可以把盡可能少的操作放進(jìn)同步代碼螟炫,避免其他線程等待。
例如上面的例子艺晴,只需要把count()的同步方法改成同步代碼塊昼钻,就能解決死循環(huán)的問(wèn)題。
public class ThreadTest {
private volatile int count = 0;
public void count() {
for (int i = 0 ; i < 10; i++) {
count++;
}
synchronized (this) {
System.out.println(Thread.currentThread().getName() + " count=" + count);
}
}
public synchronized void waitCount() {
System.out.println(Thread.currentThread().getName() + " wait start");
while (count < 10) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
break;
}
}
System.out.println(Thread.currentThread().getName() + " wait finish");
}
}
- 顯式鎖
常用的顯式鎖有ReentrantLock封寞,實(shí)現(xiàn)了Lock接口的四個(gè)方法:
lock()和lock.unlock()方法類似synchronized然评,等待鎖的過(guò)程中線程阻塞,只不過(guò)synchronized是進(jìn)入BLOCKED狀態(tài)狈究,lock()是進(jìn)入WAITING狀態(tài)碗淌。
lockInterruptibly()可以被中斷,需要catch InterruptedException異常抖锥。
tryLock()成功獲取鎖返回true亿眠,否則false,不會(huì)阻塞磅废。
tryLock(long time, TimeUnit unit)纳像,阻塞等待一段時(shí)間,然后返回拯勉。
此外還有讀寫(xiě)鎖爹耗,這里不介紹,可以看下ReadWriteLock這個(gè)類谜喊。 - volatile
一般變量保存在高速緩存區(qū)潭兽,不會(huì)立刻同步到主內(nèi)存,導(dǎo)致不同線程間的數(shù)據(jù)不同步斗遏,使用volatile關(guān)鍵字就是為了保證數(shù)據(jù)的可見(jiàn)性山卦。
下面寫(xiě)了一個(gè)簡(jiǎn)單例子,兩個(gè)線程thread1和thread2會(huì)競(jìng)爭(zhēng)lock對(duì)象鎖
package com.one.thread;
import java.util.Random;
import com.one.log.Log;
public class ThreadTest {
private volatile int count = 0;
private Log log = new Log("ThreadTest");
private Object lock = new Object();
public void run() {
thread1.start();
thread2.start();
}
private void sleepRandom() {
try {
Thread.currentThread().sleep(new Random().nextInt(1000));
} catch (InterruptedException e) {
log.log("sleep interrupt");
}
}
private Thread thread1 = new Thread(new Runnable() {
@Override
public void run() {
try {
thread2.join(1000);
} catch (InterruptedException e) {
log.log("thread1 joinInterrupt");
}
for (int i = 0; i < 10; i++) {
synchronized (lock) {
sleepRandom();
count++;
log.log("count=" + count + " thread2=" + thread2.getState());
lock.notifyAll();
}
}
}
}, "thread1");
private Thread thread2 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (lock) {
sleepRandom();
log.log("thread1=" + thread1.getState());
while (count < 6) {
try {
lock.wait();
sleepRandom();
log.log("waitting count=" + count+ " thread1=" + thread1.getState());
} catch (InterruptedException e) {
log.log("thread2 waitInterrupt");
break;
}
}
log.log("thread2 waitFinished");
}
}
}, "thread2");
}
這兩個(gè)線程的狀態(tài)如下
如果代碼中的join()方法沒(méi)有加超時(shí),就會(huì)形成死鎖账蓉。
參考:
https://www.cnblogs.com/dolphin0520/p/3920373.html
https://www.cnblogs.com/trust-freedom/p/6606594.html