04 Java SE 多線程
By Kevin Song
- 04-01 多線程概述
- 04-02 JVM中的多線程
- 04-03 多線程創建
- 04-04 多線程同步
- 04-05 多線程中的單例模式
- 04-06 多線程死鎖
- 04-07 多線程通信
- 04-08 多線程停止
04-01 多線程概述
進程:正在進行中的程序
線程:進程中的一個負責程序執行的控制單元(執行路徑)
- 一個進程中可以有多個線程
- 一個進程中至少有一個線程
多線程作用:開啓多個線程是爲了同時運行多部分代碼
多線程的優缺點:
- 優點:可以同時運行多個程序
- 缺點:內存處理到程序頻率變低,運行速度變慢
04-02 JVM中的多線程
JVM啓動時就啓動了多個線程,至少有兩個線程
- 執行主方法的線程:該線程的任務代碼都定義在主方法中
- 負責垃圾回收的線程
Object 類中的 finalize() 方法:
當垃圾回收器確定不存在該對象的更多引用時,由對象的垃圾回收器調用此方法
System類中的 System.gc() 方法:運行垃圾回收器,垃圾回收器運行時間隨機
class Demo extends Object {
public void finalize() {
System.out.println("Recycle Complete");
}
}
class ThreadDemo {
public static void main(String[] args) {
new Demo();
new Demo();
System.gc(); //
new Demo();
System.out.println("Hello World!");
}
}
/*
輸出
Hello World!
Recycle Complete
Recycle Complete
因爲是兩個線程,先運行主線程,JVM關閉前運行垃圾回收線程
*/
04-03 多線程創建
主方法單線程運行
不創建多線程
class Demo {
private String name;
Demo (Srting name) {
this.name = name;
}
public void show() {
for(int x = 0; x < 10; x++) {
//y的for循環讓每次輸出都有一定的延遲,但是必須d1都輸出完才輸出d2,這時就需要多線程來讓他們同時輸出
for(int y =-9999999; y < 999999999; y++) {}
System.out.println(name+"...x="+x);
}
}
}
class ThreadDemo {
public static void main(String[] args) {
Demo d1 = new Demo("Sequence Initializing");
Demo d2 = new Demo("序列初始化中");
d1.show();
d2.show();
}
}
創建多線程
目的:開啓一條執行路徑,使得指定的代碼和其他代碼實現同時運行,而此執行路徑運行的指定代碼就是這個執行路徑的任務
任務存放位置:
- JVM創建的主線程的任務都定義在了主方法中
- 自定義線程的任務定義在Thread類的run方法中
創建多線程有兩種方法
- 繼承Thread類
- 實現Runnable接口
創建線程方式一
繼承Thread類
步驟:
- 定義一個類,繼承Thread類
- 重寫Thread類中的 run(); 方法
- 直接創建Thread類的子類對象(創建線程)
- 調用 start(); 方法開啓線程,並調用線程的任務run(); 方法
class Demo extends Thread{
private String name;
Demo (Srting name) {
this.name = name;
}
public void run() {
for(int x = 0; x < 10; x++) {
//currentThread()獲取當前運行中線程的引用
System.out.println(name+"...x="+x+"...Thread Name="+Thread.currentThread().getname());
}
}
}
class ThreadDemo {
public static void main(String[] args) {
Demo d1 = new Demo("Sequence Initializing");
Demo d2 = new Demo("序列初始化中");
d1.start();//開啓線程,調用run方法
d2.start();//開啓線程,調用run方法
//CPU在主線程,d1,d2之間隨機高速切換
}
}
Thread類中的方法&線程名稱
getName()方法
獲取線程的名稱Thread-編號(從0開始)
主線程的名字main
currentThread()方法
獲取當前運行中線程的引用,該方法爲靜態,可以直接被類名調用
多線程異常問題
有異常的線程結束運行,run() 方法出棧,線程之間互不影響。
線程的狀態
- new被創建
- start() 運行(具備執行資格,具備執行權)
- 凍結(釋放執行權,釋放執行資格)
- sleep(time) 凍結 時間到自動喚醒
- wait() 凍結 notify 喚醒
- 阻塞(具備執行資格,不具備執行權,正在等待執行權)
- 消亡
- run() 方法結束
- stop() 停止線程
- 凍結(釋放執行權,釋放執行資格)
- start() 運行(具備執行資格,具備執行權)
CPU的執行資格:可以被CPU處理,在處理隊列中排隊
CPU的執行權:正在被CPU處理
創建線程方式二
實現Runnable接口
步驟:
- 定義一個類,實現Runnable接口
- 重寫接口中的run方法,將線程的任務代碼封裝到run方法中
- 通過Thread類創建線程對象,將Runnable接口的子類對象作爲構造方法的參數進行傳遞
- 調用線程對象的start(); 方法開啓線程
class Demo extends Fu implements Runable{ //無法繼承Thread但是需要多線程,通過接口形式完成
public void run() {
show();
}
public void show() {
for(int x = 0; x < 20; x++) {
System.out.println(Thread.currentThread().getName()+"..."+x);
}
}
}
class ThreadDemo {
public static void main(String[] args) {
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start;
t2.start;
}
}
第二種方法的好處:
- 將線程的任務從線程的子類中分離出來,進行了單獨的封裝,按照面向對象的思想將任務封裝成對象
- 避免了java單繼承的侷限性
04-04 多線程同步
賣票示例
四個線程,每個線程都賣100張票
class Ticket extends Thread{
private int num = 100;
public void run() {
sale();
}
public void sale() {
while(true) {
if(num > 0) {
System.out.println(Thread.currentThread().getName()+“...sale...”num--);
}
}
}
}
class TicketDemo {
public static void main(String[] args) {
//四個對象,每個對象都有100張票
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
四個線程一起賣100張票
class Ticket implements Runnable {
private int num = 100;
public void run() {
sale();
}
public void sale() {
while(true) {
if(num > 0) {
System.out.println(Thread.currentThread().getName()+“...sale...”num--);
}
}
}
}
class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket(); //一個對象,所有一共只有100張票
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);
Thread t4 = new Thread(t);
t1.start();
t2.start();
t3.start();
t4.start();
}
}
線程安全問題的現象
會出現0,-1,-2張票的情況,因爲線程1還沒運行到num–的時候,線程2,線程3有可能就已經加載進來,當num–運行完之後,線程還會繼續運行,這就導致0,-1,-2的出現。
安全問題產生的原因
- 多個線程在操作共享的數據
- 操作共享數據的線程代碼有多條
當一個線程在執行操作共享數據的多條代碼過程中,其他線程參與了運算
多線程同步
將多條操作共享的數據的線程代碼封裝起來,當有線程在執行這些代碼的時候,其他線程不可以參與運算。
同步代碼塊
格式
synchronized(對象) {
需要被同步的代碼;
}
class Ticket implements Runnable {
private int num = 100;
Object obj = new Object; //此對象爲了傳給synchronized作爲鎖
public void run() {
sale();
}
public void sale() {
while(true) {
synchronized(obj) { //synchronized修飾的代碼塊只能同一時間被一個線程調用
if(num > 0) {
try {
Thread.sleep(10);
} catch (InterruptedException e) {}
System.out.println(Thread.currentThread().getName()+“...sale...”num--);
}
}
}
}
}
class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket(); //一個對象,所有一共只有100張票
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
Ticket t3 = new Ticket();
Ticket t4 = new Ticket();
t1.start();
t2.start();
t3.start();
t4.start();
}
}
同步的前提:
- 同步中必須有多個線程並使用同一個鎖
- 當作鎖的對象必須在 run() 方法外創建
同步的優缺點:
- 優點:解決了線程的安全問題
- 缺點:相對降低了效率,因爲同步外的線程都會判斷同步鎖
同步方法
/*
兩個客戶每次去銀行存100,存三次
*/
class Bank {
private int sum;
private Object obj = new Object();
public synchronized void add(int num) {//同步方法
// synchronized(obj) {
sum = sum + num;
System.out.println("sum="+sum);
// }
}
}
class Cus implements Runnable {
private Bank b = new Bank();
public void run() {
for(int x = 0; x < 3; x++) {
b.add(100);
}
}
}
class BankDemo {
public static void main(String[] args) {
Cus c = new Cus;
Thread t1 = new Thread(c);
Thread t2 = new Thread(c);
t1.start();
t2.start();
}
}
/*
輸出:
sum=100
sum=200
sum=300
sum=400
sum=500
sum=600
*/
同步代碼塊和同步方法同時使用
class Ticket implements Runnable {
private int num = 100;
Object obj = new Object; //此對象爲了傳給synchronized
boolean flag = true;
public void run() {
if(flag)//flag爲true則運行同步代碼快
while(true) {
synchronized(this) {
if(num>0) {
try {
Thread.sleep(10);
} catch(InterruptedException e) {}
System.out.println(Thread.currentThread().getName()+"...obj..."+num--);
}
}
}
else //flag爲false則運行同步方法
while(true)
this.show();
}
public synchronized void show() {
if(num > 0) {
try {
Thread.sleep(10);
} catch ()
System.out.println(Thread.currentThread().getName()+“...syn...”num--);
}
}
}
class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket(); //一個對象,所有一共只有100張票
Ticket t1 = new Ticket();
Ticket t2 = new Ticket();
t1.start();
try {
Thread.sleep(10);
} catch(InterruptedException e) {}
t.flag = false;
t2.start();
}
}
同步方法和同步代碼快的區別:
- 同步方法的鎖是固定的this(當前對象)
- 同步代碼塊的鎖是任意的對象
靜態同步方法使用的鎖:
該方法所屬字節碼文件對象,可以用如下方式獲取
- this.getClass() 僅限非靜態方法中使用
- Ticket.class
04-05 多線程中的單例模式
使用同步雖然可以避免安全問題,但是會導致程序運行效率變低,因爲每個線程都需要判斷是否可以拿鎖
//餓漢式(單例設計模式)
class Single {
private static final Single s = new Single();
private Single() {}
public static Single getInstance() {
return s;
}
}
//懶漢式(延遲加載單例設計模式)
class Single {
private static Single s = null;
private Single() {}
public static Single getInstance() {
if(s == null) {//解決效率問題:多加一句判斷,後續線程不會判斷是否可以拿鎖
//不用this.getClass()是因爲getClass()是非靜態方法
synchronized(Single.class) {//解決安全問題
if(s == null)
s = new Single();
}
}
return s;
}
}
class SingleDemo {
public static void main(String[] args) {
System.out.println();
}
}
04-06 多線程死鎖
同步代碼塊嵌套
class Test implements Runnable{
private boolean flag;
Test(boolean flag) {
this.flag = flag;
}
public void run() {
if(flag) {
synchronized(MyLock.locka) {
System.out.println("if...locka...");
synchronized(MyLock.lockb) {
System.out.println("if...lockb...");
}
}
} else {
synchronized(MyLock.lockb) {
System.out.println("if...lockb...");
synchronized (MyLock.locka) {
System.out.println("if...locka...");
}
}
}
}
}
class MyLock {
public static final Object locka = new Object();
public static final Object lockb = new Object();
}
class DeadLockTest {
public static void main(String[] args) {
Test a = new Test(true);
Test b = new Test(false);
Thread t1 = new Thread(a);
Thread t2 = new Thread(b);
t1.start;
t2.start;
}
}
04-07 多線程通信
定義:多個線程在處理同一資源,任務卻不同
等待喚醒機制
涉及的方法:
- wait(); 讓線程處於凍結狀態,釋放執行權和執行資格,存儲到線程池中
- notify(); 喚醒線程池中一個線程
- notifyAll(); 喚醒線程池中所有線程,防止多生產多消費的死鎖問題
這些方法都必須定義在同步中,因爲這些方法都是用於線程狀態的方法,必須要明確到底操作的是哪個鎖上的線程。
等待喚醒機制中的方法(wait(); notify(); notifyAll();)定義在Object類中,因爲這些方法是監視器的方法,監視器其實就是鎖
//資源
class Resource {
private String name;
private String sex;
privateboolean flag = false; //用來判斷是否可以輸入輸出
public synchronized void set(String name) {
if(flag) //如果flag爲false,則不wait
try {
this.wait();
} catch (InterruptException e) {}
this.name = name;
this.sex = sex;
flag = true;
this.notify();
}
public synchronized void out() {
if(!flag)
try {
this.wait();
} catch (InterruptException e) {}
System.out.println(name+"..."+sex);
flag = false;
notify();
}
}
//輸入
class Input implements Runnable {
Resource r;
Input(Resource r) {
this.r = r;
}
public void run() {
int x = 0;
while(true) {
if(x == 0) { //控制兩種數據輸入
r.set("Kevin","Male")
} else {
r.set("Lily","Female");
}
x = (x+1)%2;
}
}
}
//輸出
class Output implements Runnable {
Resource r;
Output(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.out();
}
}
}
}
class ResourceDemo {
public static void mian(String[] args) {
//創建資源
Resource r = new Resource();
//創建任務
Input in = new Input(r);
Output out = new Output(r);
//創建線程
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
//開啓線程
t1.start();
t2.start();
}
}
多生產者多消費者問題
多生產多消費與單生產單消費的區別
flag判斷語句區別
- if只判斷標記一次,會導致不該運行的線程開始運行,出現數據錯誤
- while可以循環判斷標記,解決了線程獲取執行權後,是否要運行的問題
喚醒語句區別
- notify()只能喚醒一個線程,如果喚醒了本方,沒有意義,並且會導致死鎖,所有線程都進線程池
- notifyAll()解決了,本方線程一定會喚醒對方線程
//定義資源
class Resource {
private String name;//資源名稱
private int count = 1;//計數器
private boolean flag = false;
public synchronized void set(String name) {
while(flag)//flag爲false時不wait,直接運行下面內容。
try {
this.wait();//當flag爲true時進入wait狀態
} catch (InterruptException e) {}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
flag = true; //flag改成true
notify(); //隨機喚醒一個線程,如果是多生產多消費者則用notifyAll();
}
public synchronized void out() {
while(!flag)
try {
this.wait();
} catch (InterruptException e) {}
System.out.println(Thread.currentThread().getName()+"...消費者..."+this.name);
flag = false;
notify();//隨機喚醒一個線程,如果是多生產多消費者則用notifyAll();
}
}
class Producer implements Runnable {
private Resource r;
Producer(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.set("烤鴨");
}
}
}
class Producer implements Runnable {
private Resource r;
Producer(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.out();
}
}
}
class ProducerConsumerDemo {
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(con);
t0.start();
t1.start();
}
}
JDK1.5新特性
Lock
Lock接口:代替了同步代碼快或者同步方法,將同步的隱式鎖操作變成顯式鎖操作,更爲靈活,可以一個鎖上加多組監視器。
- lock(); 獲取鎖
- unlock(); 釋放鎖,通常需要定義在finally代碼塊中
Lock lock = new ReentrantLock();
public void run() {
lock.lock();
try {
code;
} finally {
lock.unlock();
}
}
Condition
Condition接口:代替了Object中的wait notify notifyAll方法。這些監視器被封裝,變成Condition監視器對象,可以任意鎖進行組合
- await(); 功能同wait()
- signal(); 同notify()
- signalAll(); 同notifyAll()
1.5版本的多生產者多消費者代碼
//定義資源
class Resource {
private String name;//資源名稱
private int count = 1;//計數器
private boolean flag = false;
//創建一個鎖對象
Lock lock = new ReentrantLock();
//通過已有的鎖獲取該鎖上的監視器對象
//Condition con = lock.newCondition();
Condition Producer_con = lock.newCondition();
Condition Consumer_con = lock.newCondition();
public void set(String name) {
lock.lock();
try {
while(flag)//flag爲false時不wait,直接運行下面內容。
try {
Producer_con.await();//當flag爲true時進入wait狀態
} catch (InterruptException e) {}
this.name = name + count;
count++;
System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
flag = true; //flag改成true
//notifyAll();
//con.signalAll();
consumer_con.signal();
} finally {
lock.unlock();
}
}
public synchronized void out() {
lock.lock();
try {
while(!flag)
try {
consumer_con.await();
} catch (InterruptException e) {}
System.out.println(Thread.currentThread().getName()+"...消費者..."+this.name);
flag = false;
//notifyAll();
//con.signalAll();
//Producer_Con.signal();
} finally {
lock.unlock();
}
}
}
class Producer implements Runnable {
private Resource r;
Producer(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.set("烤鴨");
}
}
}
class Consumer implements Runnable {
private Resource r;
Producer(Resource r) {
this.r = r;
}
public void run() {
while(true) {
r.out();
}
}
}
class ProducerConsumerDemo {
public static void main(String[] args) {
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t0 = new Thread(pro);
Thread t1 = new Thread(con);
t0.start();
t1.start();
}
}
wait和sleep區別
- 時間指定不同
- wait可以指定時間也可以不指定
- sleep**必須**指定時間
- 在同步中時,對CPU的執行權和鎖的處理不同
- wait:釋放執行權,釋放鎖
- sleep:釋放執行權,不釋放鎖
04-08 多線程停止
停止線程
- stop方法,已經過時
- 定義標記,使run方法結束
定義標記
定義一個標記flag,當flag爲true時線程可以運行,當flag爲負時線程停止運行。
侷限性:當有wait() 的時候會導致線程無法繼續判斷flag而進程凍結
class StopThread implements Runnable {
boolean flag = true; //定義flag標記
public void run() {
while(flag) { //flag標記爲true時運行run方法
System.out.println(Thread.currentThread().getName()+"...");
}
}
public void setFlag() { //定義設置flag爲false的方法
flag = false;
}
}
class StopThreadDemo {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 1;
for(;;) {
if(++num==50) {
st.setFlag();
break;
}
System.out.println("main..."+num);
}
System.out.println("over");
}
}
interrupt()方法
Interrput() 方法可以把線程從凍結狀態強制恢復到運行狀態中來
class StopThread implements Runnable {
boolean flag = true; //定義flag標記
public synchronized void run() {
while(flag) { //flag標記爲true時運行run方法
try {
wait();
} catch (InterruptedException e) {
System.out.println(Thread.currentThread().getNmae()+"..."+e);
flag = false;
}
System.out.println(Thread.currentThread().getName()+"...");
}
}
public void setFlag() { //定義設置flag爲false的方法
flag = false;
}
}
class StopThreadDemo {
public static void main(String[] args) {
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.start();
t2.start();
int num = 1;
for(;;) {
if(++num==50) {
//st.setFlag();
t1.interrupt();//強制恢復運行
t2.interrupt();//強制恢復運行
break;
}
System.out.println("main..."+num);
}
System.out.println("over");
}
}
setDaemon()方法
使線程變成守護線程(後臺線程),前臺線程結束之後後臺線程自動結束
join()方法
執行權讓給t1
t1.join();
setPriority()方法
設置線程優先級
t1.setPriority(Thread.MAX_PRIORITY);
- MAX_PRIORITY
- MIN_PRIORITY
- NORM_PRIORITY