線程的概念
線程不能獨(dú)立存在,資源由操作系統(tǒng)分配給進(jìn)程,但是CPU
資源是分配給線程的.同一個(gè)進(jìn)程內(nèi)的線程:
- 共享: 堆(主要存放使用
new
操作創(chuàng)建的對(duì)象實(shí)例)和方法區(qū)(JVM
加載的類,常量以及靜態(tài)變量等) - 獨(dú)立: 程序計(jì)數(shù)器(獲得
CPU
控制權(quán)后可以從計(jì)數(shù)器指定地址繼續(xù)執(zhí)行)和棧區(qū)域.
當(dāng)啟動(dòng)main
函數(shù)時(shí),就相當(dāng)于啟動(dòng)了一個(gè)JVM
的進(jìn)程,而main
所在的線程就是該進(jìn)程中的主線程.
如何創(chuàng)建線程
有三種方式:
- 繼承
Thread
類,重寫run
方法:
// Thread1.java
public class Thread1 extends Thread{
@Override
public void run() {
System.out.println("current-thread: "+this.getName());
System.out.println("繼承自Thread");
}
public static void main(String[] args) {
Thread1 t = new Thread1();
t.start();
}
}
不需要使用
currentThread
就可以獲取當(dāng)前線程,但是不能再繼承其他類
- 實(shí)現(xiàn)
Runnable
方法,并重寫run
方法:
//Thread2.java
public class Thread2 implements Runnable{
private String name;
public Thread2(String name) {
this.name = name;
}
@Override
public void run() {
System.out.println("this is "+ name + " running");
System.out.println("實(shí)現(xiàn)了Runnable接口");
}
public static void main(String[] args) {
Thread t1 = new Thread(new Thread2("t1"));
t1.start();
Thread t2 = new Thread(new Thread2("t2"));
t2.start();
}
}
可以繼承其他類,還可以添加參數(shù)區(qū)分
- 實(shí)現(xiàn)
Callable
接口的call
方法:
//Thread3.java
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
public class Thread3 implements Callable<String> {
@Override
public String call(){
System.out.println("使用FutureTask方式,可以有返回值");
return "done";
}
public static void main(String[] args) throws InterruptedException {
FutureTask<String> ft = new FutureTask<String>(new Thread3());
Thread t = new Thread(ft);
t.start();
try {
String result = ft.get();
System.out.println(result);
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
具有返回值,可以使用
get
方法獲得返回值.也可以繼承其他類.
當(dāng)調(diào)用Thread
的start
方法,線程只是進(jìn)入了就緒狀態(tài),只有當(dāng)cpu
資源得到滿足時(shí),才會(huì)真正開(kāi)始運(yùn)行.run
方法結(jié)束后,線程就處于終止?fàn)顟B(tài).
wait與notify
wait
與notify
都是Object
類中的方法.
wait
只有獲得了變量的監(jiān)視鎖,才能調(diào)用該變量的wait
方法,否則會(huì)拋出異常:
public class WaitExample {
private int a;
void incr() {
a++;
}
int getA() {
return a;
}
public static void main(String[] args) {
WaitExample waitExample = new WaitExample();
try {
// wrong
waitExample.wait();
for (int i = 0; i < 20000; i++) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
waitExample.incr();
}
});
t.start();
}
} catch (Exception e) {
e.printStackTrace();
}
System.out.println(waitExample.getA());
}
}
運(yùn)行結(jié)果:
java.lang.IllegalMonitorStateException
at java.lang.Object.wait(Native Method)
at java.lang.Object.wait(Object.java:502)
at com.may.learning.WaitExample.main(WaitExample.java:17)
0
獲得監(jiān)視鎖可以通過(guò)synchronized
關(guān)鍵字:
比如在方法上加上關(guān)鍵字:
synchronized void incr() {
a++;
}
或者,在調(diào)用時(shí)對(duì)該類加上關(guān)鍵字:
synchronized (waitExample) {
...
}
當(dāng)調(diào)用wait
時(shí),只會(huì)釋放當(dāng)前變量的鎖:
public static void main(String[] args) throws InterruptedException{
WaitExample waitExample = new WaitExample();
WaitExample anotherWaitExample = new WaitExample();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (waitExample) {
System.out.println("t: get waitExample lock success!");
System.out.println("t: try get anotherWaitExample lock");
try {
synchronized (anotherWaitExample) {
System.out.println("t: get anotherWaitExample success!");
System.out.println("t: release waitExample lock");
// t只釋放了waitExample的鎖
waitExample.wait();
}
} catch (InterruptedException e) {
System.out.println("被中斷");
}
}
}
});
t.start();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (waitExample) {
System.out.println("t1: get waitExample lock success!");
System.out.println("t1: try get anotherWaitExample lock");
try {
// 由于t未釋放鎖,因此t1獲取不到anotherWaitExample的鎖
synchronized (anotherWaitExample) {
System.out.println("t1: get anotherWaitExample success!");
System.out.println("t1: release waitExample lock");
waitExample.wait();
}
} catch (InterruptedException e) {
System.out.println("被中斷");
}
}
}
});
t1.start();
Thread.sleep(1000);
}
運(yùn)行結(jié)果:
t: get waitExample lock success!
t: try get anotherWaitExample lock
t: get anotherWaitExample success!
t: release waitExample lock
t1: get waitExample lock success!
t1: try get anotherWaitExample lock
如果需要指定時(shí)間,可以使用wait(long timeout)
或者wait(long timeout, int nanos)
.
被wait
掛起時(shí),被中斷時(shí)會(huì)拋出異常:
public class WaitExample {
private int a;
void incr() {
a++;
}
int getA() {
return a;
}
public static void main(String[] args) throws InterruptedException{
WaitExample waitExample = new WaitExample();
Thread t = new Thread(new Runnable() {
@Override
public void run() {
synchronized (waitExample) {
waitExample.incr();
try {
waitExample.wait();
} catch (InterruptedException e) {
System.out.println("被中斷");
}
}
}
});
t.start();
Thread.sleep(1000);
t.interrupt();
System.out.println(waitExample.getA());
}
}
運(yùn)行結(jié)果:
1
被中斷
notify
notify
會(huì)喚醒被wait
阻塞掛起的線程.與wait
類似,需要獲得了變量的監(jiān)視鎖后才能對(duì)其進(jìn)行進(jìn)行notify
操作.如果有多個(gè)線程都在等待喚醒,那么會(huì)隨機(jī)喚醒某一個(gè)線程.而notifyAll
會(huì)喚醒所有阻塞等待的線程.
類似wait
方法直奋,notify
也需要先獲得對(duì)象的監(jiān)視鎖(但是notify
不會(huì)釋放鎖)盗似。
public static void main(String[] args) throws InterruptedException {
NotifyExample notifyExample = new NotifyExample();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (notifyExample) {
System.out.println("t1 掛起");
notifyExample.wait();
System.out.println("t1 被喚醒了");
}
} catch (InterruptedException e) {
System.out.println("被中斷了");
}
}
});
t1.start();
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
try {
synchronized (notifyExample) {
System.out.println("t2 掛起");
notifyExample.wait();
System.out.println("t2 被喚醒了");
}
} catch (InterruptedException e) {
System.out.println("被中斷了");
}
}
});
t2.start();
Thread.sleep(1000);
synchronized (notifyExample) {
notifyExample.notify();
}
}
運(yùn)行結(jié)果:
t1 掛起
t2 掛起
t1 被喚醒了
如果將notifyExample.notify()
換成notifyExample.notifyAll()
,那么t1
和t2
都能被喚醒:
t1 掛起
t2 掛起
t2 被喚醒了
t1 被喚醒了
生產(chǎn)者消費(fèi)者
利用wait
和notify
寫一個(gè)簡(jiǎn)單的生產(chǎn)者消費(fèi)者:
import java.util.LinkedList;
import java.util.Queue;
public class ProducerConsumer {
public static void main(String[] args) throws InterruptedException {
Queue<Integer> queue = new LinkedList<>();
Thread producer = new Thread(new Runnable() {
@Override
public void run() {
int i=0;
synchronized (queue) {
while (true){
try {
if (!queue.isEmpty()) {
queue.wait();
}else{
queue.add(++i);
System.out.println("生產(chǎn)了"+i);
queue.notify();
}
} catch (InterruptedException e) {
System.out.println("被中斷了");
return;
}
}
}
}
});
Thread consumer = new Thread(new Runnable() {
@Override
public void run() {
synchronized (queue) {
while (true){
try{
if (queue.isEmpty()) {
queue.wait();
}else{
int i = queue.poll();
System.out.println("消費(fèi)了"+i);
queue.notify();
}
}catch (InterruptedException e){
System.out.println("被中斷了");
return;
}
}
}
}
});
producer.start();
consumer.start();
Thread.sleep(10000);
producer.interrupt();
consumer.interrupt();
}
}
運(yùn)行結(jié)果:
...
生產(chǎn)了782421
消費(fèi)了782421
生產(chǎn)了782422
消費(fèi)了782422
生產(chǎn)了782423
消費(fèi)了782423
生產(chǎn)了782424
被中斷了
被中斷了
join
wait
與notify
屬于Ojbect
的方法,而join
是Thread
的方法.
調(diào)用某一線程的join
方法,可以使當(dāng)前線程(下例中的main
線程)阻塞等待該線程完成:
public class JoinTest {
public static void main(String[] args) throws InterruptedException {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
try {
Thread.sleep(2000);
System.out.println("t 結(jié)束了!");
} catch (InterruptedException e) {
System.out.println("被中斷了");
}
}
});
t.start();
t.join();
System.out.println("main 結(jié)束了!");
}
}
運(yùn)行結(jié)果:
t 結(jié)束了!
main 結(jié)束了!
sleep
sleep
和wait
一樣可以掛起當(dāng)前線程,被中斷時(shí)也會(huì)拋出異常.但不同的是它是屬于Thread
的方法,并且它不會(huì)釋放對(duì)象的監(jiān)視鎖.指定睡眠時(shí)間到達(dá)后,會(huì)進(jìn)入就緒狀態(tài),等待cpu
的調(diào)度.
public class SleepTest {
public static void main(String[] args) throws InterruptedException {
SleepTest sleepTest = new SleepTest();
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
synchronized (sleepTest){
try {
System.out.println("t1 獲取到鎖");
Thread.sleep(60000);
} catch (InterruptedException e) {
System.out.println("被中斷了!");
}
}
}
});
t1.start();
Thread.sleep(1000);
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t2 等待鎖");
synchronized (sleepTest) {
System.out.println("t2 獲取到鎖了");
}
}
});
t2.start();
Thread.sleep(2000);
t1.interrupt();
}
}
運(yùn)行結(jié)果:
t1 獲取到鎖
t2 等待鎖
被中斷了!
t2 獲取到鎖了
yield
線程會(huì)主動(dòng)讓出自己的cpu
時(shí)間,并不會(huì)被掛起,只是進(jìn)入了就緒狀態(tài).但是下一次仍然可能被調(diào)度到.
中斷
前面已經(jīng)使用了interrupt
很多次,需要注意與isInterrupted
以及interrupted
進(jìn)行區(qū)分.
isInterrupted
: 判斷當(dāng)前線程是否被中斷;
interrupted
: 判斷當(dāng)前線程是否被中斷,并且復(fù)位(清除中斷標(biāo)志);
使用interrupted
判斷中斷并且復(fù)位:
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
while (!Thread.interrupted()) {
}
System.out.println("t1 is Interrupted: "+Thread.currentThread().isInterrupted());
}
});
t1.start();
t1.interrupt();
t1.join();
System.out.println("done");
}
}
運(yùn)行結(jié)果:
t1 is Interrupted: false
done
需要注意的是調(diào)用線程的interrupt
方法,只是為該線程設(shè)置中斷位.當(dāng)線程在運(yùn)行時(shí),會(huì)立即往下執(zhí)行.而當(dāng)被wait
,join
或者sleep
方法阻塞掛起時(shí),才會(huì)拋出InterruptedException
異常,此時(shí)中斷位會(huì)被恢復(fù),如下面這個(gè)例子:
public class InterruptTest throws InterruptedException{
public static void main(String[] args){
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
System.out.println("t1 running");
while (true) {
}
}
});
t1.start();
t1.interrupt();
System.out.println("main thread isInterrupted: " + Thread.interrupted());
System.out.println("t1 isInterrupted: " + t1.isInterrupted());
boolean isInterrupted = Thread.interrupted();
System.out.println("isInterrupted: " + isInterrupted);
System.out.println("main thread isInterrupted: " + Thread.interrupted());
System.out.println("t1 isInterrupted: " + t1.isInterrupted());
t1.join();
System.out.println("done");
}
}
輸出結(jié)果:
main thread isInterrupted: false
t1 isInterrupted: true
isInterrupted: false
main thread isInterrupted: false
t1 running
t1 isInterrupted: true
因?yàn)?code>t1并沒(méi)有被掛起,所以即使中斷位為true
,也仍然在繼續(xù)運(yùn)行,不會(huì)輸出"done"
.而interrupted
針對(duì)是當(dāng)前線程,也就是主線程,所以中斷標(biāo)志位一直是false
.
如果在阻塞情況下,中斷位會(huì)被馬上復(fù)位,對(duì)上面的例子稍加修改:
public class InterruptTest {
public static void main(String[] args) throws InterruptedException {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
try{
while (!Thread.currentThread().isInterrupted()) {
System.out.println("t1: running");
Thread.sleep(1000);
}
}catch (Exception e){
System.out.println("t1 被中斷");
System.out.println("thread t1 isInterrupted : "+Thread.currentThread().isInterrupted());
}
}
});
t1.start();
Thread.sleep(1000);
t1.interrupt();
System.out.println("main thread isInterrupted: " + Thread.interrupted());
System.out.println("t1 isInterrupted: " + t1.isInterrupted());
t1.join();
System.out.println("done");
}
}
運(yùn)行結(jié)果:
t1: running
t1 被中斷
thread t1 isInterrupted : false
main thread isInterrupted: false
t1 isInterrupted: false
done
主線程中的isInterrupted
仍然是false
,但是打印出的t1
的isInterrupted
由于處在阻塞狀態(tài)被中斷,中斷位又被復(fù)位成了false
.
上下文切換與死鎖
當(dāng)出現(xiàn):
-
CPU
時(shí)間用盡,線程進(jìn)入就緒狀態(tài)時(shí) - 被其他線程中斷時(shí)
會(huì)出現(xiàn)上下文的切換.
避免死鎖的出現(xiàn),可以采用不同線程都按照相同的順序去請(qǐng)求資源,保證資源獲取的有序一致性.
守護(hù)線程與用戶線程
守護(hù)線程的存在不影響JVM
的退出,只要最后一個(gè)用戶線程沒(méi)有退出,正常情況下JVM
也不會(huì)退出.
JVM
沒(méi)有退出:
public class DaemonTest {
public static void main(String[] args) {
Thread t = new Thread(new Runnable() {
@Override
public void run() {
while (true) {
}
}
});
// t.setDaemon(true);
t.start();
System.out.println("main exit");
}
}
運(yùn)行結(jié)果:
main exit
將t
利用setDeamon
設(shè)置為守護(hù)線程的注釋打開(kāi),運(yùn)行結(jié)果:
main exit
Process finished with exit code 0
原理是,main
運(yùn)行后,JVM
會(huì)自動(dòng)啟動(dòng)一個(gè)叫做DestoryJavaVM
的線程,它會(huì)等待所有用戶線程結(jié)束后終止JVM
進(jìn)程.
ThreadLocal
如果創(chuàng)建了一個(gè)ThreadLocal
變量,每個(gè)訪問(wèn)這個(gè)變量的線程都會(huì)獲得一個(gè)副本.
public class ThreadLocalTest {
static ThreadLocal<Integer> val = new ThreadLocal<>();
static void print(String string) {
System.out.println(string + ":" + val.get());
}
public static void main(String[] args) {
Thread t1 = new Thread(new Runnable() {
@Override
public void run() {
val.set(1);
for (int i = 0; i < 50; i++) {
int v = val.get();
v++;
val.set(v);
}
print("thread1");
val.remove();
print("after remove thread1");
}
});
Thread t2 = new Thread(new Runnable() {
@Override
public void run() {
val.set(1);
for (int i = 0; i < 50; i++) {
int v = val.get();
v += 2;
val.set(v);
}
print("thread2");
val.remove();
print("after remove thread2");
}
});
t1.start();
t2.start();
System.out.println("done");
}
}
運(yùn)行結(jié)果:
thread1:51
after remove thread1:null
done
thread2:101
after remove thread2:null
ThreadLocal
的原理:
-
Thread
中有一個(gè)threadLocals
變量,它的類型是ThreadLocal.ThreadLocalMap
,實(shí)際上是一個(gè)hashMap
. - 當(dāng)使用
set
方法賦值時(shí):
// ThreadLocal.java
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
getMap
方法獲得的是threadLocals
,如果不為空,將當(dāng)前threadLocal
變量作為key
,設(shè)置的值作為value
放入進(jìn)行存儲(chǔ),而如果為空,則利用createMap
進(jìn)行初始化.
// ThreadLocal.java
void createMap(Thread t, T firstValue) {
t.threadLocals = new ThreadLocalMap(this, firstValue);
}
需要注意的是
createMap
中傳入的是當(dāng)前線程,因?yàn)樾枰獮槠湓O(shè)置threadLocals
變量的值.而threadLocals
中的key
實(shí)際上是ThreadLocal
變量,也就是可以理解為:thread1.threadLocals -> {ThreadLocal@xxx:1, ThreadLocal@yyy:"hello"}
但是ThreadLocal
不支持繼承,如果需要繼承父線程的值,需要使用InheritableThreadLocal
:
public class InheritThreadLocalTest {
static InheritableThreadLocal<String> inheritableThreadLocal = new InheritableThreadLocal<>();
static void print(String str) {
System.out.println(str + ":" + inheritableThreadLocal.get());
}
public static void main(String[] args) {
inheritableThreadLocal.set("hello");
Thread t = new Thread(new Runnable() {
@Override
public void run() {
String initVal = inheritableThreadLocal.get();
if (initVal != null) {
inheritableThreadLocal.set(initVal+" world");
}
print("thread 1");
}
});
t.start();
print("main");
System.out.println("done");
}
}
運(yùn)行結(jié)果:
main:hello
done
thread 1:hello world
InheritableThreadLocal
和ThreadLocal
的set
方法是一樣的,區(qū)別在于createMap
,它將變量寫入了inheritableThreadLocals
里:
void createMap(Thread t, T firstValue) {
t.inheritableThreadLocals = new ThreadLocalMap(this, firstValue);
}
在初始化線程的時(shí)候,init
方法會(huì)設(shè)置inheritableThreadLocals
:
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals){
...
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
...
}
它會(huì)將父線程的inheritableThreadLocals
都復(fù)制到自己的inheritableThreadLocals
中.