《java開發實戰經典》李興華——C9. 多執行緒
一、程式與執行緒
1.程式:程式是程式的一次動態執行過程,包括從程式碼載入、執行、到執行完畢。
2.執行緒:執行緒是比程式更小的執行單位,一個程式可以包括多個執行緒,這些執行緒可以同時存在、執行。
程式和執行緒都是實現併發的基本單位。
Java程式執行時最少2執行緒:main執行緒 和 垃圾回收執行緒。
二、Java中執行緒的實現
Java中實現多執行緒的兩種手段:1.繼承Thread類 2.實現Runnable介面。不管用哪種方法,都要依靠Thread類來實現。
1.繼承Thread類:
1)概念:Thread類是在java.lang包中定義的,繼承時必需覆寫run()方法,此方法為執行緒的主體。
2)例項:
直接調run()方法實際上沒有啟動執行緒
要啟動執行緒,需要呼叫start()方法
注意:一個例項化物件只能呼叫一次start()方法,否則會出現IllegalThreadStateException異常
public class Test {
public static void main(String args[]) {
MyThread a1 = new MyThread("first");
MyThread a2 = new MyThread("second");
a1.run();
a2.run();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i<10;i++) {
System.out.println(name +"執行,i = "+i);
}
}
//此時未啟動執行緒,按順序執行,先列印a1的全部,再列印a2。
public class Test {
public static void main(String args[]) {
MyThread a1 = new MyThread("first");
MyThread a2 = new MyThread("second");
a1.start();
a2.start();
}
}
class MyThread extends Thread{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i<10;i++) {
System.out.println(name +"執行,i = "+i);
}
}
//此時啟動執行緒,a1,a2交替列印。
2.實現Runnable介面:
1)概念:
Runnable介面中只定義了一個抽象方法 public void run()。因此可以通過覆寫run()方法實現多執行緒。
2)例項:
實際上還是藉助於Thread類,然後呼叫start()方法。
因為Thread類提供了public Thread(Runnable r)和public Thread(Runnable r,String name)兩個構造方法。
public class Test {
public static void main(String args[]) {
MyThread a1 = new MyThread("first");
MyThread a2 = new MyThread("second");
Thread t1 = new Thread(a1);
Thread t2 = new Thread(a2);
t1.start();
t2.start();
}
}
class MyThread implements Runnable{
private String name;
public MyThread(String name) {
this.name = name;
}
@Override
public void run() {
for(int i = 0;i<10;i++) {
System.out.println(name +"執行,i = "+i);
}
}
}
3.Thread類和Runnable介面:
1)實際上,Thread和Runnable的子類都同時實現了Runnable介面,之後將Runnable的子類例項放到了Thread類中。
2)繼承Thread不適用於多個執行緒資源共享;實現Runnable介面則可以在多執行緒間資源共享。
//1.資源不共享
public class Test {
public static void main(String args[]) {
MyThread t1 = new MyThread();
MyThread t2 = new MyThread();
t1.start();
t2.start();
}
}
class MyThread extends Thread{
private int ticket = 5;
@Override
public void run() {
for(int i = 0;i<10;i++) {
if(ticket>0) {
System.out.println("賣票:ticket = "+ticket-- );
}
}
}
}
//賣票:ticket = 5
//賣票:ticket = 5
//賣票:ticket = 4
//賣票:ticket = 4
//賣票:ticket = 3
//賣票:ticket = 2
//賣票:ticket = 1
//賣票:ticket = 3
//賣票:ticket = 2
//賣票:ticket = 1
//2.資源共享
public class Test {
public static void main(String args[]) {
MyThread my = new MyThread();
new Thread(my).start();
new Thread(my).start();
}
}
class MyThread implements Runnable{
private int ticket = 5;
@Override
public void run() {
for(int i = 0;i<10;i++) {
if(ticket>0) {
System.out.println("賣票:ticket = "+ticket-- );
}
}
}
}
//賣票:ticket = 5
//賣票:ticket = 4
//賣票:ticket = 3
//賣票:ticket = 2
//賣票:ticket = 1
3.)實現Runnable對比繼承Thread的優勢:
適合多個相同程式程式碼的執行緒去處理同一資源的情況。
可以避免由於Java的單繼承帶來的侷限。
增強了程式的健壯性,程式碼能夠被多個執行緒共享,程式碼與資料是獨立的。
因此,建議用實現Runnable。
三、執行緒的狀態
建立、就緒、執行、阻塞、終止。
四、執行緒操作的相關方法
1)設定當前執行緒名稱,不設時預設分配,格式為Thread-X
Thread.currentThread().setName("執行緒A")
2)獲取當前執行緒名稱
Thread.currentThread().getName();
3)判斷執行緒是否啟動,返回true&false
t.isAlive()
4)執行緒的強制執行,執行緒強制執行期間,其他執行緒無法執行,必須等此執行緒完成。
t.join()
5)執行緒的休眠
Thread.sleep(500) //休眠500ms
6)中斷執行緒
t.interrupt()
7) 後臺執行緒
t.setDaemon()
8)執行緒的優先順序
Java中所有執行緒在執行前都會保持在就緒狀態,此時哪個執行緒優先順序高,它就會有可能先被執行。
setPriority()可以設定執行緒的優先順序,Java中有3中優先順序:
示例:
class MyThread implements Runnable{
@Override
public void run() {
for(int i=0;i<3;i++) {
try {
Thread.sleep(500);//休眠5s
}catch(Exception e) {
}finally {
System.out.println(Thread.currentThread().getName()
+"執行,i ="+i);//輸出執行緒名稱
}
}
}
}
public class Test {
public static void main(String args[]) {
//例項化3個執行緒
Thread t1 = new Thread(new MyThread(),"執行緒A");
Thread t2 = new Thread(new MyThread(),"執行緒B");
Thread t3 = new Thread(new MyThread(),"執行緒C");
//給三個執行緒設定不同優先順序
t1.setPriority(Thread.MIN_PRIORITY);
t2.setPriority(Thread.MAX_PRIORITY);
t3.setPriority(Thread.NORM_PRIORITY);
//啟動三個執行緒
t1.start();
t2.start();
t3.start();
}
}
//執行結果:
執行緒B執行,i =0
執行緒C執行,i =0
執行緒A執行,i =0
執行緒B執行,i =1
執行緒C執行,i =1
執行緒A執行,i =1
執行緒B執行,i =2
執行緒C執行,i =2
執行緒A執行,i =2
注意:main方法的優先順序是NORM_PRIORITY。getPriority()獲得當前執行緒優先順序。
9)執行緒的禮讓
Thread.currentThread().yield();//執行緒禮讓:將本執行緒暫停,讓其他執行緒先執行。
五、執行緒操作範例
設計一個執行緒操作類,產生3個執行緒物件,並設定3個執行緒的休眠時間,分別是:
執行緒A:休眠10秒
執行緒B:休眠20秒
執行緒C:休眠30秒
1.實現方法一:
class MyThread extends Thread{
private int sleepTime;
public int getSleepTime() {
return sleepTime;
}
public void setSleepTime(int sleepTime) {
this.sleepTime = sleepTime;
}
public MyThread(String name,int sleepTime) {
super(name);
this.setSleepTime(sleepTime);
}
@Override
public void run() {
try {
Thread.sleep(sleepTime);
}catch(Exception e){
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+",休眠"+sleepTime+"豪秒。");
}
}
public class Test {
public static void main(String args[]) {
MyThread t1 = new MyThread("執行緒A",10000);
MyThread t2 = new MyThread("執行緒B",20000);
MyThread t3 = new MyThread("執行緒C",30000);
t1.start();
t2.start();
t3.start();
}
}
2.實現方法二:
class MyThread implements Runnable{
private int sleepTime;
public int getSleepTime() {
return sleepTime;
}
public void setSleepTime(int sleepTime) {
this.sleepTime = sleepTime;
}
public MyThread(int st) {
this.setSleepTime(st);
}
@Override
public void run() {
try {
Thread.sleep(sleepTime);
}catch(Exception e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+",休眠"+this.getSleepTime()+"毫秒。");
}
}
public class Test {
public static void main(String args[]) {
Thread t1 = new Thread(new MyThread(1000),"執行緒A");
Thread t2 = new Thread(new MyThread(2000),"執行緒B");
Thread t3 = new Thread(new MyThread(3000),"執行緒C");
t1.start();
t2.start();
t3.start();
}
}
六、同步與死鎖
1.問題的引出:
通過runnable介面實現多執行緒,意味著屬性會被資源共享。如:產生3個執行緒,賣5張票:
class MyThread implements Runnable{
private int ticket = 5;
@Override
public void run() {
for(int i =0;i<10;i++) {
if(ticket>0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩餘票數:"+ticket--);
}
}
}
}
public class Test {
public static void main(String args[]) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
t1.start();
t2.start();
t3.start();
}
}
//執行結果(每次執行結果不相同):
剩餘票數:5
剩餘票數:4
剩餘票數:5
剩餘票數:3
剩餘票數:2
剩餘票數:3
剩餘票數:1
剩餘票數:0
剩餘票數:-1
2.問題的解決——同步
所謂同步,指多個操作在同一時間段內只有一個執行緒進行,其他執行緒要等待此執行緒完成後才可以繼續進行。
同步操作有兩種方式:同步程式碼塊和同步方法。
1)同步程式碼塊
synchronized(同步物件){
需要同步的程式碼;
}
class MyThread implements Runnable{
private int ticket = 5;
public void run() {
for(int i =0;i<10;i++) {
synchronized (this) {//一般用this指代當前物件
if(ticket>0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩餘票數:"+ticket--);
}
}
}
}
}
public class Test {
public static void main(String args[]) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
t1.start();
t2.start();
t3.start();
}
}
2)同步方法
synchronized 返回值 方法名(引數列表){
}
class MyThread implements Runnable{
private int ticket = 5;
public void run() {
for(int i =0;i<10;i++) {
this.saleTicket();
}
}
synchronized void saleTicket() {
if(ticket>0) {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("剩餘票數:"+ticket--);
}
}
}
public class Test {
public static void main(String args[]) {
MyThread mt = new MyThread();
Thread t1 = new Thread(mt);
Thread t2 = new Thread(mt);
Thread t3 = new Thread(mt);
t1.start();
t2.start();
t3.start();
}
}
3.死鎖
死鎖,即兩個執行緒都在等彼此先完成,因此儘量避免過多的同步。
七、執行緒操作案例——生產者和消費者
生產者生產兩種產品:
* 1.李興華,Java講師
* 2.mldn,www.mldnjava.cn
消費者依次取走生產者生產的產品。
理想情況下:生產者完整生產一個產品,消費者取一次,然後生產者再生產下一個產品。
應避免的問題:
* 1.產品只生產完一部分就被取走
* 2.生產者生產多次後,消費者才開始取走;或消費者取走後,還沒等到新的產品,就再次取了已取過的資料。
一、最簡單的想法
/*
* 定義一個產品類
*/
class Product{
private String info1 = "李興華";
private String info2 = "Java講師";
public String getInfo1() {
return info1;
}
public void setInfo1(String info1) {
this.info1 = info1;
}
public String getInfo2() {
return info2;
}
public void setInfo2(String info2) {
this.info2 = info2;
}
}
/*
* 定義一個生產者類
*/
class Producer implements Runnable{
private Product p = null;
public Producer(Product p) {
this.p = p;
}
private boolean pF = false;//生產標誌,true時生產第一種,false時生產第二種
@Override
public void run() {
for(int i =0;i<10;i++) {
if(pF) {
this.p.setInfo1("李興華");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.p.setInfo2("java講師");
pF = false;
}else {
this.p.setInfo1("mldn");
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.p.setInfo2("www.mldnjava.cn");
pF = true;
}
}
}
}
/*
* 定義一個消費者類
*/
class Consumer implements Runnable{
private Product p = null;
public Consumer(Product p) {
this.p = p;
}
@Override
public void run() {
for(int i=0;i<10;i++) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(p.getInfo1()+p.getInfo2());
}
}
}
/*
* 程式入口
*/
public class Test{
public static void main(String[] args) {
Product p = new Product();
Producer per = new Producer(p);
Consumer cer = new Consumer(p);
new Thread(per).start();
new Thread(cer).start();
}
}
此時結果:
李興華,www.mldnjava.cn
mldn,Java講師
李興華,www.mldnjava.cn
mldn,Java講師
李興華,www.mldnjava.cn
mldn,Java講師
mldn,Java講師
李興華,www.mldnjava.cn
李興華,Java講師
李興華,Java講師
此時問題:
一種產品的內容沒設定完整就被取走了。因此應該先保證一個產品能設定完全部內容。
解決方法:
1.將設定產品內容的方法進行同步(synchronized set)
2.將獲取產品內容的方法進行同步(synchronized get)
3.生產者生產時直接呼叫set
4.消費者消費時直接呼叫get
二、加入同步,修改以下程式碼
/*
* 定義一個產品類
*/
class Product{
private String info1 = "李興華";
private String info2 = "Java講師";
public String getInfo1() {
return info1;
}
public void setInfo1(String info1) {
this.info1 = info1;
}
public String getInfo2() {
return info2;
}
public void setInfo2(String info2) {
this.info2 = info2;
}
public synchronized void set(String info1,String info2) {
this.setInfo1(info1);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setInfo2(info2);
}
public synchronized void get() {
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getInfo1()+this.getInfo2());
}
}
/*
* 定義一個生產者類
*/
class Producer implements Runnable{
private Product p = null;
public Producer(Product p) {
this.p = p;
}
private boolean pF = false;//生產標誌,true時生產第一種,false時生產第二種
@Override
public void run() {
for(int i =0;i<10;i++) {
if(pF) {
this.p.set("李興華", "java講師");
pF = false;
}else {
this.p.set("mldn", "www.mldnjava.cn");
pF = true;
}
}
}
}
/*
* 定義一個消費者類
*/
class Consumer implements Runnable{
private Product p = null;
public Consumer(Product p) {
this.p = p;
}
@Override
public void run() {
for(int i=0;i<10;i++) {
this.p.get();
}
}
}
此時結果:
mldn,www.mldnjava.cn
李興華,Java講師
mldn,www.mldnjava.cn
mldn,www.mldnjava.cn
李興華,Java講師
mldn,www.mldnjava.cn
mldn,www.mldnjava.cn
李興華,Java講師
李興華,Java講師
李興華,Java講師
此時問題:
產品資訊錯亂解決了,但是還存在重複讀寫問題。
解決方法:
引入等待喚醒機制。
在Product類中加一個標誌位,為true時生產,為false時消費。
三、引入等待喚醒機制,修改以下程式碼
/*
* 定義一個產品類
*/
class Product{
private String info1 = "李興華";
private String info2 = "Java講師";
private boolean flag = false;
public String getInfo1() {
return info1;
}
public void setInfo1(String info1) {
this.info1 = info1;
}
public String getInfo2() {
return info2;
}
public void setInfo2(String info2) {
this.info2 = info2;
}
public synchronized void set(String info1,String info2) {
if(!flag) {
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.setInfo1(info1);
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
this.setInfo2(info2);
flag = false;
super.notify();
}
public synchronized void get() {
if(flag) {
try {
super.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
try {
Thread.sleep(300);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(this.getInfo1()+this.getInfo2());
flag = true;
super.notify();
}
}
此時結果符合預期,生產一個消費一個。
但是注意:休眠時間的設定,最好在消費前給夠生產的時間。
八、執行緒的生命週期
執行緒生命週期種的方法已經學過的有:
1.new Thread() 2.start() 3.run() 4.sleep() 5.wait()
接下來介紹:
6.suspend() :暫時掛起執行緒 7.resume() :恢復被掛起的執行緒 8.stop():停止執行緒
這三種方法不推薦使用,因為容易產生死鎖問題。
既然不推薦使用,那我們怎麼停止一個執行緒呢?
—— 通過設定標誌位的方式,如下:
class MyThread implements Runnable{
private boolean flag = true;
@Override
public void run() {
int i=0;
while(this.flag) {
System.out.println(Thread.currentThread().getName()+"執行,i="+(i++));
if(i > 4) {
this.stop();
}
}
}
public void stop() {
this.flag = false;
}
}
public class Test{
public static void main(String[] args) {
MyThread my = new MyThread();
Thread t = new Thread(my);
t.start();
}
}
相關文章
- 《java開發實戰經典》李興華——C1. Java概述及開發環境搭建Java開發環境
- Java多執行緒之併發安全經典案例-賣票Java執行緒
- 多執行緒經典面試題執行緒面試題
- Java多執行緒之守護執行緒實戰Java執行緒
- Java併發實戰一:執行緒與執行緒安全Java執行緒
- 玩轉java多執行緒 之多執行緒基礎 執行緒狀態 及執行緒停止實戰Java執行緒
- java 執行緒淺解03[執行緒同步以及經典死鎖]Java執行緒
- JAVA多執行緒併發Java執行緒
- Java多執行緒——執行緒Java執行緒
- 實戰.Net多執行緒(一)執行緒
- 實戰.Net多執行緒(二)執行緒
- 實戰.Net多執行緒(三)執行緒
- 實戰.Net多執行緒(四)執行緒
- Java面試經典題:執行緒池專題Java面試執行緒
- 面經梳理-java多執行緒其他Java執行緒
- Java高併發與多執行緒(二)-----執行緒的實現方式Java執行緒
- java多執行緒總結六:經典生產者消費者問題實現Java執行緒
- Java多執行緒-執行緒中止Java執行緒
- Java多執行緒——執行緒池Java執行緒
- 實戰體會Java的多執行緒程式設計Java執行緒程式設計
- java 多執行緒 併發 面試Java執行緒面試
- 總結:iOS中多執行緒的經典崩潰iOS執行緒
- Java多執行緒實現方式Java執行緒
- Java多執行緒的實現Java執行緒
- 【Java多執行緒】輕鬆搞定Java多執行緒(二)Java執行緒
- Java 併發和多執行緒(一) Java併發性和多執行緒介紹[轉]Java執行緒
- C#多執行緒開發-執行緒同步 02C#執行緒
- C#多執行緒開發-執行緒池03C#執行緒
- java多執行緒與併發 - 執行緒池詳解Java執行緒
- Java多執行緒/併發08、中斷執行緒 interrupt()Java執行緒
- java——多執行緒Java執行緒
- java 多執行緒Java執行緒
- 【Java】多執行緒Java執行緒
- JAVA 多執行緒 ??Java執行緒
- java多執行緒Java執行緒
- Java - 多執行緒Java執行緒
- Java多執行緒/併發12、多執行緒訪問static變數Java執行緒變數
- java 多執行緒守護執行緒Java執行緒