本次我將經過兩篇文章進行分享。第二篇傳送門JAVA併發不會?怎麼辦,看這裏(二)html
有三種使用線程的方法:java
接口的類只能當作一個能夠在線程中運行的任務,不是真正意義上的線程,所以最後還須要經過 Thread 來調用。能夠理解爲任務是經過線程驅動從而執行的。git
須要實現接口中的 run() 方法。程序員
public class MyRunnable implements Runnable {
@Override
public void run() {
// ...
}
}
複製代碼
使用 Runnable 實例再建立一個 Thread 實例,而後調用 Thread 實例的 start() 方法來啓動線程。github
public static void main(String[] args) {
MyRunnable instance = new MyRunnable();
Thread thread = new Thread(instance);
thread.start();
}
複製代碼
與 Runnable 相比,Callable 能夠有返回值,返回值經過 FutureTask 進行封裝。bash
public class MyCallable implements Callable<Integer> {
public Integer call() {
return 123;
}
}
複製代碼
public static void main(String[] args) throws ExecutionException, InterruptedException {
MyCallable mc = new MyCallable();
FutureTask<Integer> ft = new FutureTask<>(mc);
Thread thread = new Thread(ft);
thread.start();
System.out.println(ft.get());
}
複製代碼
一樣也是須要實現 run() 方法,由於 Thread 類也實現了 Runable 接口。併發
當調用 start() 方法啓動一個線程時,虛擬機會將該線程放入就緒隊列中等待被調度,當一個線程被調度時會執行該線程的 run() 方法。oracle
public class MyThread extends Thread {
public void run() {
// ...
}
}
複製代碼
public static void main(String[] args) {
MyThread mt = new MyThread();
mt.start();
}
複製代碼
實現接口會更好一些,由於:框架
Executor 管理多個異步任務的執行,而無需程序員顯式地管理線程的生命週期。這裏的異步是指多個任務的執行互不干擾,不須要進行同步操做。異步
主要有三種 Executor:
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
for (int i = 0; i < 5; i++) {
executorService.execute(new MyRunnable());
}
executorService.shutdown();
}
複製代碼
守護線程是程序運行時在後臺提供服務的線程,不屬於程序中不可或缺的部分。
當全部非守護線程結束時,程序也就終止,同時會殺死全部守護線程。
main() 屬於非守護線程。
在線程啓動以前使用 setDaemon() 方法能夠將一個線程設置爲守護線程。
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.setDaemon(true);
}
複製代碼
Thread.sleep(millisec) 方法會休眠當前正在執行的線程,millisec 單位爲毫秒。
sleep() 可能會拋出 InterruptedException,由於異常不能跨線程傳播回 main() 中,所以必須在本地進行處理。線程中拋出的其它異常也一樣須要在本地進行處理。
public void run() {
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
複製代碼
對靜態方法 Thread.yield() 的調用聲明瞭當前線程已經完成了生命週期中最重要的部分,能夠切換給其它線程來執行。該方法只是對線程調度器的一個建議,並且也只是建議具備相同優先級的其它線程能夠運行。
public void run() {
Thread.yield();
}
``
`
## 中斷
一個線程執行完畢以後會自動結束,若是在運行過程當中發生異常也會提早結束。
### InterruptedException
經過調用一個線程的 interrupt() 來中斷該線程,若是該線程處於阻塞、限期等待或者無限期等待狀態,那麼就會拋出 InterruptedException,從而提早結束該線程。可是不能中斷 I/O 阻塞和 synchronized 鎖阻塞。
對於如下代碼,在 main() 中啓動一個線程以後再中斷它,因爲線程中調用了 Thread.sleep() 方法,所以會拋出一個 InterruptedException,從而提早結束線程,不執行以後的語句。
```java
public class InterruptExample {
private static class MyThread1 extends Thread {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("Thread run");
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
複製代碼
public static void main(String[] args) throws InterruptedException {
Thread thread1 = new MyThread1();
thread1.start();
thread1.interrupt();
System.out.println("Main run");
}
複製代碼
Main run
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at InterruptExample.lambda$main$0(InterruptExample.java:5)
at InterruptExample$$Lambda$1/713338599.run(Unknown Source)
at java.lang.Thread.run(Thread.java:745)
複製代碼
若是一個線程的 run() 方法執行一個無限循環,而且沒有執行 sleep() 等會拋出 InterruptedException 的操做,那麼調用線程的 interrupt() 方法就沒法使線程提早結束。
可是調用 interrupt() 方法會設置線程的中斷標記,此時調用 interrupted() 方法會返回 true。所以能夠在循環體中使用 interrupted() 方法來判斷線程是否處於中斷狀態,從而提早結束線程。
public class InterruptExample {
private static class MyThread2 extends Thread {
@Override
public void run() {
while (!interrupted()) {
// ..
}
System.out.println("Thread end");
}
}
}
複製代碼
public static void main(String[] args) throws InterruptedException {
Thread thread2 = new MyThread2();
thread2.start();
thread2.interrupt();
}
複製代碼
調用 Executor 的 shutdown() 方法會等待線程都執行完畢以後再關閉,可是若是調用的是 shutdownNow() 方法,則至關於調用每一個線程的 interrupt() 方法。
如下使用 Lambda 建立線程,至關於建立了一個匿名內部線程。
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> {
try {
Thread.sleep(2000);
System.out.println("Thread run");
} catch (InterruptedException e) {
e.printStackTrace();
}
});
executorService.shutdownNow();
System.out.println("Main run");
}
複製代碼
Main run
java.lang.InterruptedException: sleep interrupted
at java.lang.Thread.sleep(Native Method)
at ExecutorInterruptExample.lambda$main$0(ExecutorInterruptExample.java:9)
at ExecutorInterruptExample$$Lambda$1/1160460865.run(Unknown Source)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
複製代碼
若是隻想中斷 Executor 中的一個線程,能夠經過使用 submit() 方法來提交一個線程,它會返回一個 Future<?> 對象,經過調用該對象的 cancel(true) 方法就能夠中斷線程。
Future<?> future = executorService.submit(() -> {
// ..
});
future.cancel(true);
複製代碼
Java 提供了兩種鎖機制來控制多個線程對共享資源的互斥訪問,第一個是 JVM 實現的 synchronized,而另外一個是 JDK 實現的 ReentrantLock。
public void func() {
synchronized (this) {
// ...
}
}
複製代碼
它只做用於同一個對象,若是調用兩個對象上的同步代碼塊,就不會進行同步。
對於如下代碼,使用 ExecutorService 執行了兩個線程,因爲調用的是同一個對象的同步代碼塊,所以這兩個線程會進行同步,當一個線程進入同步語句塊時,另外一個線程就必須等待。
public class SynchronizedExample {
public void func1() {
synchronized (this) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}
}
}
複製代碼
public static void main(String[] args) {
SynchronizedExample e1 = new SynchronizedExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> e1.func1());
executorService.execute(() -> e1.func1());
}
複製代碼
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
複製代碼
對於如下代碼,兩個線程調用了不一樣對象的同步代碼塊,所以這兩個線程就不須要同步。從輸出結果能夠看出,兩個線程交叉執行。
public static void main(String[] args) {
SynchronizedExample e1 = new SynchronizedExample();
SynchronizedExample e2 = new SynchronizedExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> e1.func1());
executorService.execute(() -> e2.func1());
}
複製代碼
0 0 1 1 2 2 3 3 4 4 5 5 6 6 7 7 8 8 9 9
複製代碼
public synchronized void func () {
// ...
}
複製代碼
它和同步代碼塊同樣,做用於同一個對象。
public void func() {
synchronized (SynchronizedExample.class) {
// ...
}
}
複製代碼
做用於整個類,也就是說兩個線程調用同一個類的不一樣對象上的這種同步語句,也會進行同步。
public class SynchronizedExample {
public void func2() {
synchronized (SynchronizedExample.class) {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
}
}
}
複製代碼
public static void main(String[] args) {
SynchronizedExample e1 = new SynchronizedExample();
SynchronizedExample e2 = new SynchronizedExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> e1.func2());
executorService.execute(() -> e2.func2());
}
複製代碼
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
複製代碼
public synchronized static void fun() {
// ...
}
複製代碼
做用於整個類。
ReentrantLock 是 java.util.concurrent(J.U.C)包中的鎖。
public class LockExample {
private Lock lock = new ReentrantLock();
public void func() {
lock.lock();
try {
for (int i = 0; i < 10; i++) {
System.out.print(i + " ");
}
} finally {
lock.unlock(); // 確保釋放鎖,從而避免發生死鎖。
}
}
}
複製代碼
public static void main(String[] args) {
LockExample lockExample = new LockExample();
ExecutorService executorService = Executors.newCachedThreadPool();
executorService.execute(() -> lockExample.func());
executorService.execute(() -> lockExample.func());
}
複製代碼
0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9
複製代碼
synchronized 是 JVM 實現的,而 ReentrantLock 是 JDK 實現的。
新版本 Java 對 synchronized 進行了不少優化,例如自旋鎖等,synchronized 與 ReentrantLock 大體相同。
當持有鎖的線程長期不釋放鎖的時候,正在等待的線程能夠選擇放棄等待,改成處理其餘事情。
ReentrantLock 可中斷,而 synchronized 不行。
公平鎖是指多個線程在等待同一個鎖時,必須按照申請鎖的時間順序來依次得到鎖。
synchronized 中的鎖是非公平的,ReentrantLock 默認狀況下也是非公平的,可是也能夠是公平的
一個 ReentrantLock 能夠同時綁定多個 Condition 對象。
除非須要使用 ReentrantLock 的高級功能,不然優先使用 synchronized。這是由於 synchronized 是 JVM 實現的一種鎖機制,JVM 原生地支持它,而 ReentrantLock 不是全部的 JDK 版本都支持。而且使用 synchronized 不用擔憂沒有釋放鎖而致使死鎖問題,由於 JVM 會確保鎖的釋放。
當多個線程能夠一塊兒工做去解決某個問題時,若是某些部分必須在其它部分以前完成,那麼就須要對線程進行協調。
在線程中調用另外一個線程的 join() 方法,會將當前線程掛起,而不是忙等待,直到目標線程結束。
對於如下代碼,雖然 b 線程先啓動,可是由於在 b 線程中調用了 a 線程的 join() 方法,b 線程會等待 a 線程結束才繼續執行,所以最後可以保證 a 線程的輸出先於 b 線程的輸出。
public class JoinExample {
private class A extends Thread {
@Override
public void run() {
System.out.println("A");
}
}
private class B extends Thread {
private A a;
B(A a) {
this.a = a;
}
@Override
public void run() {
try {
a.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("B");
}
}
public void test() {
A a = new A();
B b = new B(a);
b.start();
a.start();
}
}
複製代碼
public static void main(String[] args) {
JoinExample example = new JoinExample();
example.test();
}
複製代碼
A
B
複製代碼
調用 wait() 使得線程等待某個條件知足,線程在等待時會被掛起,當其餘線程的運行使得這個條件知足時,其它線程會調用 notify() 或者 notifyAll() 來喚醒掛起的線程。
它們都屬於 Object 的一部分,而不屬於 Thread。
只能用在同步方法或者同步控制塊中使用,不然會在運行時拋出 IllegalMonitorStateException。
使用 wait() 掛起期間,線程會釋放鎖。這是由於,若是沒有釋放鎖,那麼其它線程就沒法進入對象的同步方法或者同步控制塊中,那麼就沒法執行 notify() 或者 notifyAll() 來喚醒掛起的線程,形成死鎖。
public class WaitNotifyExample {
public synchronized void before() {
System.out.println("before");
notifyAll();
}
public synchronized void after() {
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("after");
}
}
複製代碼
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
WaitNotifyExample example = new WaitNotifyExample();
executorService.execute(() -> example.after());
executorService.execute(() -> example.before());
}
複製代碼
before
after
複製代碼
wait() 和 sleep() 的區別
java.util.concurrent 類庫中提供了 Condition 類來實現線程之間的協調,能夠在 Condition 上調用 await() 方法使線程等待,其它線程調用 signal() 或 signalAll() 方法喚醒等待的線程。
相比於 wait() 這種等待方式,await() 能夠指定等待的條件,所以更加靈活。
使用 Lock 來獲取一個 Condition 對象。
public class AwaitSignalExample {
private Lock lock = new ReentrantLock();
private Condition condition = lock.newCondition();
public void before() {
lock.lock();
try {
System.out.println("before");
condition.signalAll();
} finally {
lock.unlock();
}
}
public void after() {
lock.lock();
try {
condition.await();
System.out.println("after");
} catch (InterruptedException e) {
e.printStackTrace();
} finally {
lock.unlock();
}
}
}
複製代碼
public static void main(String[] args) {
ExecutorService executorService = Executors.newCachedThreadPool();
AwaitSignalExample example = new AwaitSignalExample();
executorService.execute(() -> example.after());
executorService.execute(() -> example.before());
}
複製代碼
before
after
複製代碼
一個線程只能處於一種狀態,而且這裏的線程狀態特指 Java 虛擬機的線程狀態,不能反映線程在特定操做系統下的狀態。
建立後還沒有啓動。
正在 Java 虛擬機中運行。可是在操做系統層面,它可能處於運行狀態,也可能等待資源調度(例如處理器資源),資源調度完成就進入運行狀態。因此該狀態的可運行是指能夠被運行,具體有沒有運行要看底層操做系統的資源調度。
請求獲取 monitor lock 從而進入 synchronized 函數或者代碼塊,可是其它線程已經佔用了該 monitor lock,因此出於阻塞狀態。要結束該狀態進入從而 RUNABLE 須要其餘線程釋放 monitor lock。
等待其它線程顯式地喚醒。
阻塞和等待的區別在於,阻塞是被動的,它是在等待獲取 monitor lock。而等待是主動的,經過調用 Object.wait() 等方法進入。
無需等待其它線程顯式地喚醒,在必定時間以後會被系統自動喚醒。
調用 Thread.sleep() 方法使線程進入限期等待狀態時,經常用「使一個線程睡眠」進行描述。調用 Object.wait() 方法使線程進入限期等待或者無限期等待時,經常用「掛起一個線程」進行描述。睡眠和掛起是用來描述行爲,而阻塞和等待用來描述狀態。
能夠是線程結束任務以後本身結束,或者產生了異常而結束。
本文參考聊聊併發(八)——Fork/Join 框架介紹、線程通訊、Threads and Locks、Threads and Locks、CS-Notes、Java內存模型 之三個特性、輕量級鎖、thread state java
第二篇傳送門JAVA併發不會?怎麼辦,看這裏(二)