JavaSE 線程(下)
三、Thread類的常用方法
1 線程一般方法
方法名稱 | 功能 |
---|---|
void start() | 啓動線程 |
run() | 線程在被調度時執行的操作 |
String getName() | 取出線程名稱 |
void setName(String name) | 設置線程名稱 |
static currentThread() | 返回當前線程 |
int getPriority() | 獲取線程優先級 |
void setPriority() | 設置線程優先級(默認爲5) |
補充:線程優先級就是哪個線程有較大的概率被執行。優先級用數字1-10表示,數字越大,優先級越高。若未設置優先級,默認爲5。
以上方法案例展示:
package com.thread;
public class Demo01 {
public static void main(String[] args) {
RunnalbalImpl run0 = new RunnalbalImpl();
RunnalbalImpl run1 = new RunnalbalImpl();
Thread t0 = new Thread(run0);
Thread t1 = new Thread(run1);
t1.setName("線程t1");
/*
線程優先級就是哪個線程有較大的概率被執行。
優先級用數字1-10表示,數字越大,優先級越高。
若爲設置,默認爲5。
*/
t0.setPriority(1); //設置優先級爲1,若爲設置默認爲5
t0.start();
t1.start();
System.out.println("t0的線程名稱: "+ t0.getName()); //若在創建線程時沒有指定名稱,則系統默認給出"Thread-0/1/..."
System.out.println("t1的線程名稱: "+ t1.getName());
System.out.println("t0的優先級: "+t0.getPriority()); //獲取線程優先級
}
}
class RunnalbalImpl implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"Runnable多線程運行的代碼");
for (int i = 0; i < 5; i++) {
System.out.println(Thread.currentThread().getName()+"這是Runnable多線程的邏輯代碼" + i);
}
}
}
/*運行結果:
Thread-0Runnable多線程運行的代碼
線程t1Runnable多線程運行的代碼
線程t1這是Runnable多線程的邏輯代碼0
線程t1這是Runnable多線程的邏輯代碼1
t0的線程名稱: Thread-0
線程t1這是Runnable多線程的邏輯代碼2
Thread-0這是Runnable多線程的邏輯代碼0
Thread-0這是Runnable多線程的邏輯代碼1
線程t1這是Runnable多線程的邏輯代碼3
Thread-0這是Runnable多線程的邏輯代碼2
線程t1這是Runnable多線程的邏輯代碼4
Thread-0這是Runnable多線程的邏輯代碼3
Thread-0這是Runnable多線程的邏輯代碼4
t1的線程名稱: 線程t1
t0的優先級: 1
*/
2 線程控制方法
方法名稱 | 功能 |
---|---|
static void yield() | 線程讓步: 1. 暫停當前正在執行的線程,把執行機會讓給優先級相同或更高的線程 2. 若隊列中沒有同優先級的線程,忽略此方法 |
join() | 當某個程序執行流中調用其他線程的join()方法時,調用線程將被阻塞,直到·join()方法加入的join線程執行完爲止。 |
static void sleep(long millis) | 1. 另當前活動線程在指定時間段內放棄對CPU控制,使其他線程有機會被執行,時間到後重新排隊。 2. 需要拋出InterruptedException異常 |
stop() | 強制線程結束 |
boolean isAlive() | 返回boolean值,判斷線程是否還存活 |
以上方法案例展示:
package com.thread;
public class Demo02 {
public static void main(String[] args) {
RunnalbalImpl2 run0 = new RunnalbalImpl2();
RunnalbalImpl2 run1 = new RunnalbalImpl2();
Thread t0 = new Thread(run0);
Thread t1 = new Thread(run1);
t0.start();
t1.start();
System.out.println("-----------------------------------------1");
System.out.println("-----------------------------------------2");
t1.stop(); //強制停止線程
try {
t0.join(); //相當於在這裏將t0的run方法插入到這個位置執行!
/* 專業說法
阻塞當前main方法,先不執行System.out.println("-----------------------------------------3");代碼
優先執行join進來的線程的代碼。
但是較爲不明顯。
*/
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("-----------------------------------------3");
System.out.println(t0.isAlive()); //判斷線程是否存活
System.out.println(t1.isAlive());
}
}
class RunnalbalImpl2 implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"Runnable多線程運行的代碼");
for (int i = 0; i < 5; i++) {
try {
Thread.sleep(1000);//每次循環睡眠1000ms
//相當於當前循環每隔1000ms執行一次
} catch (InterruptedException e) {
e.printStackTrace();
}
if (i % 2 == 0){
Thread.yield(); //線程讓步 <-- 當i是偶數時
}
System.out.println(Thread.currentThread().getName()+"這是Runnable多線程的邏輯代碼" + i);
}
}
}
/* 運行結果:
Thread-0Runnable多線程運行的代碼
Thread-1Runnable多線程運行的代碼
-----------------------------------------1
-----------------------------------------2
Thread-0這是Runnable多線程的邏輯代碼0
Thread-0這是Runnable多線程的邏輯代碼1
Thread-0這是Runnable多線程的邏輯代碼2
Thread-0這是Runnable多線程的邏輯代碼3
Thread-0這是Runnable多線程的邏輯代碼4
-----------------------------------------3
false
false
*/
四、線程的生命週期
線程在一個完整的生命週期中通常要經歷如下五種狀態:
- 新建:當一個Thread類或其子類的對象被聲明應創建時,新生的線程對象處於新建狀態。
- 就緒:處於新建狀態的線程被start()後,將進入線程隊列等待CPU時間片,此時它已具備了運行的條件。
- 運行:當就緒的線程被嗲度並獲得處理器資源時,便進入運行狀態,run()方法定義了線程的操作和功能。
- 阻塞:如果一個線程執行了sleep(睡眠)、suspend(掛起)等方法,失去所佔用資源之後,該線程就從運行狀態進入阻塞狀態。在睡眠時間已到或獲得設備資源後可以重新進入就緒狀態。
- 死亡:一個運行狀態的線程完成任務或者其他終止條件發生時,該線程就切換到終止狀態。
生命週期圖示:
五、線程的同步
1 案例
例子:同一個賬戶,支付寶轉賬,微信轉賬。現有兩個手機,一個手機開支付寶,另一個開微信,假設現有餘額3000,支付寶和微信同時提款2000。 如果沒有線程同步控制,賬戶就會變爲-1000,這種情況不可以出現。
該例子通過代碼實現如下:
package com.thread;
public class Demo03 {
public static void main(String[] args) {
//定義賬戶對象
Acount a = new Acount();
User u_wechat = new User(a,2000);
User u_zhi = new User(a,2000);
//多線程對象
Thread wechat = new Thread(u_wechat,"微信");
Thread zhi = new Thread(u_zhi,"支付寶");
wechat.start();
zhi.start();
}
}
class Acount{
public static int money = 3000; //全局變量,所有線程共享
/**
* 提款,判斷賬戶餘額是否充足
* 多線程調用這個方法,就有問題,線程共享資源時,一個線程在執行這個方法沒有完畢時,另一個線程又開始執行這個方法。
* @param m
*/
public void drawing(int m){
String name = Thread.currentThread().getName();
if (money<m){
System.out.println(name+"操作,賬戶金額不足:" + money);
}else {
System.out.println(name + "操作,賬戶原有金額: " + money);
System.out.println(name + "操作,取款金額: " + m);
money -= m;
System.out.println(name + "操作,取款後的金額: " + money);
}
}
}
class User implements Runnable{
Acount acount;
int money;
public User(Acount acount, int money){
this.acount = acount;
this.money = money;
}
@Override
public void run() {
acount.drawing(money);
}
}
/*運行結果:
支付寶操作,賬戶原有金額: 3000
微信操作,賬戶原有金額: 3000
支付寶操作,取款金額: 2000
支付寶操作,取款後的金額: 1000
微信操作,取款金額: 2000
微信操作,取款後的金額: -1000
*/
造成問題的原因: 當多條語句在操作同一個線程共享數據時,一個線程對多條語句只執行了一部分,還沒有執行完,另一個線程參與進來執行。導致共享數據的錯誤。
解決辦法: 對多條操作共享數據的語句,只能讓一個線程都執行完,在執行過程中,其他線程不可以參與執行。
2 synchonized同步鎖!!
通過synchronized同步鎖來完成!
只需在上述案例中Acount類的drawing方法前加上synchronized
即可!即:
public synchronized void drawing(int m){}
結果顯示:
{...}
/*
微信支付操作,賬戶原有金額: 3000
微信支付操作,取款金額: 2000
微信支付操作,取款後的金額: 1000
支付寶支付操作,賬戶金額不足:1000
*/
以上問題解決。
synchronized同步鎖注意!!!
-
synchronized放在普通方法前,僅能鎖當前對象(實例),不同對象有不同的鎖。
-
synchronized放在靜態方法前,鎖整個類,所有類對象公用一個鎖。
-
也可對代碼塊加入同步鎖。實現如下:
public void drawing2(int m, Acount a){ //使用this鎖代碼塊是代表當前的對象,如果在其他方法中也有synchronized(this)代碼塊使用的都是同一個同步鎖 synchronized(this){//表示對整個代碼塊加鎖 ... } synchronized(a){ //鎖傳入的對象,並對代碼塊加鎖 ... }; }
3 線程的死鎖問題
-
死鎖
不同的線程分別佔用對方需要的同步資源不放棄,都在等待對方放棄自己需要的同步資源,就形成了線程的死鎖。
例:線程a0,需要執行f0;線程a1,需要執行f1;f0和f1都有同步鎖的方法,現在出現一個問題:a0調用f1方法但一直沒有執行完f1,a1調用f0方法但一直沒有執行完f0。導致a0和a1線程都在等待對方釋放方法,而對方都不釋放,這樣就形成了線程的死鎖。
-
解決辦法
專門的算法、原則,比如加鎖的順序一致。
儘量減少同步資源的定義,儘量避免鎖未釋放的場景。
六、線程通信
1 線程通信方法
注:以下三種方法只能用在有同步鎖的方法或代碼塊中!
方法名稱 | 功能 |
---|---|
wait() | 另當前線程掛起並放棄CPU、同步資源,使別的線程可以訪問並修改共享資源,而當前線程排隊等候再次對資源的訪問。 |
notify() | 喚醒正在排隊等待同步資源的線程中優先級最高者結束等待。 |
notifyAll() | 喚醒正在排隊等待資源的所有線程結束等待。 |
現在想要實現以下功能:
如果是微信操作的,先不執行,等待支付寶操作,支付寶操作完成後,微信在繼續操作。
方法案例展示:
package com.thread;
public class Demo03 {
public static void main(String[] args) {
//定義賬戶對象
Acount a = new Acount();
User u_wechat = new User(a,2000);
User u_zhi = new User(a,2000);
//多線程對象
Thread wechat = new Thread(u_wechat,"微信");
Thread zhi = new Thread(u_zhi,"支付寶");
wechat.start();
zhi.start();
}
}
class Acount{
public static int money = 3000; //全局變量,所有線程共享
public void drawing(int m, Acount a){
synchronized (a){ //對實例加同步鎖
String name = Thread.currentThread().getName();
//如果是微信操作的,先不執行,等待支付寶操作,支付寶操作完成後,微信在繼續操作。
if(name.equals("微信")){
try {
a.wait();//當前進程進入等待狀態!
} catch (InterruptedException e) {
e.printStackTrace();
}
}
if (name.equals("支付寶")) {
try {
a.notify(); //喚醒當前優先級最高的線程,進入就緒狀態!
// a.notifyAll(); //喚醒所有線程,進入就緒狀態!
} catch (Exception e) {
e.printStackTrace();
}
}
if (money<m){
System.out.println(name+"操作,賬戶金額不足:" + money);
}else {
System.out.println(name + "操作,賬戶原有金額: " + money);
System.out.println(name + "操作,取款金額: " + m);
money -= m;
System.out.println(name + "操作,取款後的金額: " + money);
}
}
}
}
class User implements Runnable{
Acount acount;
int money;
public User(Acount acount, int money){
this.acount = acount;
this.money = money;
}
@Override
public void run() {
acount.drawing(money,acount);
}
}
/*運行結果:
支付寶操作,賬戶原有金額: 3000
支付寶操作,取款金額: 2000
支付寶操作,取款後的金額: 1000
微信操作,賬戶金額不足:1000
*/
2 生產者/消費者問題
**問題描述:**生產者將產品交給店員,而消費者從店員處取走產品,店員一次只能持有固定數量的產品(20),如果生產者試圖生產更多的產品,店員會叫生產者停一下;如果店中有空位放產品了在通知生產者繼續生產;如果店中沒有產品了,店員會告訴消費者等一下,如果店中有產品了再通知消費者來取走產品。
此處有兩個問題:
-
生產者比消費者更快時,消費者會漏掉一些數據沒有取到。
-
消費者比生產者快時,消費者會取相同的數據。
代碼實現:(此處使用兩個匿名內部類實現!)
package com.thread;
/**
* 生產者與消費者
*/
public class Test3 {
public static void main(String[] args) {
//店員
Clerk c = new Clerk();
//消費時不生產,生產時不消費
//生產者線程
new Thread(new Runnable() {
@Override
public void run() {
synchronized (c){
while (true){ //無限循環代表無限的生產次數
if (c.productNum == 0) { //產品數量爲0,開始生產
System.out.println("產品數量爲0,開始生產");
while (c.productNum < 4){
System.out.println("庫存:" + c.productNum++);
}
System.out.println("產品數爲:" + c.productNum + ",結束生產");
c.notify(); //喚醒消費者線程
}else {
try {
c.wait(); //生產者線程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}, "生產者").start();
//消費者線程
new Thread(new Runnable() {
@Override
public void run() {
synchronized (c){
while (true) { //無限循環代表無限的生產與消費
if (c.productNum != 0) { //產品數爲4,開始消費
c.notify(); //喚醒生產者線程
System.out.println("產品數爲4,開始消費");
while (c.productNum > 0) {
System.out.println("庫存:" + c.productNum--);
}
System.out.println("產品數爲:" + c.productNum + ",結束消費");
} else {
try {
c.wait(); //消費者線程等待
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
}, "消費者").start();
}
}
class Clerk{
public static int productNum;
}
寫在最後
行百里者半九十!
To Demut and Dottie!