文章目錄
- 1. Intellij IDEA 軟體使用
- 2. 多執行緒 的概念
- 3. 方式一 繼承Thread 執行緒
- 3.1 繼承Thread 創建執行緒
- 3.2 Thread方式 兩個問題
- 3.3 Thread匿名物件創建執行緒
- 3.4 Thread類常用的方法
- 3.5 執行緒的調度
- 4. 方式二 實作Runnable介面 創建多執行緒
- 5. 兩種創建多執行緒方式的 對比
- 6. JVM 和 行程執行緒關系
- 7. 執行緒的 生命周期
- 8. 執行緒的 安全問題
- 9. 使用同步代碼塊解決 實作Runnable介面的 執行緒安全問題
- 10. 使用同步代碼塊解決 繼承Thread的 執行緒安全問題
- 11. 使用同步方法解決 實作Runnable介面的 執行緒安全問題
- 12. 使用同步方法解決 繼承Thread類的 執行緒安全問題
- 13. 死鎖 問題
- 14. Lock鎖的方式解決 多執行緒安全問題
- 15. 執行緒的通信
- 16. 創建執行緒方式三 :實作Callable介面
- 17. 方式四:使用執行緒池創建(開發常用)
1. Intellij IDEA 軟體使用
和eclipse不同的簡寫方式:
- psvm = main方法,
- sout = System.out.println();
public class TestStart {
//psvm等于下面的main方法
public static void main(String[] args) {
//"hello,world".sout 就等于下面的內容
System.out.println("hello , world");
}
}
eclipse 和 IDEA的區別:

IDEA軟體的對于一些快捷鍵,查看setting =》 Keymap查看相應的快捷鍵就可以了,
IDEA軟體對于快速填充的識別符號修改查看等等:


2. 多執行緒 的概念
程式,行程,執行緒的概念:

>傳統行程 和 多執行緒行程:


- 方法區:包含靜態方法
- 堆:包含各種不同的物件,
- 方法區和堆是一個行程一份,換句話說,該行程中的執行緒都要共享一個方法區和堆,
- 虛擬機堆疊和程式計數器,每一個執行緒都會有一個虛擬機堆疊和程式計數器,每個執行緒一份,
單核CPU 和 多核CPU , 并行 和 并發 的概念!!!

3. 方式一 繼承Thread 執行緒
3.1 繼承Thread 創建執行緒
Java語言通過java.lang.Thread類來實作多執行緒的!

上圖對應下面代碼:
(注意:run()方法是重寫Thread類后的run()方法,將此執行緒執行的操作宣告在run()方法中)

Thread創建執行緒格式如下:
public class TestStart{
/*
多執行緒創建:方式一繼承于Thread類
1. 創建一個繼承于Thread類的子類
2. 重寫Thread類的run()方法 --> 將此執行緒執行的操作宣告在run()方法中
3. 創建Thread類的子類的物件
4. 通過此物件呼叫start()方法
*/
public static void main(String[] args) {
//new MyThread(); alt + enter 可以自動補全宣告內容
MyThread my = new MyThread();
//start()方法作用就是啟動當前執行緒,呼叫當前執行緒的run()方法,
my.start();
//如下操作仍然是在main執行緒中執行的
//呼叫玩start以后,該my執行緒已經開始執行,但是主執行緒依然向下執行!!!
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println("主執行緒的i:" + i);
}
}
//這樣就相當于my子執行緒執行的同時,也不影響主執行緒的執行,
}
}
class MyThread extends Thread{
@Override
public void run(){
for (int i = 0;i<100;i++){
if(i % 2 ==0){
System.out.println(i);
}
}
}
}
3.2 Thread方式 兩個問題
問題一:我們不能通過直接呼叫run()方法啟動執行緒,
如果直接呼叫run()方法,也僅僅是呼叫了該run()方法,并沒有開啟執行緒,自始至終都是main執行緒而已,
我們通過使用Thread.currentThread().getName()來獲取當前執行緒的名字,
public class TestStart{
/*
多執行緒創建:方式一繼承于Thread類
1. 創建一個繼承于Thread類的子類
2. 重寫Thread類的run()方法 --> 將此執行緒執行的操作宣告在run()方法中
3. 創建Thread類的子類的物件
4. 通過此物件呼叫start()方法
*/
public static void main(String[] args) {
//new MyThread(); alt + enter 可以自動補全宣告內容
MyThread my = new MyThread();
my.start();
//呼叫玩start以后,該my執行緒已經開始執行,但是主執行緒依然向下執行!!!
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
//這樣就相當于my子執行緒執行的同時,也不影響主執行緒的執行,
}
}
class MyThread extends Thread{
@Override
public void run(){
for (int i = 0;i<100;i++){
if(i % 2 ==0){
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}

問題二:再啟動一個相同的執行緒,會怎么樣?
會拋出例外!
其實注釋中就有介紹:Throws:
IllegalThreadStateException – if the thread was already started.

因此,我們不能同時呼叫相同的執行緒,而是定義不同的物件呼叫不同的執行緒,
3.3 Thread匿名物件創建執行緒
匿名物件呼叫執行緒:
package com.holmes.java;
public class ThreadDemo {
public static void main(String[] args) {
//匿名物件也可以用來呼叫多執行緒
new Thread(){
@Override
public void run(){
for (int i=0;i<100;i++){
if (i % 2 == 0)
System.out.println(Thread.currentThread().getName());
}
}
}.start();
}
}
3.4 Thread類常用的方法
Thread類常用的方法:


設定執行緒名稱兩種方式:
- 自己setName()方法設定執行緒名字,
- 通過繼承父類構造器方法來設定執行緒名字,

注意的是:因為像sleep,join等等方法都是拋出例外的,因此我們要設定throws或try-catch例外!
此外,再說一點例外范圍問題,子類的例外范圍一定小于父類的范圍例外!!

package com.holmes.java;
/*
1. start()方法:啟動當前執行緒;呼叫當前執行緒的run()方法,
2. run()方法:重寫此方法,將創建的執行緒要執行的操作宣告在此方法中,
3. currentThread()方法:靜態方法,回傳執行當前代碼的執行緒,就是當前創建的執行緒物件,
4. getName()方法:獲取當前執行緒的名字,
5. setName()方法: 設定當前執行緒的名字,
6. yield()方法: 釋放當前CPU的執行權,當然有可能下一刻又分配到當前的執行緒,
7. join()方法: 在執行緒a中呼叫執行緒b的join()方法,此時的執行緒a就進入阻塞狀態,直到執行緒b完全執行完以后執行緒a才結束阻塞狀態,
再往后就看系統怎么分配資源了,
8. stop()方法:已過時,當執行此方法時,強制結束當前執行緒,
9. sleep(long millitime)方法:讓當前執行緒“睡眠,也就是阻塞”指定的millitime毫秒(在睡眠的時間,執行緒是阻塞狀態),
10. isAlive()方法: 判斷當前執行緒是否存活,
*/
public class ThreadMethodTest {
public static void main(String[] args) {
//第一種:通過構造器傳遞String引數來命名,這在Thread原始碼中是具備的!
ThreadMethods tm = new ThreadMethods("執行緒一");
//第二種:可以自己給tm執行緒重新命名
//tm.setName("執行緒一");
tm.start();
//怎樣給主執行緒命名呢?
//直接通過Thread.currentThread().setName("XXX")設定就可以了
Thread.currentThread().setName("主執行緒");
for (int i=0;i<100;i++){
if (i % 2 == 0)
System.out.println(Thread.currentThread().getName() + ":" +i);
if (i == 20){
try {
//join()方法使當前執行緒進入阻塞狀態,等待另一個執行緒呼叫結束,再進行,
tm.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//判斷tm執行緒是否還存在
System.out.println(tm.isAlive());
}
}
class ThreadMethods extends Thread{
@Override
public void run(){
for (int i=0;i<100;i++){
if (i % 2 == 0){
try {
sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" +i);
}
//if(i % 20 == 0){
//釋放當前CPU的執行權,當然有可能下一刻又分配到當前的執行緒
//yield();
//}
}
}
public ThreadMethods(String name){
//通過繼承Thread的構造器方法,來命名執行緒名字
super(name);
}
}
3.5 執行緒的調度


執行緒優先級:
- MAX_PRIORITY :10
- MIN_PRIORITY :1
- NORM_PRIORITY :5

如何獲取和設定當前執行緒的優先級:
- getPriority() :獲取執行緒的優先級,
- setPriority(int p) :設定執行緒的優先級,
注意:高優先級的執行緒要搶占低優先級執行緒cpu執行權,但是只是從概率上來講,并不是從一開始只有當高優先級執行緒執行完以后再執行低優先級,
package com.holmes.java;
public class ThreadMethodTest {
public static void main(String[] args) {
ThreadMethods tm = new ThreadMethods("執行緒一");
//設定執行緒優先級
tm.setPriority(Thread.MAX_PRIORITY);
tm.start();
//怎樣給主執行緒命名呢?
//直接通過Thread.currentThread().setName("XXX")設定就可以了
Thread.currentThread().setName("主執行緒");
for (int i=0;i<100;i++){
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + Thread.currentThread().getPriority() +"--"+ i);
}
if (i == 20){
try {
//join()方法使當前執行緒進入阻塞狀態,等待另一個執行緒呼叫結束,再進行,
tm.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
//判斷tm執行緒是否還存在
System.out.println(tm.isAlive());
}
}
class ThreadMethods extends Thread{
@Override
public void run(){
for (int i=0;i<100;i++){
if (i % 2 == 0){
//getPriority()獲取執行緒優先級,提示這里不寫TThread.currentThread(),默認就是當前的this,還是該執行緒,
System.out.println(Thread.currentThread().getName() + ":" + getPriority() + "--"+i);
}
}
}
public ThreadMethods(String name){
//通過繼承Thread的構造器方法,來命名執行緒名字
super(name);
}
}
4. 方式二 實作Runnable介面 創建多執行緒
package com.holmes.java;
/*
創建多執行緒方式二:實作Runnable介面
1. 創建一個實作了Runnable介面的類,
2. 實作類去實作Runnable中的抽象方法:run()方法,
3. 創建實作類的物件,
4. 將此物件作為引數傳遞到Thread類的構造器中,創建Thread類的物件,
5. 通過Thread類的物件呼叫start()方法,
*/
public class ThreadTest2 {
public static void main(String[] args) {
MyThead3 mt3 = new MyThead3();
//將實作類物件作為引數傳遞給Thread構造器
//整體上而言就是一個多型的效果
Thread th1 = new Thread(mt3);
//此時這個執行緒是th1,而不是mt3!
//而這里的start()方法:開啟執行緒呼叫run()方法,這里的run()方法自然也是Thread中的run()方法,
//之所以還是執行的MyThread3得看原始碼,如下面圖:
th1.setName("執行緒一");
th1.start();
Thread th2 = new Thread(mt3);
th2.setName("執行緒二");
th2.start();
}
}
class MyThead3 implements Runnable{
@Override
public void run() {
for (int i=0;i<100;i++){
if (i%2==0){
System.out.println(Thread.currentThread().getName() +":"+ i);
}
}
}
}
start()方法:開啟執行緒呼叫run()方法,這里的run()方法自然也是Thread中的run()方法,
問題來了,既然是Thread中得run()方法,為什么最后還是呼叫得實作類MyThread中的run()方法呢?看原始碼,

5. 兩種創建多執行緒方式的 對比
- 首先,一個繼承,一個實作介面,
為什么開發中,優先選擇:實作Runable介面的方式?
- 實作的方式沒有類的單繼承性的局限性,
- 實作的方式更適合來處理多個執行緒有共享資料的情況,
相同點:兩種方式都需要重寫run()方法,將執行緒要執行的邏輯宣告在run()中,
通過看Thread原始碼,差不多也都知道,它們的聯系,
6. JVM 和 行程執行緒關系


執行緒分為兩種:一種守護執行緒,一種用戶執行緒,
通過呼叫thread.setDaemon(true),可以將用戶執行緒變成一個守護執行緒:

7. 執行緒的 生命周期
Thread.State類定義了執行緒的幾種狀態:
新建,就緒,運行,阻塞,死亡

生命周期流程如下:

8. 執行緒的 安全問題
出現執行緒安全的關鍵所在,就是多執行緒是否存在共享資料,

執行緒的安全問題:
package com.holmes.java02;
public class WindowTest2 {
public static void main(String[] args) {
MyThread4 m = new MyThread4();
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.setName("視窗一");
t2.setName("視窗二");
t3.setName("視窗三");
t1.start();
t2.start();
t3.start();
}
}
class MyThread4 implements Runnable{
private static int ticket = 100;
@Override
public void run() {
while (true){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() +": 買票,票號為:" + ticket);
ticket--;
}else {
break;
}
}
}
}
就取票而言,三個視窗(t1,t2,t3)如果都進入阻塞狀態,而票最后僅僅剩余1張,隨后三個視窗進入就緒狀態就變成了下面的情形:
(也就是票成了0和成了負數的這種錯誤情況!就是出現了重票或者錯票的問題,)

這就是一種執行緒安全問題!
為什么出現這種執行緒安全問題?
當某個執行緒操作車票的程序中,尚未操作完成時(像上面的sleep()方法阻塞了),其他執行緒接著參與進來,也來操作車票,就會出現執行緒的安全問題,
解決方式兩種:一種是同步代碼塊 ,一種是同步方法,
9. 使用同步代碼塊解決 實作Runnable介面的 執行緒安全問題
如何解決這種執行緒安全問題?
當一個執行緒a在操作共享資料(車票ticket),其他執行緒不能參與進來,直到執行緒a操作完ticket時,其他執行緒才可以開始操作ticket,這種情況即使執行緒a出現了阻塞,也不能被改變,
在Java中,我們通過同步機制,來解決執行緒的安全問題,
方式一:同步代碼塊

上面synchronized的同步監視器是什么? 俗稱:鎖
對于Runnable實作執行緒,同步監視器用this時最好的,也是最簡單的,
任何一個類的物件,都可以充當鎖,充當同步監視器,意思就是只要是物件都可以,他只不過起到了一個鎖的效果,
這個同步代碼塊的方式,要求多個執行緒必須要共用同一把鎖,
package com.holmes.java02;
public class WindowTest2 {
public static void main(String[] args) {
MyThread4 m = new MyThread4();
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.setName("視窗一");
t2.setName("視窗二");
t3.setName("視窗三");
t1.start();
t2.start();
t3.start();
}
}
class MyThread4 implements Runnable{
private static int ticket = 100;
//這里定義的obj同步監視器,不能定義到run()方法中,否則三個執行緒一呼叫,就是三個鎖在執行了!
Object obj = new Object();
@Override
public void run() {
while (true){
//這里的synchronized就是同步代碼塊,來解決執行緒安全的問題(重票,錯票)
//這里的obj也就是鎖,必須是所有執行緒共用的一把鎖,不能不同!!
//對于Runnable實作執行緒,同步監視器用this時最好的,也是最簡單的,
//synchronized(this){ ... }
synchronized (obj) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 買票,票號為:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
該方式的優缺點:
- 解決了執行緒的安全問題,
- 但是再操作同步代碼時,只能有一個執行緒參與,其他執行緒等待,相當于是一個單執行緒程序,效率低,
10. 使用同步代碼塊解決 繼承Thread的 執行緒安全問題
通過繼承Thread類創建的執行緒安全問題,就不能按照上面實作Runnable創建的執行緒安全解決方式來解決,
原因就是它是創建了多個物件執行緒,每個物件執行緒都對應了一把鎖,換句話說,這三個執行緒物件,都對應自己的一把鎖,不是同步的,
怎么解決?
加一個static就可以了,把對應的同步監測器(鎖),設定為靜態共享就可以了,
package com.holmes.java02;
/*
三個視窗買票,總票數為100張
*/
public class WindowTest {
public static void main(String[] args) {
Window w1 = new Window();
Window w2 = new Window();
Window w3 = new Window();
w1.setName("視窗1");
w2.setName("視窗2");
w3.setName("視窗3");
w1.start();
w2.start();
w3.start();
}
}
class Window extends Thread{
private static int ticket = 100;
//通過加static,把obj變成每個物件共享,這樣3個執行緒就還是共用一把鎖,
private static Object obj = new Object();
@Override
public void run(){
//用這種方式是可以并且最簡便的,
//synchronized (Window.class){...}
//這里的obj,三個執行緒必須都相同!
synchronized (obj){
while (true) {
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 買票,票號為:" + ticket);
ticket--;
} else {
break;
}
}
}
}
}
對于Runnable實作執行緒,同步監視器用this時最好的,也是最簡單的,那么對于繼承Thread類執行緒,同步監視器用XXX.class(當前類) ,是最好的,例如上面的:Window.class (像),
11. 使用同步方法解決 實作Runnable介面的 執行緒安全問題
在Java中,我們通過同步機制,來解決執行緒的安全問題,
方式二:同步方法
給方法賦予一個關鍵字:synchronized,
package com.holmes.java02;
/*
使用同步方法來解決實作Runnable介面的執行緒安全問題
*/
public class WindowTest03 {
public static void main(String[] args) {
Window3 m = new Window3();
Thread t1 = new Thread(m);
Thread t2 = new Thread(m);
Thread t3 = new Thread(m);
t1.setName("視窗一");
t2.setName("視窗二");
t3.setName("視窗三");
t1.start();
t2.start();
t3.start();
}
}
class Window3 implements Runnable{
private static int ticket = 100;
@Override
//這里不適合直接給run()方法操作同步,因為3個執行緒共享代碼僅僅是while里面的內容,不能多余也不能少!
public void run() {
while (true){
show();
}
}
//需要注意的是synchronized在這里也是有同步監視器,默認就是this,當前物件,
private synchronized void show(){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 買票,票號為:" + ticket);
ticket--;
}
}
}
12. 使用同步方法解決 繼承Thread類的 執行緒安全問題
繼承Thread類的執行緒,變數和方法都要求是靜態的!
package com.holmes.java02;
/*
使用同步方法解決 繼承Thread類的 執行緒安全問題
*/
public class WindowTest4 {
public static void main(String[] args) {
Window2 w1 = new Window2();
Window2 w2 = new Window2();
Window2 w3 = new Window2();
w1.setName("視窗1");
w2.setName("視窗2");
w3.setName("視窗3");
w1.start();
w2.start();
w3.start();
}
}
class Window2 extends Thread{
//繼承thread類的執行緒,必須都要靜態才對
private static int ticket = 100;
@Override
public void run(){
while (true) {
show();
}
}
//繼承thread類的執行緒,必須都要靜態才對
//但是這里的同步監視器就不是this了,而是當前的類Window4.class!
private static synchronized void show(){
if (ticket > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ": 買票,票號為:" + ticket);
ticket--;
}
}
}

注意:一旦涉及到多執行緒共享資料(變數,物件等),一定考慮執行緒安全問題!!!
13. 死鎖 問題
死鎖的定義很重要:

出現死鎖后,不會出現例外,不會出現提示,只是所有的執行緒都處于阻塞狀態,
因此,像synchronized同步機制,我們要避免這種死鎖情況發生!!
package com.holmes.java02;
public class ThreadTest {
//因此,像synchronized同步機制,我們要避免這種死鎖情況發生!!
public static void main(String[] args) {
StringBuffer s1 = new StringBuffer();
StringBuffer s2 = new StringBuffer();
new Thread(){
@Override
public void run(){
//這里呼叫了s1,進入sleep,等待s2.
synchronized (s1){
s1.append("a");
s2.append("1");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s2){
s1.append("b");
s2.append("2");
System.out.println(s1);
System.out.println(s2);
}
}
}
}.start();
new Thread(new Runnable() {
@Override
public void run() {
//這里呼叫了s2,進入sleep,等待s2.
synchronized (s2){
s1.append("c");
s2.append("3");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
synchronized (s1){
s1.append("d");
s2.append("4");
System.out.println(s1);
System.out.println(s2);
}
}
}
}).start();
//想這種就會造成死鎖,
}
}
死鎖解決辦法:
- 專門的演算法,原則,
- 盡量減少同步資源的定義,
- 盡量避免嵌套同步,
14. Lock鎖的方式解決 多執行緒安全問題
Lock鎖是JDK5.0 ,新增的特性,
Lock鎖創建步驟:
- 1.實體化ReentrantLock,
- 2.呼叫鎖定方法(上鎖):lock()方法,
- 3.呼叫解鎖方法(解鎖):unlock()方法,
package com.holmes.java02;
import java.util.concurrent.locks.ReentrantLock;
class Window5 implements Runnable{
private int ticket = 100;
//1. 實體化ReentrantLock
//ReentrantLock:引數為true(公平鎖),引數默認為false(非公平鎖)
//公平鎖就是按照先到先得順序來執行執行緒順序,非公平鎖就是看誰先搶到cpu資源誰先執行,
private ReentrantLock lock = new ReentrantLock(true);
@Override
public void run() {
while(true){
try{
//2. 呼叫鎖定方法(上鎖):lock()方法
lock.lock();
if (ticket > 0){
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+",售票,票號為:"+ticket);
ticket--;
}else{
break;
}
}finally {
//3. 呼叫解鎖方法(解鎖):unlock()方法
lock.unlock();
}
}
}
}
public class LockTest {
public static void main(String[] args) {
Window5 w = new Window5();
Thread t1 = new Thread(w);
Thread t2 = new Thread(w);
Thread t3 = new Thread(w);
t1.setName("視窗1");
t2.setName("視窗2");
t3.setName("視窗3");
t1.start();
t2.start();
t3.start();
}
}
面試題:synchronized 于 Lock 的異同?

建議優先使用順序:
面試題:如何解決執行緒安全問題?有幾種方式?
- 兩種,synchronized和lock,
15. 執行緒的通信
本質上,就是wait()方法和notify()方法的使用,
- wait()方法:一旦執行此方法,當前執行緒進入阻塞狀態,并釋放同步監視器(釋放鎖),
- notify()方法:一旦執行此方法,就會喚醒被wait的一個執行緒,如果有多個執行緒被wait,就喚醒優先級高的執行緒,
- notifyAll():一旦執行此方法,就會喚醒所有被wait的執行緒,
package com.holmes.java03;
class Number implements Runnable{
private int number = 1;
@Override
public void run() {
while (true){
synchronized (this){
//notify()和notifyAll():前者只能喚醒一個,后者能喚醒所有!
notify();
if (number < 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
//wait()方法:使得呼叫如下wait()方法的執行緒進入阻塞狀態
//wait方法會釋放鎖!!!因此才能互動運行,
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("執行緒1");
t2.setName("執行緒2");
t1.start();
t2.start();
}
}
注意事項:
- wait()和notify()等方法,默認就是this呼叫的,例如:this.wait(),
- wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中,
- wait(),notify(),notifyAll()方法都是定義在java.lang.Object類中,
為什么,wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中?
原因:就是這三個方法的this物件的使用必須和同步監視器相同!!!

故因此,要么都是this當前物件,要么都定義為別的物件,例如:obj.wait(),
package com.holmes.java03;
class Number implements Runnable{
private int number = 1;
Object obj = new Object();
@Override
public void run() {
while (true){
synchronized (obj){
//上面同步監視器是this,下面wait,notify方法也都是this(默認就是this),
//同樣上面是obj物件,下面wait,notify方法也都是obj呼叫的方法,
obj.notify();
if (number < 100){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + ":" + number);
number++;
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}else {
break;
}
}
}
}
}
public class CommunicationTest {
public static void main(String[] args) {
Number number = new Number();
Thread t1 = new Thread(number);
Thread t2 = new Thread(number);
t1.setName("執行緒1");
t2.setName("執行緒2");
t1.start();
t2.start();
}
}
面試題:sleep()方法和wait()方法的異同?
- 相同點:一旦執行方法,都可以使得當前的執行緒進入阻塞狀態,
- 不同點:
1.兩個方法宣告的位置不同:Thread類中宣告sleep(),Object類宣告wait(),
2.呼叫的范圍不同:sleep()可以在任何場景下呼叫,wait()必須使用在同步代碼塊和同步方法中執行,
3.如果兩個方法都是用在同步代碼塊或同步方法中,sleep()不會釋放同步監視器(鎖),而wait()會釋放同步監視器(鎖),
16. 創建執行緒方式三 :實作Callable介面
實作Callable介面和執行緒池的方式是JDK5.0新增的特性,
與Runnable方式比較:
實作Callable介面方式,沒有了run()方法,但是卻有call()方法來代替,
實作Callable介面方式,創建執行緒步驟:
- 1.創建一個實作Callable的實作類,
- 2.實作call方法,將此執行緒需要執行的操作宣告在call()中,
- 3.創建Callable介面實作類的物件,
- 4.將此Callable介面實作類的物件作為引數傳遞到FutureTask構造器中,創建FutureTask的物件,
- 5.將FutureTask的物件作為引數傳遞到Thread類的構造器中,創建Thread物件,并呼叫start()方法,
- 6.需要獲取Callable中的call方法的回傳值,那就用get()獲取,
package com.holmes.java03;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;
//1.創建一個實作Callable的實作類,
class NumThread implements Callable{
//2.實作call方法,將此執行緒需要執行的操作宣告在call()中,
@Override
public Object call() throws Exception {
int sum = 0;
for (int i = 1;i <= 100;i++){
if (i % 2 == 0){
System.out.println(i);
sum += i;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.創建Callable介面實作類的物件
NumThread nt1 = new NumThread();
//4.將此Callable介面實作類的物件作為引數傳遞到FutureTask構造器中,創建FutureTask的物件,
//FutureTask類是也實作了Runnable介面,
FutureTask futureTask = new FutureTask(nt1);
//5.將FutureTask的物件作為引數傳遞到Thread類的構造器中,創建Thread物件,并呼叫start()方法,
new Thread(futureTask).start();
try {
//6.需要獲取Callable中的call方法的回傳值,那就用get()獲取,
//get()回傳值即為FutureTask構造器引數Callable實作類重寫的call()的回傳值,
Object sum = futureTask.get();
System.out.println("總和為:"+sum);
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
面試題:如何理解實作Callable介面的方式創建多執行緒比實作Runnable介面創建多執行緒方式強大?
- call()方法可以有回傳值的,
- call()可以拋出例外,被外面的操作捕獲,獲取例外的資訊,
- Callable是支持泛型的,
17. 方式四:使用執行緒池創建(開發常用)
執行緒池原理為:
提前創建好多個執行緒,放入執行緒池中,使用時直接獲取,使用完放回池中,可以避免頻繁創建銷毀,實作重復利用,

執行緒池創建方式:
1.提供指定執行緒數量的執行緒池
2.執行指定的執行緒的操作,需要提供實作Runnable介面或Callable介面實作類物件
3.關閉執行緒池
創建不同的執行緒池需求:
- Executors.newCachedThreadPool(): 創建一個可根據需要創建新執行緒的執行緒池,
- Executors.newFixedThreadPool(n): 創建一個可重用固定執行緒數的執行緒池,
- Executors.newSingleThreadExecutor(): 創建一個只有一個執行緒的執行緒池,
- Executors.newScheduledThreadPool(n): 創建一個執行緒池,它可安排在給定延遲后運行命令或者定期地執行,
注意:
設定執行緒池的一些屬性等等,必須要有一個強轉,具體見下面代碼,
package com.holmes.java03;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor;
/*
* 執行緒管理:
* corePoolSize:核心池的大小
* maximumPoolSize:最大執行緒數
* keepAliveTime:執行緒沒有任務時最多保持多長時間后會終止
*
* */
class NumberThread implements Runnable{
@Override
public void run() {
for(int i=0;i<=100;i++){
if (i%2==0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
}
}
class NumberThread2 implements Callable {
@Override
public Object call() throws Exception {
for(int i=0;i<=100;i++){
if (i%2!=0){
System.out.println(Thread.currentThread().getName()+":"+i);
}
}
return null;
}
}
public class ThreadPool {
public static void main(String[] args) {
//1.提供指定執行緒數量的執行緒池
ExecutorService service = Executors.newFixedThreadPool(10);
//System.out.println(service.getClass()); 查看service所在類
//設定執行緒池的屬性
//強轉service,為ThreadPoolExecutor型別,進而設定執行緒池的屬性,
ThreadPoolExecutor sercive1 = (ThreadPoolExecutor) service;
//sercive1.setCorePoolSize(15);
//sercive1.setKeepAliveTime();
//sercive1.setMaximumPoolSize();
//2.執行指定的執行緒的操作,需要提供實作Runnable介面或Callable介面實作類物件
//要點1:service.execute();//適合適用于Runnable
service.execute(new NumberThread());//適合適用于Runnable
//要點2:service.submit();//適合適用于Callable
service.submit(new NumberThread2());
//3.關閉執行緒池
service.shutdown();//關閉執行緒池
}
}
轉載請註明出處,本文鏈接:https://www.uj5u.com/houduan/348355.html
標籤:java



