開(kāi)啟線程方式
1. 繼承自Thread類(lèi)的線程
public class ThreadExer {
public static void main(String[] args){
Thread thread = new MyThread();
thread.setName("sub thread");//設(shè)置子線程的名稱
thread.start();
for(int i=0;i<20;i++){
try {
Thread.sleep(100);
System.out.println("thread name:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyThread extends Thread{
@Override
public void run() {
super.run();
for(int i=0;i<20;i++){
try {
Thread.sleep(100);
System.out.println("thread name:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2. 實(shí)現(xiàn)Runnable接口的線程
public class ThreadExer {
public static void main(String[] args){
Thread thread = new Thread(new MyRunnable(),"sub thread(sub thread)");
thread.start();
for(int i=0;i<20;i++){
try {
Thread.sleep(100);
System.out.println("thread name:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class MyRunnable implements Runnable{
@Override
public void run() {
for(int i=0;i<20;i++){
try {
Thread.sleep(100);
System.out.println("thread name:"+Thread.currentThread().getName());
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
實(shí)現(xiàn)Runnable接口和繼承自Thread的區(qū)別
- 實(shí)現(xiàn)Runnable接口的方法,只要傳入同一個(gè)Runnable對(duì)象即可實(shí)現(xiàn)資源共享
- 可以避免java中單繼承的限制
線程狀態(tài)
線程內(nèi)部狀態(tài)
線程內(nèi)部定義了線程狀態(tài)的枚舉值如下
public static enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
private State() {
}
}
NEW 新建狀態(tài)
==Thread thread = new Thread(runnable);== 使用new操作符創(chuàng)建Thread對(duì)象則處于NEW狀態(tài)RUNNABLE 可運(yùn)行狀態(tài)
==thead.start()==BLOCKED 阻塞狀態(tài)
WAITING 等待狀態(tài)
TIMED_WAITING 休眠狀態(tài)
TERMINATED 終止?fàn)顟B(tài)
一般所說(shuō)的5種狀態(tài)
- 新建狀態(tài)(New) :新創(chuàng)建了一個(gè)線程對(duì)象。
- 就緒狀態(tài)(Runnable):線程對(duì)象創(chuàng)建后,其他線程調(diào)用了該對(duì)象的start()方法。該狀態(tài)的線程位于可運(yùn)行線程池中,變得可運(yùn)行,等待獲取CPU的使用權(quán)。
- 運(yùn)行狀態(tài)(Running):就緒狀態(tài)的線程獲取了CPU,執(zhí)行程序代碼。
-
阻塞狀態(tài)(Blocked):阻塞狀態(tài)是線程因?yàn)槟撤N原因放棄CPU使用權(quán),暫時(shí)停止運(yùn)行。直到線程進(jìn)入就緒狀態(tài),才有機(jī)會(huì)轉(zhuǎn)到運(yùn)行狀態(tài)。阻塞的情況分三種:
- 等待阻塞:運(yùn)行的線程執(zhí)行wait()方法,JVM會(huì)把該線程放入等待池中。(wait會(huì)釋放持有的鎖)
- 同步阻塞:運(yùn)行的線程在獲取對(duì)象的同步鎖時(shí),若該同步鎖被別的線程占用,則JVM會(huì)把該線程放入鎖池中。
- 其他阻塞:運(yùn)行的線程執(zhí)行sleep()或join()方法,或者發(fā)出了I/O請(qǐng)求時(shí),JVM會(huì)把該線程置為阻塞狀態(tài)。當(dāng)sleep()狀態(tài)超時(shí)、join()等待線程終止或者超時(shí)、或者I/O處理完畢時(shí),線程重新轉(zhuǎn)入就緒狀態(tài)。(注意,sleep是不會(huì)釋放持有的鎖)
- 死亡狀態(tài)(Dead):線程執(zhí)行完了或者因異常退出了run()方法,該線程結(jié)束生命周期。
線程狀態(tài)轉(zhuǎn)化
等待Blocked,鎖定Blocked都屬于阻塞狀態(tài)。
Thead常用Api
1. sleep()
// Thread.java
public static void sleep(long millis) throws InterruptedException
使當(dāng)前線程主動(dòng)讓出CPU,CPU去執(zhí)行其他線程,在sleep指定的時(shí)間過(guò)后,該線程進(jìn)入就緒狀態(tài),線程重新?lián)寠Z執(zhí)行權(quán),如果當(dāng)前線程進(jìn)入運(yùn)行狀態(tài)會(huì)接著sleep后的代碼繼續(xù)執(zhí)行。如果當(dāng)前線程進(jìn)入了同步鎖,sleep方法并不會(huì)釋放鎖,即使當(dāng)前線程使用sleep方法讓出了CPU,但其他被同步鎖擋住了的線程也無(wú)法得到執(zhí)行
Thread.sleep(0)的作用是"觸發(fā)操作系統(tǒng)立刻重新進(jìn)行一次CPU競(jìng)爭(zhēng),重新計(jì)算優(yōu)先級(jí)
2. yield()
// Thread.java
public static native void yield();
使當(dāng)前線程由"運(yùn)行狀態(tài)"進(jìn)入到"就緒狀態(tài)",從而讓其它具有相同優(yōu)先級(jí)的等待線程獲取執(zhí)行權(quán);但是,并不能保證在當(dāng)前線程調(diào)用Thead.yield()
之后,其它具有相同優(yōu)先級(jí)的線程就一定能獲得執(zhí)行權(quán);也有可能是當(dāng)前線程又進(jìn)入到“運(yùn)行狀態(tài)”繼續(xù)運(yùn)行!
3. join()
// Thread.java
public final void join() throws InterruptedException
public final void join(long millis) throws InterruptedException
在A線程中調(diào)用b.join()方法,會(huì)使A線程進(jìn)入阻塞狀態(tài),直到B結(jié)束生命周期或到達(dá)了給定時(shí)間
4. interrupt相關(guān)方法
interrupt相關(guān)方法如下
// Thread.java
1. public void interrupt()
3. public native boolean isInterrupted()
2. public static native boolean interrupted()
- interrupt()
當(dāng)B線程調(diào)用wait,sleep,join等方法會(huì)使線程進(jìn)入阻塞狀態(tài),在A線程中調(diào)用b.interrupt()方法就會(huì)打斷B線程阻塞狀態(tài),wait,sleep,join等方法就會(huì)拋出InterruptedException。一個(gè)線程內(nèi)部存在interrupt flag,默認(rèn)false,當(dāng)被打斷時(shí)interrupt會(huì)被置為true,當(dāng)捕獲InterruptedException異常時(shí),該interrupt又會(huì)被置false。 - isInterrupted()
b.isInterrupted()會(huì)判斷B線程是否被中斷,僅僅通過(guò)interrupt flag判斷,并不會(huì)影響interrupt的改變 - interrupted()
調(diào)用Thread.interrupted()方法會(huì)首先返回當(dāng)前線程的interrupt的狀態(tài),如果interrupt為true,則會(huì)置interrupt為false
5. wait/notify/notifyAll()
Object類(lèi)中定義了wait(),notify(),notifyAll()方法
1. public final native void wait() throws InterruptedException;
public final void wait(long millis) throws InterruptedException
2. public final native void notify();
3. public final native void notifyAll();
- wait()
o.wait()
鎖對(duì)象o調(diào)用wait方法使當(dāng)前線程進(jìn)入阻塞狀態(tài),jvm會(huì)使調(diào)用者所屬線程進(jìn)入waitset,并立刻釋放鎖對(duì)象,直到通過(guò)調(diào)用該鎖對(duì)象o的notify()或notifyAll()方法將其喚醒,或者阻塞時(shí)間到指定時(shí)間時(shí)自動(dòng)喚醒 - notify() 通過(guò)鎖對(duì)象調(diào)用notify后會(huì)將waitset中的一個(gè)線程彈出,具體彈出哪個(gè)取決于jvm,彈出的線程將可以重新獲得鎖
- notifyAll() 通過(guò)鎖對(duì)象調(diào)用notifyAll后會(huì)彈出waitset中的所有線程,這些線程會(huì)爭(zhēng)奪鎖,得到鎖的線程才可以繼續(xù)執(zhí)行。
wait,notify,notifyAll方法必須在同步方法中使用,且調(diào)用者就是鎖對(duì)象,對(duì)于同步方法來(lái)說(shuō)就是this,對(duì)于同步代碼塊來(lái)說(shuō)就是指定的鎖對(duì)象。
有下面兩個(gè)問(wèn)題需要注意
1. 首先為什么wait、notify和notifyAll方法要和synchronized關(guān)鍵字一起使用?
因?yàn)閣ait方法是使一個(gè)線程進(jìn)入等待狀態(tài),并且釋放其所持有的鎖對(duì)象,notify方法是通知等待該鎖對(duì)象的線程重新獲得鎖對(duì)象,然而如果沒(méi)有獲得鎖對(duì)象,wait方法和notify方法都是沒(méi)有意義的,因此必須先獲得鎖對(duì)象再對(duì)鎖對(duì)象進(jìn)行進(jìn)一步操作于是才要把wait方法和notify方法寫(xiě)到同步方法和同步代碼塊中了。
由此可知,wait和notify、notifyAll方法是由確定的對(duì)象即鎖對(duì)象來(lái)調(diào)用的,鎖對(duì)象就像一個(gè)傳話的人,他對(duì)某個(gè)線程說(shuō)停下來(lái)等待,然后對(duì)另一個(gè)線程說(shuō)你可以執(zhí)行了(實(shí)質(zhì)上是被捕獲了),這一過(guò)程是線程通信。sleep方法是讓某個(gè)線程暫停運(yùn)行一段時(shí)間,其控制范圍是由當(dāng)前線程決定,運(yùn)行的主動(dòng)權(quán)是由當(dāng)前線程來(lái)控制(擁有CPU的執(zhí)行權(quán))。
2. 鎖(monitor)池和等待池(waitset)的區(qū)別?
- 鎖池 假設(shè)線程A已經(jīng)擁有了某個(gè)對(duì)象(注意:不是類(lèi))的鎖,而其它的線程想要調(diào)用這個(gè)對(duì)象的某個(gè)synchronized方法(或者synchronized塊),由于這些線程在進(jìn)入對(duì)象的synchronized方法之前必須先獲得該對(duì)象的鎖的擁有權(quán),但是該對(duì)象的鎖目前正被線程A擁有,所以這些線程就進(jìn)入了該對(duì)象的鎖池中。簡(jiǎn)單點(diǎn)說(shuō),如果有個(gè)線程已拿到某個(gè)對(duì)象鎖,其他線程要執(zhí)行含有該鎖的synchronized方法時(shí)就會(huì)進(jìn)入鎖池
- 等待池 假設(shè)當(dāng)前A線程持有鎖對(duì)象后,調(diào)用了這個(gè)鎖對(duì)象的 wait() 方法,則 A線程就會(huì)釋放該對(duì)象的鎖(因?yàn)?wait() 方法必須出現(xiàn)在 synchronized 中,所以在執(zhí)行 wait() 方法之前 A 線程就已經(jīng)擁有了該對(duì)象的鎖),同時(shí)線程 A會(huì)進(jìn)入該鎖對(duì)象的等待池中。如果有其它線程持有上述鎖對(duì)象后,并調(diào)用了該鎖對(duì)象的 notifyAll() 方法,那么處于該鎖對(duì)象的等待池中的線程就會(huì)全部進(jìn)入該鎖對(duì)象的鎖池中,從新?tīng)?zhēng)奪鎖的擁有權(quán)。如果另外的一個(gè)線程調(diào)用了相同對(duì)象的 notify() 方法,那么僅僅有一個(gè)處于該對(duì)象的等待池中的線程(隨機(jī))會(huì)進(jìn)入該對(duì)象的鎖池。
線程調(diào)度
線程優(yōu)先級(jí)
Java線程的優(yōu)先級(jí)用整數(shù)表示,取值范圍是1~10,Thread類(lèi)有以下三個(gè)靜態(tài)常量
public static final int MIN_PRIORITY = 1;
public static final int NORM_PRIORITY = 5;
public static final int MAX_PRIORITY = 10;
Thread類(lèi)的setPriority()和getPriority()方法分別用來(lái)設(shè)置和獲取線程的優(yōu)先級(jí)。
每個(gè)線程都有默認(rèn)的優(yōu)先級(jí)。主線程的默認(rèn)優(yōu)先級(jí)為T(mén)hread.NORM_PRIORITY。
線程的優(yōu)先級(jí)有繼承關(guān)系,比如A線程中創(chuàng)建了B線程,那么B將和A具有相同的優(yōu)先級(jí)。
JVM提供了10個(gè)線程優(yōu)先級(jí),但與常見(jiàn)的操作系統(tǒng)都不能很好的映射。如果希望程序能移植到各個(gè)操作系統(tǒng)中,應(yīng)該僅僅使用Thread類(lèi)有以下三個(gè)靜態(tài)常量作為優(yōu)先級(jí),這樣能保證同樣的優(yōu)先級(jí)采用了同樣的調(diào)度方式。
如果CPU比較忙,設(shè)置優(yōu)先級(jí)可能會(huì)獲得更多的CPU時(shí)間,但如果CPU比較空閑,優(yōu)先級(jí)幾乎不會(huì)起任何作用。所以不要指望依賴優(yōu)先級(jí)來(lái)綁定某些特定業(yè)務(wù),實(shí)際一般不會(huì)設(shè)置優(yōu)先級(jí),采用默認(rèn)優(yōu)先級(jí)為5.
線程的停止
- ~thread.stop()
stop方法已被廢棄,原因是什么? - thread.setDaemon(true);
public class StopThreadExer {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
// 調(diào)用該方法設(shè)置守護(hù)線程
thread.setDaemon(true);
thread.start();
for (int i = 0; i < 5; i++) {
try {
thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + i);
}
}
}
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName() + i);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
輸出結(jié)果
Thread-00
main0
main1
main2
main3
main4
在不調(diào)用setDaemon情況下,子線程會(huì)輸出5次,上述輸出可見(jiàn)主線程停止了守護(hù)線程也停止了,即使是在阻塞的情況下。守護(hù)線程是為其他線程服務(wù),當(dāng)所有的非守護(hù)線程結(jié)束時(shí), 程序也就終止了,同時(shí)會(huì)殺死進(jìn)程中的所有守護(hù)線程,所以不要在守護(hù)線程中去訪問(wèn)文件等資源
- 標(biāo)志位(使用最多)
可以根據(jù)業(yè)務(wù)邏輯決定什么時(shí)候結(jié)束子線程
public class StopThreadExer {
public static void main(String[] args) {
Thread thread = new Thread(new MyRunnable());
thread.start();
for (int i = 0; i < 5; i++) {
try {
thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + i);
}
MyRunnable.running = false;// 使標(biāo)志量為false,則子線程不會(huì)繼續(xù)執(zhí)行下一次循環(huán),但有有可能正在延時(shí),故需要調(diào)用interrupt()方法使打斷阻塞的線程
thread.interrupt();
}
}
class MyRunnable implements Runnable {
public static boolean running = true;
@Override
public void run() {
for (int i = 0; i < 5&&running; i++) {
System.out.println(Thread.currentThread().getName() + i);
try {
Thread.sleep(3000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
注意 MyRunnable.running = false;thread.interrupt();
1.線程生命周期正常結(jié)束
任務(wù)耗時(shí)短時(shí)或者時(shí)間可控放任正常結(jié)束即可
2. 通過(guò)中斷信號(hào)interrupt關(guān)閉線程
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread() {
@Override
public void run() {
super.run();
System.out.println("I will start work");
while (!isInterrupted()) {
// working
}
System.out.println("I will be exiting");
}
};
thread.start();
TimeUnit.SECONDS.sleep(5);
// 1.
thread.interrupt();
}
注釋1處調(diào)用interrupt后,thread線程的interrupt標(biāo)識(shí)就會(huì)返回true,isInterrupted()方法就會(huì)返回true,則while循環(huán)條件為false,接著執(zhí)行下一句輸出生命周期結(jié)束,線程結(jié)束。
Thread thread = new Thread() {
@Override
public void run() {
super.run();
System.out.println("I will start work");
while (true) {
try {
TimeUnit.MILLISECONDS.sleep(100);
} catch (InterruptedException e) {
// 1.
break;
}
}
System.out.println("I will be exiting");
}
};
thread.start();
TimeUnit.SECONDS.sleep(5);
// 2.
thread.interrupt();
注釋2處調(diào)用interrupt后,thread線程注釋1處會(huì)捕獲InterruptedException異常,捕獲到該異常時(shí)退出循環(huán)即可,進(jìn)而執(zhí)行下個(gè)輸出語(yǔ)句,最后線程停止。
3. volatile 變量控制開(kāi)關(guān)
由于interrupt標(biāo)識(shí)很有可能被擦除,故加入flag來(lái)控制,注意需要使用volatile關(guān)鍵字修飾,保證每個(gè)線程可以讀取到主內(nèi)存中最新的值,否則可能會(huì)引起線程的無(wú)線循環(huán)
static class MyThread extends Thread {
private volatile boolean closed;
@Override
public void run() {
super.run();
System.out.println("I will start work");
while (!closed && !isInterrupted()) {
// working
}
System.out.println("I will be exiting");
}
public void close() {
closed = true;
this.interrupt();
}
}
public static void main(String[] args) throws InterruptedException {
MyThread thread = new MyThread();
thread.start();
TimeUnit.SECONDS.sleep(5);
thread.close();
}
同步
同步是為了解決多線程共同訪問(wèn)數(shù)據(jù)導(dǎo)致數(shù)據(jù)出現(xiàn)問(wèn)題,它使多線程可以實(shí)現(xiàn)對(duì)一段代碼互斥訪問(wèn),也就是在同步代碼中只會(huì)存在一個(gè)線程執(zhí)行。
- 同步方法
private int count;
public synchronized void count(){
count++;
}
使用synchronized修飾符修飾成員方法,此時(shí)鎖對(duì)象默認(rèn)為當(dāng)前對(duì)象
private static int count;
public static synchronized void count(){
count++;
}
修改在靜態(tài)方法上,鎖對(duì)象默認(rèn)為當(dāng)前類(lèi)對(duì)象(類(lèi)名.class)
- 同步代碼塊
synchronized (this){
count++;
}
括號(hào)中可以是任意對(duì)象。
線程間的協(xié)作
1. Object類(lèi)中的成員方法
- wait()
使當(dāng)前持有該鎖的線程釋放鎖,并變?yōu)?strong>阻塞狀態(tài),然后加入鎖對(duì)象的等待隊(duì)列(等待池)中 - notify()
由當(dāng)前持有同步鎖的線程調(diào)用,以喚醒鎖對(duì)象的等待隊(duì)列中的一個(gè)線程,使其變?yōu)榫途w狀態(tài)。直到當(dāng)前線程釋放鎖,被喚醒的線程才可能被執(zhí)行,被喚醒后且得到了鎖的線程,才會(huì)從wait方法的下一句繼續(xù)執(zhí)行。 - notifyAll()
由當(dāng)前持有同步鎖的線程調(diào)用,以喚醒鎖對(duì)象的等待隊(duì)列中的所有線程,被喚醒的線程將競(jìng)爭(zhēng)鎖
2. 生產(chǎn)者和消費(fèi)者
public class ThreadExer {
public static void main(String[] args) {
List list = new ArrayList();
Runnable producerRunnable = new ProducerRunnable(list);
Runnable consumerRunnable = new ConsumerRunnable(list);
Thread producer = new Thread(producerRunnable, "producer1");
Thread producer1 = new Thread(producerRunnable, "producer2");
Thread producer2 = new Thread(producerRunnable, "producer3");
Thread consumer = new Thread(consumerRunnable, "consumer1");
Thread consumer1 = new Thread(consumerRunnable, "consumer2");
Thread consumer2 = new Thread(consumerRunnable, "consumer3");
Thread consumer3 = new Thread(consumerRunnable, "consumer4");
producer.start();
producer1.start();
producer2.start();
consumer.start();
consumer1.start();
consumer2.start();
consumer3.start();
}
}
class ProducerRunnable implements Runnable {
private static final int MAX_CONTAINER_SIZE = 10;
List container;
String produceProductName;
int count;
public ProducerRunnable(List container) {
this.container = container;
}
@Override
public void run() {
for (int i = 0; i < 20; i++) {
synchronized (container) {
// 由于生產(chǎn)者被喚醒后會(huì)接著wait()后的一句代碼進(jìn)行執(zhí)行,當(dāng)一個(gè)被喚醒的生產(chǎn)者搶奪鎖后生產(chǎn)一個(gè)后釋放鎖,當(dāng)?shù)诙€(gè)生產(chǎn)者擁有鎖后也會(huì)接著wait方法后執(zhí)行,故無(wú)法判斷容器已滿,故采用循環(huán)判斷。
while (container.size() == MAX_CONTAINER_SIZE) {
System.out.println("容器已滿,等待消費(fèi)");
try {
container.wait();// 讓當(dāng)前生產(chǎn)者線程進(jìn)入該鎖對(duì)象的等待池,并釋放該鎖,
} catch (InterruptedException e) {
e.printStackTrace();
}
}
produceProductName = (count++) + "號(hào)產(chǎn)品";
System.out.println(Thread.currentThread().getName()+"生產(chǎn) " + produceProductName);
container.add(produceProductName);
container.notifyAll(); // 會(huì)喚醒等待池中的所有線程,代碼走到此處說(shuō)明容器沒(méi)滿,意味著沒(méi)有生產(chǎn)者處于等待池,則意思就是喚醒所有消費(fèi)者線程去競(jìng)爭(zhēng)鎖
}
try {
Thread.sleep(50);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class ConsumerRunnable implements Runnable {
private static final int MIN_CONTAINER_SIZE = 0;
List container;
String consumeProductName;
public ConsumerRunnable(List container) {
this.container = container;
}
@Override
public void run() {
while (true) {
synchronized (container) {
while (container.size() == MIN_CONTAINER_SIZE) {
System.out.println("容器為空,等待生產(chǎn)");
try {
container.wait(); // 容器為空,消費(fèi)者加入等待池
} catch (InterruptedException e) {
e.printStackTrace();
}
}
consumeProductName = (String) container.remove(0);
System.out.println(Thread.currentThread().getName() + " 消費(fèi)" + consumeProductName);
container.notifyAll(); // 代碼走到此處說(shuō)明所有等待池中的消費(fèi)者都被喚醒,故此處會(huì)喚醒處于等待池中的所有生產(chǎn)者
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
下面log為截取了一段輸出
producer1生產(chǎn) 0號(hào)產(chǎn)品
consumer2 消費(fèi)0號(hào)產(chǎn)品
容器為空,等待生產(chǎn)
容器為空,等待生產(chǎn)
容器為空,等待生產(chǎn)
producer3生產(chǎn) 1號(hào)產(chǎn)品
producer2生產(chǎn) 2號(hào)產(chǎn)品
consumer1 消費(fèi)1號(hào)產(chǎn)品
consumer4 消費(fèi)2號(hào)產(chǎn)品
容器為空,等待生產(chǎn)
producer1生產(chǎn) 3號(hào)產(chǎn)品
consumer3 消費(fèi)3號(hào)產(chǎn)品
producer2生產(chǎn) 4號(hào)產(chǎn)品
producer3生產(chǎn) 5號(hào)產(chǎn)品
producer3生產(chǎn) 6號(hào)產(chǎn)品
producer2生產(chǎn) 7號(hào)產(chǎn)品
producer1生產(chǎn) 8號(hào)產(chǎn)品
這個(gè)示例3個(gè)生產(chǎn)者總共會(huì)生產(chǎn)60個(gè)產(chǎn)品,但容器里最多只能放入10個(gè)產(chǎn)品,放滿時(shí)生產(chǎn)者等待,當(dāng)容器產(chǎn)品被消費(fèi)消費(fèi)完時(shí)則消費(fèi)者等待。使用wait和notify實(shí)現(xiàn)了生產(chǎn)者和消費(fèi)者的協(xié)作。
死鎖
實(shí)際寫(xiě)代碼要避免死鎖
public class DeadLock {
public static Object obj1 = new Object();
public static Object obj2 = new Object();
public static void main(String[] args) {
Runnable aRunnable = new ARunnable();
Runnable bRunnable = new BRunnable();
Thread thread1 = new Thread(aRunnable);
Thread thread2 = new Thread(bRunnable);
thread1.start();
thread2.start();
}
}
class ARunnable implements Runnable {
@Override
public void run() {
synchronized (DeadLock.obj1) {
System.out.println("A Runnable obj1 lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (DeadLock.obj2) {
System.out.println("A Runnable obj2 lock");
}
}
}
}
class BRunnable implements Runnable {
@Override
public void run() {
synchronized (DeadLock.obj2) {
System.out.println("B Runnable obj2 lock");
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (DeadLock.obj1) {
System.out.println("B Runnable obj1 lock");
}
}
}
}
輸出
A Runnable obj1 lock
B Runnable obj2 lock
A線程獲得到obj1鎖后睡眠1s不會(huì)釋放鎖,1s沒(méi)有到達(dá)時(shí),B線程此時(shí)獲得到obj2鎖,接著A線程去嘗試獲得B線程已經(jīng)持有的obj2鎖,而B(niǎo)線程又在等待A線程中的obj1鎖,從而導(dǎo)致了死鎖。
死鎖檢查分析
- jconsole 運(yùn)行工具點(diǎn)擊detect lock
-
jstack [pid]
即可在stack信息中查看對(duì)應(yīng)進(jìn)程的死鎖情況
jps 命令查看java程序pid。截取主要信息如下
Java stack information for the threads listed above:
===================================================
"Thread-1":
at com.nan.mylibrary.BRunnable.run(DeadLock.java:48)
- waiting to lock <0x000000076d674558> (a java.lang.Object)
- locked <0x000000076d674568> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
"Thread-0":
at com.nan.mylibrary.ARunnable.run(DeadLock.java:30)
- waiting to lock <0x000000076d674568> (a java.lang.Object)
- locked <0x000000076d674558> (a java.lang.Object)
at java.lang.Thread.run(Thread.java:745)
Found 1 deadlock.
知識(shí)點(diǎn)
- 守護(hù)線程
hread.setDaemon(true);
通過(guò)設(shè)置該屬性則線程為守護(hù)線程,守護(hù)線程是為其他線程服務(wù)的,如果其他線程停止運(yùn)行了,虛擬機(jī)中只剩守護(hù)線程,則虛擬機(jī)回退出 - 線程名稱
通過(guò)setName可以設(shè)置線程名稱,不同的線程可以有相同的線程名稱,不能使用線程名稱來(lái)區(qū)分線程 - 線程ID
每個(gè)線程創(chuàng)建時(shí)則會(huì)分配線程ID,通過(guò)getId()即可拿到線程ID,無(wú)法修改==系統(tǒng)通過(guò)ID來(lái)區(qū)分不同的線程== - Java中程序運(yùn)行至少會(huì)啟動(dòng)2個(gè)線程。一個(gè)是main線程,一個(gè)是垃圾收集線程
特性
1 使用同一個(gè)runnable接口創(chuàng)建的線程可以變量資源的共享
2 線程在創(chuàng)建后就被分配內(nèi)存空間,該空間也在JVM中堆內(nèi)存中,其私有數(shù)據(jù)已被初始化
3 異常無(wú)法跨線程拋出或捕獲
4 調(diào)用yield()方法后調(diào)度器會(huì)將該線程放入等待隊(duì)列的末尾
5 同步方法鎖對(duì)象為this,同步塊鎖對(duì)象為傳入的對(duì)象,對(duì)于加鎖,釋放鎖是有JVM自動(dòng)完成的
6 當(dāng)使用不是同一個(gè)Runnable接口或繼承Thread創(chuàng)建的線程所加的同步方法,持有的鎖不是同一把鎖,因?yàn)閷?duì)象發(fā)生了變化
如果對(duì)于繼承Thread類(lèi)想要互斥訪問(wèn)同一個(gè)變量,則需使變量變?yōu)閟tatic,并且同步鎖需加在static方法上,此時(shí)鎖對(duì)象為Class對(duì)象,從而具有同一把鎖,實(shí)現(xiàn)了互斥訪問(wèn)
Java內(nèi)存模型
java內(nèi)存模型(JMM):
所有的變量都存儲(chǔ)在主內(nèi)存中
每個(gè)線程都有自己獨(dú)立的工作內(nèi)存,里面保存了該線程使用到的變量的副本(主內(nèi)存中該變量的一份拷貝)
JMM規(guī)定
1)線程對(duì)共享變量的所有操作都必須在自己的工作內(nèi)存中進(jìn)行, 不能直接從主內(nèi)存中讀寫(xiě)
2)不同線程之間無(wú)法直接訪問(wèn)其它線程工作內(nèi)存中的變量,線程間變量值的傳遞是需要通過(guò)主內(nèi)存來(lái)完成的。
synchronized:具有可見(jiàn)性,有序性,也具有原子性
JMM關(guān)于synchronized的兩條規(guī)定
- 線程解鎖前,必須把共享變量的最新值刷新到主內(nèi)存中
- 線程加鎖時(shí),將清空工作內(nèi)存中的共享變量的值,從而使用共享變量時(shí)要從主內(nèi)存中重新讀取最新的變量的值(加鎖和解鎖必須是同一把鎖)
線程執(zhí)行互斥代碼的過(guò)程:
(1)獲得互斥鎖
(2)清空工作內(nèi)存
(3)從主內(nèi)存拷貝變量最新的值給工作內(nèi)存中的副本
(4)執(zhí)行代碼
(5)將修改后的共享變量的值刷新到主內(nèi)存中
(6)釋放互斥鎖
volatile關(guān)鍵字
- 具有可見(jiàn)性、有序性,不具備原子性。其不會(huì)進(jìn)行鎖操作,所以性能更好。
注意,volatile不具備原子性,這是volatile與java中的synchronized、java.util.concurrent.locks.Lock最大的功能差異
下面來(lái)分別看下可見(jiàn)性、有序性、原子性:
原子性:如果你了解事務(wù),那這個(gè)概念應(yīng)該好理解。原子性通常指多個(gè)操作不存在只執(zhí)行一部分的情況,如果全部執(zhí)行完成那沒(méi)毛病,如果只執(zhí)行了一部分,那對(duì)不起,你得撤銷(xiāo)(即事務(wù)中的回滾)已經(jīng)執(zhí)行的部分。
-
可見(jiàn)性:當(dāng)多個(gè)線程訪問(wèn)同一個(gè)變量x時(shí),線程1修改了變量x的值,線程1、線程2...線程n能夠立即讀取到線程1修改后的值。
- 寫(xiě)操作:就是修改線程的工作內(nèi)存中副本的值,然后刷新到主內(nèi)存中,
- 讀操作:從主內(nèi)存中讀取值到工作內(nèi)存中的副本,然后再取副本中的值
有序性:即程序執(zhí)行時(shí)按照代碼書(shū)寫(xiě)的先后順序執(zhí)行。在Java內(nèi)存模型中,允許編譯器和處理器對(duì)指令進(jìn)行重排序,但是重排序過(guò)程不會(huì)影響到單線程程序的執(zhí)行,卻會(huì)影響到多線程并發(fā)執(zhí)行的正確性。(本文不對(duì)指令重排作介紹,但不代表它不重要,它是理解JAVA并發(fā)原理時(shí)非常重要的一個(gè)概念)。
不具有原子性
number++不具有原子性:eg:具有A,B線程,number = 5,然后A,B線程分別對(duì)其進(jìn)行加一操作
(1)A線程執(zhí)行number++讀到值時(shí)阻塞,B線程執(zhí)行
(2)B線程進(jìn)行加1操作后為6,并會(huì)把修改的值刷新到主內(nèi)存中,A線程執(zhí)行
(3)A線程繼續(xù)進(jìn)行加1操作,修改后的值為6,從而產(chǎn)生問(wèn)題
線程問(wèn)題整理
- 如果調(diào)用Thread的start()方法兩次則會(huì)報(bào)如下異常
Exception in thread "main" java.lang.IllegalThreadStateException
at java.lang.Thread.start(Thread.java:705)
at com.example.ThreadExer.main(ThreadExer.java:24)
thread name:Thread-0
thread name:Thread-0