個人網站:https://chenmingyu.top/concurrent-thread/
進程與線程
進程:操作系統在運行一個程序的時候就會為其創建一個進程(比如一個java程序),進程是資源分配的最小單位,一個進程包含多個線程
線程:線程是cpu調度的最小單位,每個線程擁有各自的計數器,對戰和局部變量等屬性,並且能過訪問共享的內存變量
線程的狀態
java線程的生命周期總共包括6個階段:
- 初始狀態:線程被創建,但是還沒有調用
start()
方法 - 運行狀態:java中將就緒狀態和運行狀態統稱為運行狀態
- 阻塞狀態:線程阻塞,線程等待進入
synchronized
修飾的代碼塊或方法 - 等待狀態:線程進入等待狀態,需要調用
notify()
或notifyAll()
進行喚醒 - 超時等待狀態:線程進入等待狀態,在指定時間后自行返回
- 終止狀態:線程執行完畢
在某一時刻,線程只能處於其中的一個狀態
線程初始化后,調用start()
方法變為運行狀態,調用wait()
,join()
等方法,線程由運行狀態變為等待狀態,調用notify()
或notifyAll()
等方法,線程由等待狀態變成運行狀態,超時等待狀態就是在等待狀態基礎上加了時間限制,超過規定時間,自動更改為運行狀態,當需要執行同步方法時,如果沒有獲得鎖,這時線程狀態就變為阻塞狀態,直到獲取到鎖,變為運行狀態,當執行完線程的run()
方法后,線程變為終止狀態
創建線程
創建線程有三種方式
- 繼承
Thread
類 - 實現
Runnable
接口 - 實現
Callable
接口
繼承Thread
類
/**
* @author: chenmingyu
* @date: 2019/4/8 15:13
* @description: 繼承Thread類
*/
public class ThreadTest extends Thread{
@Override
public void run() {
IntStream.range(0,10).forEach(i->{
System.out.println(this.getName()+":"+i);
});
}
public static void main(String[] args) {
Thread thread = new ThreadTest();
thread.start();
}
}
實現Runnable
接口
/**
* @author: chenmingyu
* @date: 2019/4/8 15:18
* @description: 實現Runnable接口
*/
public class RunnableTest implements Runnable {
@Override
public void run() {
IntStream.range(0,10).forEach(i->{
System.out.println(Thread.currentThread().getName()+":"+i);
});
}
public static void main(String[] args) {
Runnable runnable = new RunnableTest();
new Thread(runnable,"RunnableTest").start();
}
}
實現Callable
接口
/**
* @author: chenmingyu
* @date: 2019/4/8 15:23
* @description: 實現Callable接口
*/
public class CallableTest implements Callable<Integer> {
@Override
public Integer call() throws Exception {
IntStream.range(0,10).forEach(i->{
System.out.println(Thread.currentThread().getName()+":"+i);
});
return -1;
}
public static void main(String[] args) throws Exception {
Callable callable = new CallableTest();
FutureTask futureTask = new FutureTask(callable);
new Thread(futureTask,"future").start();
System.out.println("result:"+futureTask.get());
}
}
線程間通信
不安全的線程暫停,恢復,停止操作
Thread
提供的過期方法可以實現對線程進行暫停suspend()
,恢復resume()
,停止stop()
的操作
例:創建一個線程,run()
中循環輸出當前時間,在main()
方法中對新建線程進行暫停,恢復,停止的操作
/**
* @author: chenmingyu
* @date: 2019/4/8 15:51
* @description: 線程的暫停,恢復,停止
*/
public class OperationThread implements Runnable{
@Override
public void run() {
while (true){
try {
TimeUnit.SECONDS.sleep(1L);
System.out.println(Thread.currentThread().getName()+"運行中:"+LocalTime.now());
}catch (InterruptedException e){
System.err.println(e.getMessage());
}
}
}
public static void main(String[] args) throws Exception{
Runnable runnable = new OperationThread();
Thread thread = new Thread(runnable,"operationThread");
/**
* 啟動,輸出當前時間
*/
thread.start();
TimeUnit.SECONDS.sleep(3L);
/**
* 線程暫停,不在輸出當前時間
*/
System.out.println("此處暫停:"+LocalTime.now());
thread.suspend();
TimeUnit.SECONDS.sleep(3L);
/**
* 線程恢復,繼續輸出當前時間
*/
System.out.println("此處恢復:"+LocalTime.now());
thread.resume();
TimeUnit.SECONDS.sleep(3L);
/**
* 線程停止,不在輸出當前時間
*/
thread.stop();
System.out.println("此處停止:"+LocalTime.now());
TimeUnit.SECONDS.sleep(3L);
}
}
輸出
因為是過期方法,所以不推薦使用,使用suspend()
方法后,線程不會釋放已經占有的資源,就進入睡眠狀態,容易引發死鎖問題,而使用stop()
方法終結一個線程是不會保證線程的資源正常釋放的,可能會導致程序異常
安全的線程暫停,恢復(等待/通知機制)
線程安全的暫停,恢復操作可以使用等待/通知機制代替
安全的線程暫停,恢復(等待/通知機制)
相關方法:
方法名 | 描述 |
---|---|
notify() | 通知一個在對象上等待的線程,使其重wait()方法中返回,前提是該線程獲得了對象的鎖 |
notifyAll() | 通知所有等待在該對象上的線程 |
wait() | 調用該方法線程進入等待狀態,只有等待另外線程的通知或被中斷才會返回,調用該方法會釋放對象的鎖 |
wait(long) | 超時等待一段時間(毫秒),如果超過時間就返回 |
wait(long,int) | 對於超時時間耕細粒度的控制,可以達到納秒 |
例:創建一個名為waitThread
的線程,在run()
方法,使用中使用synchronized
進行加鎖,以變量flag
為條件進行while
循環,在循環中調用LOCK.wait()
方法,此時會釋放對象鎖,由main()
方法獲得鎖,調用LOCK.notify()
方法通知LOCK
對象上等待的waitThread
線程,將其置為阻塞狀態,並將變量flag
置為true
,當waitThread
線程再次獲取對象鎖之后繼續執行余下代碼
/**
* @author: chenmingyu
* @date: 2019/4/8 20:00
* @description: wait/notify
*/
public class WaitNotifyTest {
private static Object LOCK = new Object();
private static Boolean FLAG = Boolean.TRUE;
public static void main(String[] args) throws InterruptedException{
Runnable r = new WaitThread();
new Thread(r,"waitThread").start();
TimeUnit.SECONDS.sleep(1L);
synchronized (LOCK){
System.out.println(Thread.currentThread().getName()+"喚醒waitThread線程:"+LocalTime.now());
/**
* 線程狀態由等待狀態變為阻塞狀態
*/
LOCK.notify();
/**
* 只有當前線程釋放對象鎖,waitThread獲取到LOCK對象的鎖之后才會從wait()方法中返回
*/
TimeUnit.SECONDS.sleep(2L);
FLAG = Boolean.FALSE;
}
}
public static class WaitThread implements Runnable {
@Override
public void run() {
/**
* 加鎖
*/
synchronized (LOCK){
while (FLAG){
try {
System.out.println(Thread.currentThread().getName()+"運行中:"+LocalTime.now());
/**
* 線程狀態變為等待狀態
*/
LOCK.wait();
/**
* 再次獲得對象鎖之后,才會執行
*/
System.out.println(Thread.currentThread().getName()+"被喚醒:"+LocalTime.now());
}catch (InterruptedException e){
System.err.println(e.getMessage());
}
}
}
System.out.println(Thread.currentThread().getName()+"即將停止:"+LocalTime.now());
}
}
}
輸出
可以看到在mian
線程調用LOCK.notify()
方法后,沉睡了2s才釋放對象鎖,waitThread
線程在獲得對象鎖之后執行余下代碼
安全的線程停止操作(中斷標識)
線程的安全停止操作是利用線程的中斷標識來實現,線程的中斷屬性表示一個運行中的線程是否被其他線程進行了中斷操作,其他線程通過調用該線程的interrupt()
方法對其進行中斷操作,而該線程通過檢查自身是否被中斷來進行響應,當一個線程被中斷可以使用Thread.interrupted()
方法對當前線程的中斷標識位進行復位
例:新建一個線程,run
方法中使用Thread.currentThread().isInterrupted()
是否中斷作為判斷條件,在主線程中使用thread.interrupt()
方法對子線程進行中斷操作,用來達到終止線程的操作,這種方式會讓子線程可以去清理資源或一些別的操作,而使用stop()
方法則會會直接終止線程
/**
* @author: chenmingyu
* @date: 2019/4/8 20:47
* @description: 中斷
*/
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
Runnable r = new StopThread();
Thread thread = new Thread(r,"stopThread");
thread.start();
TimeUnit.SECONDS.sleep(1L);
System.out.println(Thread.currentThread().getName()+"對stopThread線程進行中斷:"+LocalTime.now());
thread.interrupt();
}
public static class StopThread implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName()+"運行中:"+LocalTime.now());
}
System.out.println(Thread.currentThread().getName()+"停止:"+LocalTime.now());
}
}
}
Thread.join()
Thread.join()
作用是等待該線程終止
比如在主線程中新建一個子線程,調用子線程的join()
方法,那么在子線程未執行完時,主線程的狀態是阻塞狀態,只有當子線程執行結束,主線程才會繼續往下執行
方法名 | 作用 |
---|---|
join() | 調用A線程的join() 方法后,那么當前線程需要等待A線程終止,才可以繼續執行 |
join(long) | 在join() 方法的基礎上增加了時間限制(毫秒),超出時間后,無論A線程是否執行完,當前線程都進入就緒狀態,重新等待cpu調用 |
join(long,int) | 在join(long) 方法基礎上,時間控制上更加嚴謹,時間細粒度為納秒(Long毫秒+int納秒) |
例:循環創建子線程,在main
線程中調用子線程的join()
方法,在子線程中輸出了一句日志
/**
* @author: chenmingyu
* @date: 2019/4/9 20:53
* @description: thread.join();
*/
public class JoinThreadTest {
public static void main(String[] args) throws InterruptedException{
IntStream.range(0, 5).forEach(i -> {
try {
Runnable runnable = new JoinThread();
Thread thread = new Thread(runnable,"joinThread");
thread.start();
thread.join();
System.out.println(Thread.currentThread().getName() + "運行中: " + LocalTime.now());
} catch (InterruptedException e) {
System.err.println(e.getMessage());
}
System.out.println("------- 分隔符 ------- ");
});
}
public static class JoinThread implements Runnable {
@Override
public void run() {
try {
TimeUnit.SECONDS.sleep(1L);
System.out.println(Thread.currentThread().getName() + "運行中: " + LocalTime.now());
} catch (InterruptedException e) {
System.err.println(e.getMessage());
}
}
}
}
輸出
每次循環都是主線程等待子線程終止,在子線程執行完之后主線程才會繼續執行
thread.join()源碼
調用方線程(調用join方法的線程)執行等待操作,直到被調用的線程(join方法所屬的線程)結束,再被喚醒
public final void join() throws InterruptedException {
join(0);
}
join(0)
方法
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
join()
方法實現等待其實是調用了wait()
方法,isAlive()
方法的作用是監測子線程是否終止,如果終止或者超過了指定時間,代碼就繼續往下執行,否則就繼續等待,知道條件滿足
ThreadLocal
ThreadLocal
,叫線程變量,是一個以ThreadLocal
對象為鍵,任意對象為值的存儲結構,ThreadLocal
類型的變量在每個線程中是獨立的,在多線程環境下不會相互影響
/**
* @author: chenmingyu
* @date: 2019/4/10 17:56
* @description: ThreadLocal
*/
public class ThreadLocalTest {
private static String STATE;
private static ThreadLocal<String> STRING_THREAD_LOCAL = new InheritableThreadLocal<>();
public static void main(String[] args) throws InterruptedException{
STATE = "未重置";
STRING_THREAD_LOCAL.set("未重置");
Thread thread = new Thread(() ->
{
STATE = "已重置";
STRING_THREAD_LOCAL.set("已重置");
System.out.println(Thread.currentThread().getName() + " : 變量已重置");
});
thread.start();
thread.join();
System.out.println(Thread.currentThread().getName() + "STATE : " + STATE);
System.out.println(Thread.currentThread().getName() + "STRING_THREAD_LOCAL : " + STRING_THREAD_LOCAL.get());
}
}
輸出
ThreadLocal<String>
類型的變量STRING_THREAD_LOCAL
未被子線程修改
參考:java並發編程的藝術