tags:
- 進程
- 線程
JavaDay24 多線程與多進程
@toc
代碼示例:
package DemoDay24;
import org.junit.jupiter.api.Test;
/**使用線程實現同時視頻和語音
* @author GJXAIOU
* @create 2019-07-24-20:54
*/
class VideoThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("視頻中。。。。。");
}
}
}
class AudioThread extends Thread{
@Override
public void run() {
for (int i = 0; i < 5; i++) {
System.out.println("語音中。。。。。");
}
}
}
public class Demo1 {
//方法一:使用main函數進行調試
// public static void main(String[] args) {
// VideoThread videoThread = new VideoThread();
// AudioThread audioThread = new AudioThread();
//
// videoThread.start();
// audioThread.start();
// }
//方法二:使用JUnit中@test進行調試
@Test
public void test(){
VideoThread videoThread = new VideoThread();
AudioThread audioThread = new AudioThread();
videoThread.start();
audioThread.start();
}
}
程序運行結果:
每次運行結果會不同,進程會別搶佔;
語音中。。。。。
語音中。。。。。
語音中。。。。。
語音中。。。。。
語音中。。。。。
視頻中。。。。。
視頻中。。。。。
視頻中。。。。。
視頻中。。。。。
視頻中。。。。。
一、線程中的常用方法
方法名 | 含義 | 說明 |
---|---|---|
Thread(String name); | 初始化線程的名字 | 屬於線程的一個有參數的構造方法 |
setName(String name); | 修改線程的名字 | |
getName(); | 獲取線程的名字 | |
sleep(); | static靜態方法,通過Thread類名調用,這裏需要處理一些異常,要求當前線程睡覺多少毫秒; | 【哪一個線程執行了sleep方法,哪一個線程就睡覺】。 |
currentThead(); | static靜態方法,返回當前的線程對象; | 【哪一個線程執行了currentThread方法,就返回哪一個線程對象】。 |
getPriority(); | 返回當前線程的優先級 | CPU執行的優先級,不是絕對的,僅僅是提升概率。 |
setPriority(int newPriority); | 設置線程的優先級。 |
-
【注意】
線程的優先級範圍是從1 ~ 10, 10最高,1最低
這裏的優先級只是提高了當前線程擁有CPU執行權的概率,並不能完全保證當前線程能夠一定會佔用更多的CPU時間片。線程的默認優先級爲5。Thread[main,5,main]
Thread[Thread-0,5,main]
Thread[線程名, 優先級, 線程組名] -
線程中常見方法的測試:
run()方法中不能拋出異常,只能使用 try-catch
package DemoDay24;
/**
* @author GJXAIOU
* @create 2019-07-24-21:23
*/
public class Demo2 extends Thread {
public Demo2(String name) {
super(name);//調用父類Thread的有參構造方法
}
@Override
public void run() {
//這裏是Demo2線程對象的線程代碼
System.out.println("28:" + Thread.currentThread());
for (int i = 0; i < 5; i++) {
System.out.println("自定義線程");
/*
在其他方法中, 使用sleep方法,可以拋出,可以捕獲,
但是在run方法爲什麼只有捕獲沒有拋出?因爲這是一個語法規則:
在Java中,重寫父類的方法,要求和父類的方法聲明一模一樣,
在Thread類中,run方法沒有拋出異常,所以在子類中,你也不能拋出異常,要和父類一致
*/
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
//這裏是main線程
Demo2 d = new Demo2("狗蛋");
d.setName("狗娃");
d.setPriority(10);
d.start(); //開啓自定義線程,執行自定義線程中的run方法裏面的功能
System.out.println("39:" + Thread.currentThread());
for (int i = 0; i < 5; i++) {
System.out.println("這裏是main線程");
sleep(100);
}
}
}
程序運行結果:
39:Thread[main,5,main]
這裏是main線程
28:Thread[狗娃,10,main]
自定義線程
這裏是main線程
自定義線程
自定義線程
這裏是main線程
這裏是main線程
自定義線程
自定義線程
這裏是main線程
下面的 InterfaceA 和 testA 是爲了測試什麼時候是不能使用拋出異常,只能使用 try-catch
package com.qfedu.a_thread;
interface A {
public void testA();
}
public class Demo2 extends Thread implements A{
public Demo2(String name) {
super(name);//調用父類Thread的有參構造方法
}
@Override
public void testA() {
//這裏也無法拋出異常,兩種處理方法,第一種,捕獲異常,
//第二種,在接口中聲明方法部分,聲明該異常
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//--------------------------------------------------
//下面代碼省略
}
二、線程的生命週期
[外鏈圖片轉存失敗(img-j6ZAToze-1568986092781)($resource/%E7%BA%BF%E7%A8%8B%E7%9A%84%E7%94%9F%E5%91%BD%E5%91%A8%E6%9C%9F.png)]
三、線程的共享資源問題
以動物園買票爲示例:
一共有 50張票 3個窗口同時售賣
這裏隱含多線程,這裏可以把3個窗口看做是3個線程
-
第一次問題:
發現票每一張都被買了三次- 原因:
因爲Ticket是在每一個線程中run方法裏面的一個局部變量,這個局部變量是每一個線程對象都擁有的,這裏Ticket就是不在是一個共享資源 - 處理方式:
把Ticket變成成員變量
- 原因:
-
第二次問題:
發現貌似每一張票還都是賣了50次,而且這裏還優化了售賣的算法- 原因:
這裏Ticket變成了一個成員變量,在每一個線程對象中,都擁有這個Ticket成員變量,每一個成員變量是一個獨立的個體,不是共享資源 - 處理方式:
用static修飾ticket成員變量,變成一個存放在數據共享區的一個靜態成員變量
- 原因:
-
第三次問題:
發現會出現幾張票是買了多次的- 原因:
因爲窗口1在賣票的時候,還沒有運行到ticket–這條語句的時候,下一個窗口2開始執行賣票算法
這裏窗口2賣的票是窗口1還沒有ticket–的票
[外鏈圖片轉存失敗(img-ArlJjEsh-1568986092782)($resource/%E7%BA%BF%E7%A8%8B%E5%AE%89%E5%85%A8%E9%97%AE%E9%A2%98.png)]
處理方式:
上鎖,鎖門
- 原因:
-
Java中的線程同步機制:
方式1:
同步代碼塊:
synchronized (鎖對象) {
//需要同步的代碼;
}
同步代碼塊的注意事項:
- 鎖對象,可以是任意的一個對象, 但是必須是同一個對象!!!不能在這裏使用new 來創建匿名對象
- sleep() 不會釋放鎖對象,不會開鎖。例如: 廁所有人關門睡着了
- 使用synchronized 同步代碼塊的時候,必須是真正意義上存在共享資源的線程問題,纔會使用
而且通常情況下,用synchronized鎖住的代碼越少越好,提高代碼執行效率
package com.qfedu.a_thread;
class SaleTicket extends Thread {
private static int ticket = 50;
public SaleTicket(String name) {
super(name);
}
@Override
public void run() {
while (true) {
synchronized ("你好") {
//可以使用"你好 "的任何確定的對象
//不能使用new Demo3()創建不同對象,即對應不同的鎖
if (ticket > 0) {
System.out.println(Thread.currentThread().getName()+
":賣出來第" + ticket+ "張票");
try {
sleep(500); //睡眠也不會釋放鎖對象
} catch (InterruptedException e) {
e.printStackTrace();
}
} else {
System.out.println("賣完了");
break;
}
ticket--;
}
}
}
}
public class Demo3 {
public static void main(String[] args) {
SaleTicket s1 = new SaleTicket("窗口1");
SaleTicket s2 = new SaleTicket("窗口2");
SaleTicket s3 = new SaleTicket("窗口3");
s2.start();
s1.start();
s3.start();
}
}
程序運行結果:
窗口1:賣出來第50張票
窗口1:賣出來第49張票
。。。。。
窗口3:賣出來第28張票
窗口3:賣出來第27張票
窗口3:賣出來第26張票
窗口3:賣出來第2張票
窗口3:賣出來第1張票
賣完了
賣完了
賣完了
(一)死鎖
- 出現死鎖的原因:
1.存在兩個或兩個以上的共享資源
2.存在兩個或者兩個以上的線程使用這些共享資源
下面的代碼會出現死鎖;
package com.qfedu.a_thread;
class DeadLock extends Thread {
public DeadLock(String name) {
super(name);
}
@Override
public void run() {
if (Thread.currentThread().getName().equals("小胖")) {
synchronized ("電池") {
System.out.println("小胖有電池,想要遙控器");
synchronized ("遙控器") {
System.out.println("小胖拿到了遙控器,打開了投影儀");
}
}
} else if (Thread.currentThread().getName().equals("逗比")) {
synchronized ("遙控器") {
System.out.println("逗比有遙控器,想要電池");
synchronized ("電池") {
System.out.println("逗比拿到了電池,打開了投影儀");
}
}
}
}
}
public class Demo4 {
public static void main(String[] args) {
DeadLock d1 = new DeadLock("小胖");
DeadLock d2 = new DeadLock("逗比");
d1.start();
d2.start();
}
}
輸出結果:
小胖有電池,想要遙控器
逗比有遙控器,想要電池
(二)自定義線程
Java語言是一種單繼承,多實現【遵從】面向對象的語言
- 自定義線程的方式:
-
方式1:
1.自定義一個類,繼承Thread類
2.重寫Thread裏面的run方法,把線程的功能代碼放入到run方法中
3.創建自定義線程類對象
4.開啓線程,使用start方法- 弊端:
因爲Java是一個單繼承的語言,一旦某一個類繼承了Thread類,就無法再繼承其他類,或者說一個類繼承了其他類,也就沒有辦法繼承Thread類
- 弊端:
-
方式2: 強烈推薦
【遵從】Runnable接口實現自定線程類
1.自定義一個類,【遵從】Runnable接口
2.實現Runnable接口中唯一要求的方法 Run方法,把線程的功能代碼寫入到run方法中
3.創建Thread類對象,並且把【遵從】Runnable接口的自定義類對象,作爲參數傳入到Thread構造方法中
4.調用Thread類對象的start方法,開啓線程
-
package com.qfedu.a_thread;
import java.util.Arrays;
import java.util.Comparator;
class TestRunnable implements Runnable {
//實現自定義線程類,遵從Runnable接口要求實現的Run方法,把線程代碼寫入到Runnable裏面
@Override
public void run() {
for (int i = 0 ; i < 10; i++) {
System.out.println("當前線程爲:" + Thread.currentThread());
}
}
}
public class Demo5 {
public static <T> void main(String[] args) {
//創建Thread類對象,調用Thread構造方法中,需要傳入Runnable接口實現類對象的方法~
//方法一:
Thread t1 = new Thread(new TestRunnable());//匿名對象
//方法二:
Thread t2 = new Thread(new Runnable() { //匿名內部類的匿名對象,不再需要定義上面的TestRunnable類
@Override
public void run() {
for (int i = 0; i < 10; i++) {
System.out.println("匿名內部類的匿名對象,作爲方法的參數,這裏是作爲線程對象的參數" +
Thread.currentThread());
}
}
});
t1.start();
t2.start();
/*
target是在創建Thread類對象時候,傳入的【遵從】Runnable接口的實現類,這個實現類中
實現類【遵從】Runnable接口要求實現的run方法,在run方法中,就是定義的線程代碼
在Thread類中有一個成員變量
//What will be run
private Runnable target;
@Override
public void run() {
if (target != null) {
target.run();
}
}
*/
}
}
三、守護線程(後臺線程)
例如:
軟件的Log日誌文件,軟件的自動更新,軟件的自動下載
特徵:
如果整個程序再運行過程中,只剩下一個守護線程,那麼這個守護線程也就沒有意義了,會自動停止
JVM的垃圾回收機制是守護線程。
這裏當主線程停止,則下載也會自動停止;
package com.qfedu.a_thread;
public class Demo6 extends Thread {
public Demo6(String name) {
super(name);
}
//模擬後臺下載更新的線程
@Override
public void run() {
for (int i = 0; i <= 100; i++) {
System.out.println("軟件更新下載中………………" + i + "%");
if (i == 100) {
System.out.println("軟件更新下載完成,是否安裝~~");
}
try {
Thread.sleep(10);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Demo6 d = new Demo6("後臺線程");
//設置當前線程爲守護線程或者是後臺線程
d.setDaemon(true);//true爲守護線程
//System.out.println(d.isDaemon());
d.start();
for (int i = 0; i <= 50; i++) {
Thread.sleep(10);
System.out.println("主線程:" + i);
}
}
}
四、線程通訊
- 線程通訊:
一個線程完成任務之後,通知另一個線程來完成該線程應該執行的任務
生產者和消費者問題:這裏商品是兩個線程直接的共享資源
wait(); 等待,如果一個線程執行了wait方法,那麼這個線程就會進入臨時阻塞狀態,等待喚醒,這個喚醒必須其他線程調用notify() 方法喚醒;
notify(); 喚醒,喚醒線程池中進入【臨時阻塞狀態】的一個線程
- 注意事項:
- wait()和notify()這兩個方法都是Object類的方法
- 在消費者生產者模式下,鎖對象只能是商品;
package com.qfedu.a_thread;
//兩者之間的共享資源
class Product {
String name; //商品的名字
int price; //價格
boolean flag = false; //產品是否生產成功,如果成功flag設置爲true,消費者購買之後,設置爲false
}
class Producer extends Thread {
Product p; //商品的類對象,是和消費者之間的共享資源
public Producer(Product p) {
this.p = p;
}
@Override
public void run() {
int count = 0;
while (true) {
synchronized (p) {
try {
sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
if (!p.flag) { //p.flag == false
//商品不存在,生產過程
if (count % 2 == 0) {
p.name = "紅辣椒擀麪皮";
p.price = 5;
} else {
p.name = "唐風閣肉夾饃";
p.price = 10;
}
count++;
System.out.println("生產者生產了:" + p.name + ":" + p.price);
p.flag = true;
//生產結束,喚醒消費者
p.notify();
} else {
//商品存在,要求消費者來購買,生產者進入臨時阻塞
try {
p.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} // try - catch
}// if - else
} // 同步代碼塊
}// while (true)
} //run()
}
class Customer extends Thread {
Product p; //商品類對象,是和生產者之間的共享資源
public Customer(Product p) {
this.p = p;
}
@Override
public void run() {
while (true) {
synchronized (p) {
try {
sleep(100);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
if (p.flag) { //p.flag == true
//這裏表示商品存在
System.out.println("消費者購買了:" + p.name + ":" + p.price);
p.flag = false; //表示消費者購買完畢,要求生產者生產
//需要喚醒生產者
p.notify(); //打開線程鎖
} else {
try {
//商品不存在,消費者進入臨時阻塞
p.wait();
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
} // try - catch
}// if - else
} //同步代碼塊
} // while (true)
} //run()
}
public class Demo7 {
public static void main(String[] args) {
Product product = new Product();
Producer p = new Producer(product);
Customer c = new Customer(product);
p.start();
c.start();
}
}
[外鏈圖片轉存失敗(img-c1SINa4C-1568986092783)($resource/%E5%8F%AF%E9%81%87%E4%B8%8D%E5%8F%AF%E6%B1%82%E7%9A%84%E9%94%99%E8%AF%AF.png)]