JAVA執行緒(簡單)
阿新 • • 發佈:2019-01-12
多執行緒:就是應用程式有多條執行路徑
執行緒:是程序的執行單元,執行路徑。
如果一個應用程式只有一條只有執行路徑,那麼,該程式就是單執行緒程式
如果一個應用程式有多條執行路徑,那麼,該程式就是多執行緒
程序:正在執行的應用程式,
每個程序具有獨立的空間。
我們目前的作業系統都是支援多程序的。
舉例:360,迅雷
一個人吃一桌飯:單程序單執行緒
多個人吃一桌飯:單程序多執行緒
多執行緒的好處:效率高一點
多執行緒的問題:
多個執行緒針對同一個資源,導致資料會有安全問題,資源衝突
我們怎麼實現多執行緒程式?
假設JAVA沒有提供,怎麼辦?
我們要想實現多執行緒,首先要去建立一個程序,然後將程序拆分成多個執行緒
問題來了:程序是由作業系統根據應用程式的執行產生的,也就是,我想要建立程序,我們得去操作作業系統的資源,但是,java程式是不能直接操作作業系統的資源的,那麼,我們直接通過java語言做這件事情是實現不了的。
所以,java就提供了執行緒的類供我們使用,
但是,底層肯定是C 或者C++去完成這件事情,這個操作被包裝了,我們看不多,把C或C++的程式按照java的模式去編寫,然後用java去呼叫了C或C++的方法。
接下來,我們只能依賴Thread實現。
方法一:通過繼承Thread類
通過檢視API,可以知道,多執行緒程式的模擬有兩種方法
A:繼承Thread類
B:實現Runnable介面
繼承Thread類的步驟:
A:自定義類實現Thread類
B:在自定義類中重寫run()方法
為什麼要重寫run()方法呢?
因為run()方法中的封裝的程式碼才是可以被執行緒執行的。
C :建立自定義類的物件
D:啟動執行緒並使用
如何啟動?
執行緒物件建立後,將來呼叫的肯定是run方法裡面的程式碼
但是,必須通過start()方法啟動
run()和start()的區別
run()封裝了被執行緒執行的程式碼
start():讓執行緒啟動,並由jvm呼叫run()方法。
如何獲取到執行時的執行緒的名字?
getName()//返回執行緒名稱,Thread類中的方法
設定執行緒的名字:
一:自定義名字:
setName(String name)//更改執行緒名稱
二:Thread(String name):父類的帶參構造。
自定義類呼叫上面的方法:
public myThread(String name){
super(name);
}
方法二:通過實現Runnable介面
實現Runnable介面的方式:
A:自定義類MyRunnable實現Runnable介面
B:重寫run()方法
C:建立自定義類的物件
D:建立Thread類的物件,將MyRunnable類的物件作為構造引數傳遞
E:??呼叫Thread的物件呼叫start方法。
針對實現介面的這種方法,沒有辦法獲取名稱,所以java就提供了一個靜態方法。獲取當前正在執行的執行緒物件
public static Thread currentThread();
然後通過執行緒物件去獲取物件的名稱。
設定執行緒名字:
A:setName()
B:Thread(Runnable target, String name)通過建構函式引數直接設定名字。
問題1:繼承Thread類和實現Runnable有什麼區別?
因為類只能單繼承!!!!!!!!!!!!!
比如:
我有一個fu類,一個Zi類,Zi類已經繼承了父類,要想實現多繼承,怎麼辦?
實現Runnable介面。
問題2:繼承Thread類的方式,子類本身就是一個執行緒類,實現Runnable介面的方式,具體類本身是不是一個執行緒例項呢?
不是!因為如果是的話,就可以直接呼叫setName方法去設定執行緒的名字了。
應用程式的執行:
CPU在多個應用程式之間進行著高效的切換執行,這個切換的時間很短,短到你感覺不到,所以,我們覺得我們的程式是在同時併發的執行, 但是準確的說:CPU在某個時間點上只能有一個程式在執行
多執行緒的好處
開多執行緒的目的是提高CPU的使用率,進而來提高程式的效率。
面試題:執行緒的生命週期
新建:建立執行緒物件
就緒:準備隨時執行,具備執行資格,沒有執行權
執行:執行Run中程式碼
死亡:run結束
阻塞:在執行過程中出現意外情況,,當情況唄解決後,就回到就緒狀態。
程式碼一:Thread
package com.thread.ticket.thread;
/**
* 我有一百張票,放在4個視窗賣
*
* 執行緒是共享程序的資源
* 一個JAVA程式是 一個程序
* 棧:每個執行緒都有自己的一份
* 堆:共享的
* 方法區:共享的
*
* 問題:
* A:把int ticket = 100 ;放到run()方法中,每次呼叫run()都會重新定義100張票。
* 每個執行緒都有自己的棧空間,那麼,每個執行緒物件都會擁有自己的100張票
* B:把private int tickets =100 ;定義到了成員變數的位置,還是會有問題
* 按照正常的思緒,應該是沒有問題了的,但是還是出現了問題。
* 原因很簡單,因為我們建立了四個物件,而普通的成員變數是每個物件所特有的。
* 也就是說,每個執行緒物件還是擁有自己的100張票。
* 我們的目的是:這四個執行緒物件共同擁有100張票,請問怎麼能夠實現?
* 加static
*/
public class TicketDemo {
public static void main(String[] args) {
Ticket t = new Ticket("視窗1");
Ticket t1 = new Ticket("視窗2");
Ticket t2 = new Ticket("視窗3");
Ticket t3 = new Ticket("視窗4");
t.start();
t1.start();
t2.start();
t3.start();
}
}
package com.thread.ticket.thread;
public class Ticket extends Thread {
private static int tickets = 100;
public Ticket() {
}
public Ticket(String name) {
super(name);
}
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(getName() + "正在出售第" + (tickets--) + "張票");
}
}
}
}
程式碼二:Runnable優化
package com.thread.ticket.runnable;
/**
* 我有一百張票,放在4個視窗賣
* 通過Runnable改進賣票程式
*/
public class TicketDemo {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Thread t = new Thread(t1, "視窗1");
Thread t4 = new Thread(t1, "視窗2");
Thread t2 = new Thread(t1, "視窗3");
Thread t3 = new Thread(t1, "視窗4");
t.start();
t4.start();
t2.start();
t3.start();
}
}
package com.thread.ticket.runnable;
public class Ticket implements Runnable {
private static int tickets = 100;
@Override
public void run() {
while (true) {
if (tickets > 0) {
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票");
}
}
}
}
程式碼三:執行緒安全問題分析與討論
package com.thread.ticket1;
/**
* 我有一百張票,放在4個視窗賣
* 通過Runnable改進賣票程式
* //模擬正常的現象,我讓執行緒稍微等一下。
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
*問題:出現相同的票賣了兩次,以及出現負數票。
*A:相同票賣了兩次:
* CPU的每一次執行,必須是一個原子性的操作,這個操作是不能再分隔的,
* int i= 10; 其實是兩個原子性動作。
* 這樣的話
* tickets-- 也不是一個原子性動作,可能在操作的中間部分,被其他的執行緒給執行了,
* 這樣就會有相同的票執行了多次
* 面試題:
* if(true)
* int a=10;
* //if語句沒有寫大括號,預設情況下,只負責處理跟在其後面的一條語句,
* 而:int a = 10; 其實不是一個語句;
* 等價:
* int a;
* a=10;
*B:負票:
* 因為執行緒的隨機性,
*/
public class TicketDemo {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Thread t = new Thread(t1, "視窗1");
Thread t4 = new Thread(t1, "視窗2");
Thread t2 = new Thread(t1, "視窗3");
Thread t3 = new Thread(t1, "視窗4");
t.start();
t4.start();
t2.start();
t3.start();
}
}
package com.thread.ticket1;
public class Ticket implements Runnable {
private static int tickets = 100;
/* @Override
public void run() {
while (true) {
//t1,t2,t3,t4
//tickets = 100;
//假設t1搶到了CPU的執行權
if (tickets > 0) {
//模擬正常的現象,我讓執行緒稍微等一下。
try {
Thread.sleep(100);//t1睡眠了。t2搶到了CPU的執行權,t2進來後,也睡眠了
} catch (InterruptedException e) {
e.printStackTrace();
}
//t1醒過來,繼續執行
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票");
//視窗1正在出售100張票
//tickets = 99
//這僅僅是我們自己的理想狀況,但是實際情況並不是這個樣子的。
//實際情況是:每一次執行緒執行的程式應該是一個原子性的操作。也就是這個操作是不能再分割的。
//tickets--這個動作有tickets-1=99和tickets=99兩個動作
//這裡的話,至少是兩個動作,那麼在-1的動作執行時候,並沒有給tickets重新賦值。
//t2醒過來了,這個時候tickets還是100,所以:這個時候,視窗二正在執行100張票。
}
}
}*/
@Override
public void run() {
while (true) {
//t1,t2,t3,t4
//tickets = 100;
//假設t1搶到了CPU的執行權
//t2搶到了
//t3搶到了
//t4搶到了
if (tickets > 0) {
//t1睡了
//t2睡了
//t3睡了
//t4睡了
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票");
/**
* t1醒過來了
* 視窗1正在出售第1張票 tickets = 0
* t2醒過來了
* 視窗2正在出售第0張票 tickets = -1
* t3醒過來了
* 視窗3正在出售第-1張票 tickets = -2
* t4醒過來了
* 視窗4正在出售第-2張票 tickets = -3
*/
}
}
}
}
程式碼4:執行緒安全的問題解決
package com.thread.ticket2;
/**
* 這裡解決上面程式碼出現負票的方法:
*
* 首先:多執行緒出現安全問題的原因:
* A:是多執行緒程式;
* B:有共享資料
* C:針對共享資料有多條語句操作
*
* 解決:
* 只需要把多執行緒環境中,操作共享資料的操作給變成單執行緒的就沒有問題了。
* java針對這種情況,就提供了同步技術,,同步程式碼塊
* 格式:
* synchronized(物件){
* 需要被同步的程式碼;
* }
* A:物件?
* 如果不知道用哪個物件,就使用Object,
* B:需要被同步的程式碼塊?
* 哪些程式碼導致出現了問題,就把哪些程式碼同步起來。
*
* 哪些程式碼會出現問題呢?
* 有共享資料
* 針對共享資料有多條語句操作
* 加入同步後發現還有問題,為什麼??
* while (true) {
synchronized (new Object()) {
if (tickets > 0) {
try {
Thread.sleep(100);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票");
}
}
}
* 同步程式碼塊中的物件針對多個執行緒必須是同一個,
* 其實這個物件被稱為同步鎖物件
*
*/
public class TicketDemo {
public static void main(String[] args) {
Ticket t1 = new Ticket();
Thread t = new Thread(t1, "視窗1");
Thread t4 = new Thread(t1, "視窗2");
Thread t2 = new Thread(t1, "視窗3");
Thread t3 = new Thread(t1, "視窗4");
t.start();
t4.start();
t2.start();
t3.start();
}
}
package com.thread.ticket2;
/**
* 類似火車上廁所的例子
*
* @author yuliyang
* @version $Id: Ticket.java, v 0.1 2016年11月30日 下午10:02:32 yuliyang Exp $
*/
public class Ticket implements Runnable {
private static int tickets = 100;
private final Object b = new Object();
@Override
public void run() {
while (true) {
/**
* t1,t2,t3,t4
* 假設 ticket = 1
* t1過來,看到synchronized就知道下面的程式碼被同步了。
* 看到obj就知道這是這是鎖物件,假設t1進來前,obj這個鎖是開的狀態
*
*/
synchronized (b) {
/**
* t1進來後,就把鎖物件的狀態改為關的狀態。
*/
if (tickets > 0) {
try {
Thread.sleep(100);
//t1睡著了。
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票");
}
//t1出來了,你們繼續搶,我也還可以搶。
}
}
}
}