線程生命周期

NEW:創(chuàng)建了一個線程對象,但是還沒有調用start()方法。此時稱為初始狀態(tài)NEW
RUNNABLE:調用了start()方法后,進入就緒狀態(tài),此時已經進入run()方法
BLOCKED:如果run()方法中有用synchronized包含的代碼塊,則需要等其他線程釋放鎖對象才能進入synchronized代碼塊。
BLOCKED狀態(tài)是不能被interrupt打斷的,只有其他線程釋放鎖之后,才能回到RUNNABLE狀態(tài)。
WAITING:如果線程主動調用wait()或join()方法,則進入WAITING狀態(tài),
直到join()的線程返回;
或者wait()的對象執(zhí)行notify()或notifyAll()方法被喚醒;
或者其他線程調用interrupt()方法。
如果沒有其它線程喚醒,WATING狀態(tài)會無限期等待下去,形成死鎖。
TIMED_WAITING:如果線程主動調用sleep(long millis),wait(long millis),或join(long millis)方法,則進入TIMED_WAITING狀態(tài),
直到超時返回或join()的線程返回;
或者wait()的對象執(zhí)行notify()或notifyAll()方法被喚醒;
或者其他線程調用interrupt()方法。
TERMINATED:如果run()方法執(zhí)行完畢,或者線程異常退出,進入TERMINATED狀態(tài)。進入此狀態(tài)后不能再調用start()方法。
線程等待
- sleep()和wait()
sleep()沒有鎖的概念,wait()有。wait()方法會釋放線程本身持有的鎖。
sleep()方法會進入TIMED_WAITING,直到超時返回,跟鎖沒有關系,如果sleep()方法被synchronized代碼塊包含,線程本身持有的鎖不會釋放。
不帶參數的wait()方法會進入WAITING狀態(tài),無限期等待,直到滿足條件,或者interrupt()方法被調用。
帶參數的wait(long millis)方法會進入TIMED_WAITING狀態(tài),能實現(xiàn)超時返回。 - sleep()和join()
sleep()和join()都沒有鎖的概念,只是等待返回的條件不一樣。
不帶參數的join()方法會進入WAITING狀態(tài),無限期等待,直到該線程返回,或者interrupt()方法被調用。
帶參數的join(long millis)方法會進入TIMED_WAITING狀態(tài),能實現(xiàn)超時返回。 - sleep()和yield()
sleep()和yield()都沒有鎖的概念。
yield()方法不會改變線程的狀態(tài),只是讓出CPU使用權。有可能下一次CPU會立即執(zhí)行。
sleep()方法會讓出CPU使用權,直到超時返回。 - sleep()和suspend()
suspend()方法不會改變線程狀態(tài),依然是RUNNABLE,但是被暫停了不會執(zhí)行,只有通過resume()方法能夠喚醒。suspend()和resume()已經不推薦使用了。
線程安全
多線程并發(fā)可能會遇到的問題是同樣的輸入,每次的輸出不一樣,需要了解Java內存模型(JMM)的幾個概念:
- 原子性:操作中途是不能被其他線程打斷的;
- 可見性:一個線程對共享變量的修改,其他線程是立即可見的;
-
有序性:在多核處理器的環(huán)境下,代碼的執(zhí)行順序是沒保障的,需要其他條件來保證。
在Java內存模型中,允許編譯器和處理器對指令進行重排序。重排序過程不會影響到單線程程序的執(zhí)行,卻會影響到多線程并發(fā)執(zhí)行的正確性。
內存模型.jpg
為了實現(xiàn)操作的原子性,可見性和有序性,通常會用到鎖。
Java提供了synchronized關鍵字來實現(xiàn)內部鎖。被 synchronized 關鍵字修飾的方法和代碼塊叫同步方法和同步代碼塊。
- synchronized 方法
鎖的對象是每個類實例,只有兩個線程對同一個類實例的synchronized方法進行訪問才會競爭鎖資源,分別對兩個實例的同一個synchronized方法訪問是不影響的。
例如下面這個例子:
當兩個線程分別去訪問同個對象的兩個synchronized方法時,就可能出現(xiàn)時序問題。
如果count()方法先執(zhí)行,兩個線程都能夠正常運行。
如果waitCount()方法先執(zhí)行,就會出現(xiàn)死鎖,waitCount線程進入死循環(huán),一直在TIMED_WAITING狀態(tài),count線程沒有獲取到對象鎖,一直在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內部最好不要對共享變量使用一些while判斷,防止出現(xiàn)死循環(huán)。
- synchronized代碼塊
synchronized代碼塊跟synchronized方法是類似的,只不過可以指定鎖的對象,使用比較靈活,可以把盡可能少的操作放進同步代碼,避免其他線程等待。
例如上面的例子,只需要把count()的同步方法改成同步代碼塊,就能解決死循環(huá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,實現(xiàn)了Lock接口的四個方法:
lock()和lock.unlock()方法類似synchronized,等待鎖的過程中線程阻塞,只不過synchronized是進入BLOCKED狀態(tài),lock()是進入WAITING狀態(tài)。
lockInterruptibly()可以被中斷,需要catch InterruptedException異常。
tryLock()成功獲取鎖返回true,否則false,不會阻塞。
tryLock(long time, TimeUnit unit),阻塞等待一段時間,然后返回。
此外還有讀寫鎖,這里不介紹,可以看下ReadWriteLock這個類。 - volatile
一般變量保存在高速緩存區(qū),不會立刻同步到主內存,導致不同線程間的數據不同步,使用volatile關鍵字就是為了保證數據的可見性。
下面寫了一個簡單例子,兩個線程thread1和thread2會競爭lock對象鎖
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");
}
這兩個線程的狀態(tài)如下

如果代碼中的join()方法沒有加超時,就會形成死鎖。
參考:
https://www.cnblogs.com/dolphin0520/p/3920373.html
https://www.cnblogs.com/trust-freedom/p/6606594.html
