詳解Java多線程與并發(fā)
一、進程與線程
進程:是代碼在數(shù)據(jù)集合上的一次運行活動,是系統(tǒng)進行資源分配和調(diào)度的基本單位。
線程:是進程的一個執(zhí)行路徑,一個進程中至少有一個線程,進程中的多個線程共享進程的 資源。
雖然系統(tǒng)是把資源分給進程,但是CPU很特殊,是被分配到線程的,所以線程是CPU分配的基本單位。

二者關(guān)系:
一個進程中有多個線程,多個線程共享進程的堆和方法區(qū)資源,但是每個線程有自己的程序計數(shù)器和棧區(qū)域。
- 程序計數(shù)器:是一塊內(nèi)存區(qū)域,用來記錄線程當前要執(zhí)行的指令地址 。
- 棧:用于存儲該線程的局部變量,這些局部變量是該線程私有的,除此之外還用來存放線程的調(diào)用棧禎。
- 堆:是一個進程中最大的一塊內(nèi)存,堆是被進程中的所有線程共享的。
- 方法區(qū):則用來存放 NM 加載的類、常量及靜態(tài)變量等信息,也是線程共享的 。
二者區(qū)別:
- 進程:有獨立的地址空間,一個進程崩潰后,在保護模式下不會對其它進程產(chǎn)生影響。
- 線程:是一個進程中的不同執(zhí)行路徑。線程有自己的堆棧和局部變量,但線程之間沒有單獨的地址空間,一個線程死掉就等于整個進程死掉。
1)簡而言之,一個程序至少有一個進程,一個進程至少有一個線程.
2)線程的劃分尺度小于進程,使得多線程程序的并發(fā)性高。
3)另外,進程在執(zhí)行過程中擁有獨立的內(nèi)存單元,而多個線程共享內(nèi)存,從而極大地提高了程序的運行效率。
4)每個獨立的線程有一個程序運行的入口、順序執(zhí)行序列和程序的出口。但是線程不能夠獨立執(zhí)行,必須依存在應(yīng)用程序中,由應(yīng)用程序提供多個線程執(zhí)行控制。
5)從邏輯角度來看,多線程的意義在于一個應(yīng)用程序中,有多個執(zhí)行部分可以同時執(zhí)行。但操作系統(tǒng)并沒有將多個線程看做多個獨立的應(yīng)用,來實現(xiàn)進程的調(diào)度和管理以及資源分配。這就是進程和線程的重要區(qū)別
二、并發(fā)與并行
并發(fā):是指同一個時間段內(nèi)多個任務(wù)同時都在執(zhí)行,并且都沒有執(zhí)行結(jié)束。并發(fā)任務(wù)強調(diào)在一個時間段內(nèi)同時執(zhí)行,而一個時間段由多個單位時間累積而成,所以說并發(fā)的多個任務(wù)在單位時間內(nèi)不一定同時在執(zhí)行 。
并行:是說在單位時間內(nèi)多個任務(wù)同時在執(zhí)行 。
在多線程編程實踐中,線程的個數(shù)往往多于CPU的個數(shù),所以一般都稱多線程并發(fā)編程而不是多線程并行編程。
并發(fā)過程中常見的問題:
1、線程安全問題

多個線程同時操作共享變量1時,會出現(xiàn)線程1更新共享變量1的值,但是其他線程獲取到的是共享變量沒有被更新之前的值。就會導(dǎo)致數(shù)據(jù)不準確問題。
2、共享內(nèi)存不可見性問題

Java內(nèi)存模型(處理共享變量)
Java 內(nèi)存模型規(guī)定,將所有的變量都存放在主內(nèi)存中,當線程使用變量時,會把主內(nèi)存里面的變量復(fù)制到自己的工作空間或者叫作工作內(nèi)存,線程讀寫變量時操作的是自己工作內(nèi)存中的變量 。(如上圖所示)

(實際工作的java內(nèi)存模型)
上圖中所示是一個雙核 CPU 系統(tǒng)架構(gòu),每個核有自己的控制器和運算器,其中控制器包含一組寄存器和操作控制器,運算器執(zhí)行算術(shù)邏輔運算。CPU的每個核都有自己的一級緩存,在有些架構(gòu)里面還有一個所有CPU都共享的二級緩存。 那么Java內(nèi)存模型里面的工作內(nèi)存,就對應(yīng)這里的 Ll或者 L2 緩存或者 CPU 的寄存器
1、線程A首先獲取共享變量X的值,由于兩級Cache都沒有命中,所以加載主內(nèi)存中X的值,假如為0。然后把X=0的值緩存到兩級緩存,線程A修改X的值為1,然后將其寫入兩級Cache,并且刷新到主內(nèi)存。線程A操作完畢后,線程A所在的CPU的兩級Cache內(nèi)和主內(nèi)存里面的X的值都是l。
2、線程B獲取X的值,首先一級緩存沒有命中,然后看二級緩存,二級緩存命中了,所以返回X=1;到這里一切都是正常的,因為這時候主內(nèi)存中也是X=l。然后線程B修改X的值為2,并將其存放到線程2所在的一級Cache和共享二級Cache中,最后更新主內(nèi)存中X的值為2,到這里一切都是好的。
3、線程A這次又需要修改X的值,獲取時一級緩存命中,并且X=l這里問題就出現(xiàn)了,明明線程B已經(jīng)把X的值修改為2,為何線程A獲取的還是l呢?這就是共享變量的內(nèi)存不可見問題,也就是線程B寫入的值對線程A不可見。
synchronized 的內(nèi)存語義:
這個內(nèi)存語義就可以解決共享變量內(nèi)存可見性問題。進入synchronized塊的內(nèi)存語義是把在synchronized塊內(nèi)使用到的變量從線程的工作內(nèi)存中清除,這樣在synchronized塊內(nèi)使用到該變量時就不會從線程的工作內(nèi)存中獲取,而是直接從主內(nèi)存中獲取。退出synchronized塊的內(nèi)存語義是把在synchronized塊內(nèi)對共享變量的修改刷新到主內(nèi)存。會造成上下文切換的開銷,獨占鎖,降低并發(fā)性
Volatile的理解:
該關(guān)鍵字可以確保對一個變量的更新對其他線程馬上可見。當一個變量被聲明為volatile時,線程在寫入變量時不會把值緩存在寄存器或者其他地方,而是會把值刷新回主內(nèi)存。當其他線程讀取該共享變量時-,會從主內(nèi)存重新獲取最新值,而不是使用當前線程的工作內(nèi)存中的值。volatile的內(nèi)存語義和synchronized有相似之處,具體來說就是,當線程寫入了volatile變量值時就等價于線程退出synchronized同步塊(把寫入工作內(nèi)存的變量值同步到主內(nèi)存),讀取volatile變量值時就相當于進入同步塊(先清空本地內(nèi)存變量值,再從主內(nèi)存獲取最新值)。不能保證原子性
三、創(chuàng)建線程
1、繼承Thread類
重寫run方法:使用繼承方式的好處是,在run()方法內(nèi)獲取當前線程直接使用this就可以了,無須使用Thread.currentThread()方法;不好的地方是Java不支持多繼承,如果繼承了Thread類,那么就不能再繼承其他類。另外任務(wù)與代碼沒有分離,當多個線程執(zhí)行一樣的任務(wù)時需要多份任務(wù)代碼。
public class ThreadRuning extends Thread{
public ThreadRuning(String name){
//重寫構(gòu)造,可以對線程添加名字
super(name);
}
@Override
public void run() {
while(true){
System.out.println("good time");
//在run方法里,this代表當前線程
System.out.println(this);
}
}
public static void main(String[] args){
ThreadRuning threadRuning = new ThreadRuning("1111");
threadRuning.start();
}
}
2、實現(xiàn)Runable接口
實現(xiàn)run方法:解決繼承Thread的缺點,沒有返回值
public class RunableTest implements Runnable {
@Override
public void run() {
while (true) {
System.out.println("good time");
}
}
public static void main(String[] args) {
RunableTest runableTest1 = new RunableTest();
RunableTest runableTest2 = new RunableTest();
new Thread(runableTest1).start();
new Thread(runableTest1).start();
new Thread(runableTest2).start();
}
}
3、實現(xiàn)Callable接口
實現(xiàn)call方法:
public class CallTest implements Callable {
@Override
public Object call() throws Exception {
return "hello world";
}
public static void main(String[] args){
FutureTask<String> futureTask = new FutureTask<String>(new CallTest());
new Thread(futureTask).start();
try {
String result = futureTask.get();
System.out.println(result);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
使用繼承方式的好處是方便傳參,你可以在子類里面添加成員變量,通過set方法設(shè)置參數(shù)或者通過構(gòu)造函數(shù)進行傳遞,而如果使用Runnable方式,則只能使用主線程里面被聲明為final的變量。不好的地方是Java不支持多繼承,如果繼承了Thread類,那么子類不能再繼承其他類,而Runable則沒有這個限制。前兩種方式都沒辦法拿到任務(wù)的返回結(jié)果,但是Callable方式可以
四、Thread類詳解
1、線程特性
1、線程能被標記為守護線程,也可以是用戶線程
2、每個線程均分配一個name,默認為(Thread-自增數(shù)字)的組合
3、每個線程都有優(yōu)先級.高優(yōu)先級線程優(yōu)先于低優(yōu)先級線程執(zhí)行. 1-10,默認為5
4、main所在的線程組為main,構(gòu)造線程的時候沒有現(xiàn)實的指定線程組,線程組默認和父線程一樣
5、當線程中的run()方法代碼里面又創(chuàng)建了一個新的線程對象時,新創(chuàng)建的線程優(yōu)先級和父線程優(yōu)先級一樣.
6、當且僅當父線程為守護線程時,新創(chuàng)建的線程才會是守護線程.
7、當JVM啟動時,通常會有唯一的一個非守護線程(這一線程用于調(diào)用指定類的main()方法)
JVM會持續(xù)執(zhí)行線程直到下面情況某一個發(fā)生為止:
1)類運行時exit()方法被調(diào)用 且 安全機制允許此exit()方法的調(diào)用.
2)所有非守護類型的線程均已經(jīng)終止,or run()方法調(diào)用返回or在run()方法外部拋出了一些可傳播性的異常.
2、Init方法
/**
* Initializes a Thread.
* @param g 線程組
* @param target 執(zhí)行對象
* @param name 線程名
* @param stackSize 新線程棧大小,為0表示忽略
* @param acc用于繼承的訪問控制上下文
* @param inheritThreadLocals如果值為true,從構(gòu)造線程繼承可繼承線程局部變量的初始值
*/
private void init(ThreadGroup g, Runnable target, String name,
long stackSize, AccessControlContext acc,
boolean inheritThreadLocals) {
if (name == null) {
throw new NullPointerException("name cannot be null");
}
this.name = name;
Thread parent = currentThread();
SecurityManager security = System.getSecurityManager();
//如果所屬線程組為null
if (g == null) {
/* Determine if it's an applet or not */
/* If there is a security manager, ask the security manager
//如果有安全管理,查詢安全管理需要做的工作
what to do. */
if (security != null) {
g = security.getThreadGroup();
}
/* If the security doesn't have a strong opinion of the matter
use the parent thread group. */
//如果安全管理在線程所屬父線程組的問題上沒有什么強制的要求
if (g == null) {
g = parent.getThreadGroup();
}
}
/* checkAccess regardless of whether or not threadgroup is
explicitly passed in. */
//無論所屬線程組是否顯示傳入,都要進行檢查訪問.
g.checkAccess();
/*
* Do we have the required permissions?
*/
if (security != null) {
if (isCCLOverridden(getClass())) {
security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
}
}
g.addUnstarted();
this.group = g;
this.daemon = parent.isDaemon();//如果父線程為守護線程,則此線程也被 設(shè)置為守護線程.
this.priority = parent.getPriority();//獲取父進程的優(yōu)先級
if (security == null || isCCLOverridden(parent.getClass()))
this.contextClassLoader = parent.getContextClassLoader();
else
this.contextClassLoader = parent.contextClassLoader;
this.inheritedAccessControlContext =
acc != null ? acc : AccessController.getContext();
this.target = target;
setPriority(priority);
if (inheritThreadLocals && parent.inheritableThreadLocals != null)
this.inheritableThreadLocals =
ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
/* Stash the specified stack size in case the VM cares */
this.stackSize = stackSize;
/* Set thread ID 設(shè)置線程id*/
tid = nextThreadID();
}
3、構(gòu)造方法
所有的構(gòu)造方法都是調(diào)用init()方法
public Thread() {
init(null, null, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target) {
init(null, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(Runnable target, AccessControlContext acc) {
init(null, target, "Thread-" + nextThreadNum(), 0, acc, false);
}
public Thread(ThreadGroup group, Runnable target) {
init(group, target, "Thread-" + nextThreadNum(), 0);
}
public Thread(String name) {
init(null, null, name, 0);
}
public Thread(ThreadGroup group, String name) {
init(group, null, name, 0);
}
public Thread(Runnable target, String name) {
init(null, target, name, 0);
}
public Thread(ThreadGroup group, Runnable target, String name,
long stackSize) {
init(group, target, name, stackSize);
}
4、線程狀態(tài)
public enum State {
NEW,
RUNNABLE,
BLOCKED,
WAITING,
TIMED_WAITING,
TERMINATED;
}
NEW:狀態(tài)是指線程剛創(chuàng)建, 尚未啟動
RUNNABLE:狀態(tài)是線程正在正常運行中, 當然可能會有某種耗時計算/IO等待的操作/CPU時間片切換等, 這個狀態(tài)下發(fā)生的等待一般是其他系統(tǒng)資源, 而不是鎖, Sleep等
BLOCKED:這個狀態(tài)下, 是在多個線程有同步操作的場景, 比如正在等待另一個線程的synchronized 塊的執(zhí)行釋放, 或者可重入的 synchronized塊里別人調(diào)用wait() 方法, 也就是這里是線程在等待進入臨界區(qū)
WAITING:這個狀態(tài)下是指線程擁有了某個鎖之后, 調(diào)用了他的wait方法, 等待其他線程/鎖擁有者調(diào)用 notify / notifyAll 一遍該線程可以繼續(xù)下一步操作, 這里要區(qū)分 BLOCKED 和 WATING 的區(qū)別, 一個是在臨界點外面等待進入, 一個是在理解點里面wait等待別人notify, 線程調(diào)用了join方法 join了另外的線程的時候, 也會進入WAITING狀態(tài), 等待被他join的線程執(zhí)行結(jié)束
TIMED_WAITING:這個狀態(tài)就是有限的(時間限制)的WAITING, 一般出現(xiàn)在調(diào)用wait(long), join(long)等情況下, 另外一個線程sleep后, 也會進入TIMED_WAITING狀態(tài)
TERMINATED:這個狀態(tài)下表示 該線程的run方法已經(jīng)執(zhí)行完畢了, 基本上就等于死亡了(當時如果線程被持久持有, 可能不會被回收)
(在很多文章中都寫了running狀態(tài),其實源碼里面只有六種的,當自己寫一個線程通過while一直保持執(zhí)行狀態(tài),然后使用jconsole工具去查看線程的狀態(tài),確實是Runable狀態(tài))

Api文檔是這么說的:

其實我們可以理解為兩種狀態(tài),一個是running,表示正在執(zhí)行,一個是runable,表示準備就緒了,只是在等待其他的系統(tǒng)資源。然后我們就可以理解如下圖

5、Start方法
public synchronized void start() {
/**
* 此方法并不會被主要方法線程or由虛擬機創(chuàng)建的系統(tǒng)組線程所調(diào)用.
* 任何向此方法添加的新功能方法在未來都會被添加到虛擬機中.
* 0狀態(tài)值代表了NEW的狀態(tài).
*/
if (threadStatus != 0) // 線程不能重復(fù)start
throw new IllegalThreadStateException();
/* Notify the group that this thread is about to be started
* so that it can be added to the group's list of threads
* and the group's unstarted count can be decremented. */
group.add(this);
boolean started = false;
try {
start0(); //本地方法
started = true;
} finally {
try {
if (!started) {
group.threadStartFailed(this);
}
} catch (Throwable ignore) {
/* do nothing. If start0 threw a Throwable then
it will be passed up the call stack */
}
}
}
private native void start0();
6、yield方法
public static native void yield();
是一個本地方法,提示線程調(diào)度器當前線程愿意放棄當前CPU的使用。如果當前資源不緊張,調(diào)度器可以忽略這個提示。本質(zhì)上線程狀態(tài)一直是RUNNABLE,但是我可以理解為RUNNABLE到RUNNING的轉(zhuǎn)換
7、sleep方法
/**
* 此方法會引起當前執(zhí)行線程sleep(臨時停止執(zhí)行)指定毫秒數(shù).
* 此方法的調(diào)用不會引起當前線程放棄任何監(jiān)聽器(monitor)的所有權(quán)(ownership).
*/
public static native void sleep(long millis) throws InterruptedException;
public static void sleep(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
sleep(millis);
}
sleep方法,有一個重載方法,sleep方法會釋放cpu的時間片,但是不會釋放鎖,調(diào)用sleep()之后從RUNNABLE狀態(tài)轉(zhuǎn)為TIMED_WAITING狀態(tài)
8、join方法
/**
* 最多等待參數(shù)millis(ms)時長當前線程就會死亡.參數(shù)為0時則要持續(xù)等待.
* 此方法在實現(xiàn)上:循環(huán)調(diào)用以this.isAlive()方法為條件的wait()方法.
* 當線程終止時notifyAll()方法會被調(diào)用.
* 建議應(yīng)用程序不要在線程實例上使用wait,notify,notifyAll方法.
*/
public final synchronized void join(long millis)
throws InterruptedException {
long base = System.currentTimeMillis();
long now = 0;
//如果等待時間<0,則拋出異常
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
//如果等待時間為0
if (millis == 0) {
while (isAlive()) {
wait(0);
}
} else {
while (isAlive()) {
long delay = millis - now;
if (delay <= 0) {
break;
}
wait(delay);
now = System.currentTimeMillis() - base;
}
}
}
//等待時間單位為納秒,其它解釋都和上面方法一樣
public final synchronized void join(long millis, int nanos)
throws InterruptedException {
if (millis < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos >= 500000 || (nanos != 0 && millis == 0)) {
millis++;
}
join(millis);
}
//方法功能:等待一直到線程死亡.
public final void join() throws InterruptedException {
join(0);
}
join某個線程A,會使得線程B進入等待,知道線程A結(jié)束,或者到達給定的時間,那么期間線程B處于BLOCKED的狀態(tài),而不是線程A
五、其他方法
接下來聊一下Object類的wait,notify和notifyAll方法
1、wait方法
public final native void wait(long timeout) throws InterruptedException; //本地方法 參數(shù)為毫秒
public final void wait(long timeout, int nanos) throws InterruptedException {//參數(shù)為毫秒和納秒
if (timeout < 0) {
throw new IllegalArgumentException("timeout value is negative");
}
if (nanos < 0 || nanos > 999999) {
throw new IllegalArgumentException(
"nanosecond timeout value out of range");
}
if (nanos > 0) {
timeout++;
}
wait(timeout);
}
public final void wait() throws InterruptedException {
wait(0);
}
可見wait()和wait(long timeout, int nanos)都在在內(nèi)部調(diào)用了wait(long timeout)方法。
下面主要是說說wait(long timeout)方法
wait方法會引起當前線程阻塞,直到另外一個線程在對應(yīng)的對象上調(diào)用notify或者notifyAll()方法,或者達到了方法參數(shù)中指定的時間。
調(diào)用wait方法的當前線程一定要擁有對象的監(jiān)視器鎖。
wait方法會把當前線程T放置在對應(yīng)的object上的等待隊列中,在這個對象上的所有同步請求都不會得到響應(yīng)。線程調(diào)度將不會調(diào)用線程T,在以下四件事發(fā)生之前,線程T會被喚醒(線程T是在其代碼中調(diào)用wait方法的那個線程)
1、當其他的線程在對應(yīng)的對象上調(diào)用notify方法,而在此對象的對應(yīng)的等待隊列中將會任意選擇一個線程進行喚醒。
2、其他的線程在此對象上調(diào)用了notifyAll方法
3、其他的線程調(diào)用了interrupt方法來中斷線程T
4、等待的時間已經(jīng)超過了wait中指定的時間。如果參數(shù)timeout的值為0,不是指真實的等待時間是0,而是線程等待直到被另外一個線程喚醒為止。
被喚醒的線程T會被從對象的等待隊列中移除并且重新能夠被線程調(diào)度器調(diào)度。之后,線程T會像平常一樣跟其他的線程競爭獲取對象上的鎖;一旦線程T獲得了此對象上的鎖,那么在此對象上的所有同步請求都會恢復(fù)到之前的狀態(tài),也就是恢復(fù)到wait被調(diào)用的情況下。然后線程T從wait方法的調(diào)用中返回。因此,當從wait方法返回時,對象的狀態(tài)以及線程T的狀態(tài)跟wait方法被調(diào)用的時候一樣。
線程在沒有被喚醒,中斷或者時間耗盡的情況下仍然能夠被喚醒,這叫做偽喚醒。雖然在實際中,這種情況很少發(fā)生,但是程序一定要測試這個能夠喚醒線程的條件,并且在條件不滿足時,線程繼續(xù)等待。換言之,wait操作總是出現(xiàn)在循環(huán)中,就像下面這樣:
synchronized(對象){
while(條件不滿足){
對象.wait();
}
對應(yīng)的邏輯處理
}
如果當前的線程被其他的線程在當前線程等待之前或者正在等待時調(diào)用了interrupt()中斷了,那么會拋出InterruptedExcaption異常。直到這個對象上面的鎖狀態(tài)恢復(fù)到上面描述的狀態(tài)以前,這個異常是不會拋出的。
要注意的是,wait方法把當前線程放置到這個對象的等待隊列中,解鎖也僅僅是在這個對象上;當前線程在其他對象上面上的鎖在當前線程等待的過程中仍然持有其他對象的鎖。
這個方法應(yīng)該僅僅被持有對象監(jiān)視器的線程調(diào)用。 wait(long timeout, int nanos)方法的實現(xiàn)中只要nanos大于0,那么timeout時間就加上一毫秒,主要是更精確的控制時間,其他的跟wait(long timeout)一樣
2、notify方法
public final native void notify(); //本地方法
通知可能等待該對象的對象鎖的其他線程。由JVM(與優(yōu)先級無關(guān))隨機挑選一個處于wait狀態(tài)的線程。
在調(diào)用notify()之前,線程必須獲得該對象的對象級別鎖
執(zhí)行完notify()方法后,不會馬上釋放鎖,要直到退出synchronized代碼塊,當前線程才會釋放鎖
notify()一次只隨機通知一個線程進行喚醒
3、notifyAll()方法
public final native void notifyAll();//本地方法
和notify()差不多,只不過是使所有正在等待池中等待同一共享資源的全部線程從等待狀態(tài)退出,進入可運行狀態(tài)
讓它們競爭對象的鎖,只有獲得鎖的線程才能進入就緒狀態(tài)
每個鎖對象有兩個隊列:就緒隊列和阻塞隊列
- 就緒隊列:存儲將要獲得鎖的線程
- 阻塞隊列:存儲被阻塞的線程
六、實例
1、sleep
public class ThreadDemo1 {
public static void main(String[] args) {
MyThread mt = new MyThread();
//推薦
MyRunnable mr = new MyRunnable();
Thread t2 = new Thread(mr);
mt.start();//啟動線程
t2.start();
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "-" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
/**
* 實現(xiàn)線程的第一種方式:繼承thread類
*/
class MyThread extends Thread {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
if (this.isInterrupted()) {
break;
}
System.out.println(Thread.currentThread().getName() + "-" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
this.interrupt();
}
}
}
}
/**
* 實現(xiàn)線程的第二種方式:實現(xiàn)Runnable接口
*/
class MyRunnable implements Runnable {
@Override
public void run() {
for (int i = 0; i < 100; i++) {
System.out.println(Thread.currentThread().getName() + "-" + i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
2、join和中斷(推薦用標記中斷)
public class ThreadDemo2 {
public static void main(String[] args){
MyRunable2 mr2 = new MyRunable2();
Thread t = new Thread(mr2);
// t.start();
MyRunable3 mr3 = new MyRunable3();
Thread t2 = new Thread(mr3);
t2.start();
for (int i = 0; i < 50; i++) {
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
if(i==20){
// try { //這些打開用來測試join
// t.join();//讓t線程執(zhí)行完畢
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// t.interrupt();//中斷線程,只是作了一個中斷標記,用于測試interrupt方法
mr3.flag = false; //用于測試標記中斷
}
}
}
}
class MyRunable2 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
if(Thread.interrupted()){//測試中斷狀態(tài),此方法會把中斷狀態(tài)清除
//....
break;
}
System.out.println(Thread.currentThread().getName()+"--"+i);
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
Thread.currentThread().interrupt();
}
}
}
}
<br>//標記中斷
class MyRunable3 implements Runnable{
public boolean flag = true;
public MyRunable3(){
flag = true;
}
@Override
public void run() {
int i=0;
while(flag){
System.out.println(Thread.currentThread().getName()+"==="+(i++));
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
3、優(yōu)先級和守護進程
public class ThreadDemo3 {
public static void main(String[] args){
MyRunnable4 mr4 = new MyRunnable4();
Thread t = new Thread(mr4);
t.setName("Thread-t");
//優(yōu)先級高可以提高該線程搶點CPU時間片的概率大
t.setPriority(Thread.MAX_PRIORITY);
//線程可以分成守護線程和 用戶線程,當進程中沒有用戶線程時,JVM會退出
t.setDaemon(true);//把線程設(shè)置為守護線程
System.out.println(t.isAlive());
t.start();
System.out.println(t.isAlive());
for (int i = 0; i < 50; i++) {
System.out.println("main--"+i);
try {
Thread.sleep(200);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i==5){
Thread.yield();//讓出本次CPU執(zhí)行時間片
}
}
}
}
class MyRunnable4 implements Runnable{
@Override
public void run() {
for (int i = 0; i < 50; i++) {
System.out.println("--"+i);
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
4、生產(chǎn)者與消費者
定義一個接口:
package threadtest.procon;
public interface AbstractStorage {
void consume(int num);
void product(int num);
}
定義一個類實現(xiàn)接口,用于存放生產(chǎn)的東西
package threadtest.procon;
import java.util.LinkedList;
public class Storage implements AbstractStorage{
private final int MAX_SIZE = 100;
private LinkedList list = new LinkedList();
@Override
public void consume(int num) {
synchronized (list){
while (list.size()<num){
System.out.println("【要消費的產(chǎn)品數(shù)量】:" + num + "\t【庫存量】:"+ list.size() + "\t暫時不能執(zhí)行消費任務(wù)!");
try {
list.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(int i=0;i<num;i++){
list.remove();
}
System.out.println("【已經(jīng)消費產(chǎn)品數(shù)】:" + num + "\t【現(xiàn)倉儲量為】:" + list.size());
list.notifyAll();
}
}
@Override
public void product(int num) {
synchronized (list){
while(list.size()+num > MAX_SIZE){
System.out.println("【要生產(chǎn)的產(chǎn)品數(shù)量】:" + num + "\t【庫存量】:" + list.size() + "\t暫時不能執(zhí)行生成任務(wù)!");
try {
list.wait(2000);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
for(int i=0;i<num;i++){
list.add(new Object());
}
System.out.println("【已經(jīng)生產(chǎn)產(chǎn)品數(shù)】:" + num + "\t【現(xiàn)倉儲量為】:" + list.size());
list.notifyAll();
}
}
}
生產(chǎn)者類:
package threadtest.procon;
public class Producer extends Thread {
private int num;
public AbstractStorage abstractStorage;
public Producer(AbstractStorage abstractStorage){
this.abstractStorage = abstractStorage;
}
public void setNum(int num) {
this.num = num;
}
public void produce(int num){
abstractStorage.product(num);
}
@Override
public void run() {
produce(num);
}
}
消費者類:
package threadtest.procon;
public class Consumer extends Thread {
private int num;
public AbstractStorage abstractStorage;
public Consumer(AbstractStorage abstractStorage){
this.abstractStorage = abstractStorage;
}
public void setNum(int num){
this.num = num;
}
public void consume(int num){
this.abstractStorage.consume(num);
}
@Override
public void run() {
consume(num);
}
}
測試類:
package threadtest.procon;
public class Test {
public static void main(String[] args){
AbstractStorage abstractStorage = new Storage();
// 生產(chǎn)者對象
Producer p1 = new Producer(abstractStorage);
Producer p2 = new Producer(abstractStorage);
Producer p3 = new Producer(abstractStorage);
Producer p4 = new Producer(abstractStorage);
Producer p5 = new Producer(abstractStorage);
Producer p6 = new Producer(abstractStorage);
Producer p7 = new Producer(abstractStorage);
// 消費者對象
Consumer c1 = new Consumer(abstractStorage);
Consumer c2 = new Consumer(abstractStorage);
Consumer c3 = new Consumer(abstractStorage);
// 設(shè)置生產(chǎn)者產(chǎn)品生產(chǎn)數(shù)量
p1.setNum(10);
p2.setNum(20);
p3.setNum(30);
p4.setNum(40);
p5.setNum(30);
p6.setNum(20);
p7.setNum(80);
// 設(shè)置消費者產(chǎn)品消費數(shù)量
c1.setNum(50);
c2.setNum(70);
c3.setNum(20);
c1.start();
c2.start();
c3.start();
p1.start();
p2.start();
p3.start();
p4.start();
p5.start();
p6.start();
p7.start();
}
}

以上就是詳解Java多線程與并發(fā)的詳細內(nèi)容,更多關(guān)于Java 多線程 并發(fā)的資料請關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
SpringBoot與velocity的結(jié)合的示例代碼
本篇文章主要介紹了SpringBoot與velocity的結(jié)合的示例代碼,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-03-03
使用RestTemplate 調(diào)用遠程接口上傳文件方式
這篇文章主要介紹了使用RestTemplate 調(diào)用遠程接口上傳文件方式,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-09-09
Spring BeanPostProcessor接口使用詳解
本篇文章主要介紹了Spring BeanPostProcessor接口使用詳解,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧2018-01-01
Java中字符數(shù)組和字符串與StringBuilder和字符串轉(zhuǎn)換的講解
今天小編就為大家分享一篇關(guān)于Java中字符數(shù)組和字符串與StringBuilder和字符串轉(zhuǎn)換的講解,小編覺得內(nèi)容挺不錯的,現(xiàn)在分享給大家,具有很好的參考價值,需要的朋友一起跟隨小編來看看吧2019-03-03

