-----------android培訓、java培訓、java學習型技術博客、期待與您交流!------------
概述
1、 進程
正在運行的程序,是系統進行資源分配和調用的獨立單位。
每一個進程都有它自己的內存空間和系統資源。
2、線程
是進程中的單個順序控制流,是一條執行路徑
一個進程如果只有一條執行路徑,則稱爲單線程程序。
一個進程如果有多條執行路徑,則稱爲多線程程序。
3、Java程序運行原理
java 命令會啓動 java 虛擬機,啓動 JVM,等於啓動了一個應用程序,也就是啓動了一個進程。該進程會自動啓動一個 “主線程” ,然後主線程去調用某個類的 main 方法。所以 main方法運行在主線程中。在此之前的所有程序都是單線程的。
並且要注意的是,jvm虛擬機的啓動是多線程的,因爲除了主線程之外還會啓動負責垃圾回收機制的線程。
線程的創建
1、 繼承Thread類。又稱爲繼承方式。
創建步驟:
a,定義類繼承Thread。
b,複寫Thread中的run方法。
目的:將自定義代碼存儲在run方法中,讓線程運行。
c,創建定義類的實例對象。相當於創建一個線程。
d,用該對象調用線程的start方法。該方法的作用是:啓動線程,調用run方法。
發現運行結果每次都不同。
因爲多個線程都在獲取cpu的執行權。cpu執行到誰,誰就運行。
明確一點,在某一時刻,只能有一個程序在運行。(多核除外)
cpu在做着快速的切換,已達到看上去是同時運行的效果。
我們可以形象把多線程的運行行爲看做是在互相搶奪cpu的執行權。
這就是多線程的一個特性:隨機性。誰搶到誰執行,至於執行多長,由cpu說的算
爲什麼要覆蓋run方法?
Thread類用於描述線程。
該類就定義了一個功能,用於存儲線程要運行的代碼。該存儲功能就是run方法
也就是說Thread類中的run方法,用於存儲線程要運行的代碼
start調用底層讓控制單元執行
run僅僅是封裝線程要運行的代碼
程序示例:
class Demo extends Thread{
public void run(){
System.out.println("demo run");
}
}
class ThreadDemo{
public static void main(String[] args){
Demo d = new Demo();//創建好一個線程
d.start();//開啓線程並執行該線程的run方法
d.run();//僅僅是對象調用方法。而線程創建了並沒有運行
}
測試用例:
/*
創建兩個線程,和主線程交替運行。
*/
class ThreadTest{
public static void main(String[] args){
Test tt = new Test("one");
Test ts = new Test("two");
tt.start();
ts.start();
for (int i = 0;i<60 ;i++ ){
System.out.println("main....."+i);
}
}
}
class Test extends Thread{
private String name;
Test(String name){
this.name = name;}
public void run(){
for (int i = 0 ;i<60 ;i++ ){
System.out.println(name+"run....."+i);
}
}
}
如何獲取和設置線程名稱:
線程都有自己默認的名稱。
Thread-編號 該編號從0開始
static Thread currentThread():獲取當前線程對象
getName():獲取線程名稱
public final String getName()
public final void setName(String name)
其實通過構造方法也可以給線程起名字
演示用例:
class Test extends Thread{
//private String name;
Test(String name){
//this.name = name;
super(name);//自定義線程名稱
}
public void run(){
for (int i=0;i<60 ;i++ ){
System.out.println((Thread.currentThread()==this)this.getName()+"run....."+i);
}
}
class ThreadTest{
public static void main(String[] args){
Test t1 = new Test("one");
Test t2 = new Test("two");
t1.start();
t2.start();
for (int i=0;i<60 ;i++ ){
System.out.println("main......"+i);
}
}
線程調度:
線程有兩種調度模型:
分時調度模型 所有線程輪流使用 CPU 的使用權,平均分配每個線程佔用 CPU 的時間片
搶佔式調度模型 優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那麼會隨機選擇一個,優先級高的線程獲取的 CPU 時間片相對多一些。
Java使用的是搶佔式調度模型。
線程的運行狀態:
被創建:等待啓動,調用start啓動。只要有進程,線程就不會結束。
運行狀態:具有執行資格和執行權。
臨時狀態(阻塞):有執行資格,但是沒有執行權。
凍結狀態:遇到sleep(time)方法和wait()方法時,失去執行資格和執行權,sleep方法時間到或者調用notify()方法時,獲得執行資格,變爲臨時狀態。
消忙狀態:stop()方法,或者run方法結束。
2、 實現Runnable接口,又稱爲實現方式。
使用繼承方式有一個弊端,那就是如果該類本來就繼承了其他父類,那麼就無法通過Thread類來創建線程了。所以,爲了解決這一弊端就引入了創建線程的第二種方式:實現Runnable接口
創建步驟如下:
a,定義類實現Runnable的接口。
b,覆蓋Runnable接口中的run方法。目的也是爲了將線程要運行的代碼存放在該run方法中。
c,通過Thread類創建線程對象。
d,將Runnable接口的子類對象作爲實參傳遞給Thread類的構造方法。
爲什麼要將Runnable接口的子類對象傳遞給Thread的構造函數?
因爲,自定義的run方法所屬的對象是Runnable接口的子類對象。所以要讓線程去指定對象的run方法,就必須明確該run方法所屬對象。
e,調用Thread類中start方法啓動線程。start方法會自動調用Runnable接口子類的run方法。
實現方式好處:避免了單繼承的侷限性。在定義線程時,建議使用實現方式。
適合多個相同程序的代碼去處理同一個資源的情況,把線程同程序的代碼,數據有效分離,較好的體現了面向對象的設計思想。
程序示例:
class TicketDemo{
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);//創建線程並指定run方法所屬對象
Thread t2 = new Thread(t);
Thread t3 = new Thread(t);//new線程的同時,指定run方法所屬對象
t1.start();
t2.statt();
t3.start();
}
}
class Ticket implements Runnable{
private int tick = 100;
public void run(){
while (true){
System.out.println(Thread.currentThread().getName()+"sale:"+tick--);
}
}
}
兩種方法的區別:
實現方式:避免了單繼承的侷限性。
在定義線程時,建議使用實現方式
繼承Thread類:線程代碼存放在Thread子類run方法中
實現Runnable:線程代碼存放在接口子類的run方法中
線程安全問題
1、導致安全問題的出現的原因:
當多條語句在操作同一線程共享數據時,一個線程對多條語句只執行了一部分,還沒用執行完,另一個線程參與進來執行。導致共享數據的錯誤。
簡單的說就兩點:
a、多個線程訪問出現延遲。
b、線程隨機性 。
注:線程安全問題在理想狀態下,不容易出現,但一旦出現對軟件的影響是非常大的。
2、解決辦法
基本思想:讓程序沒有安全問題的環境。
把多個語句操作共享數據的代碼給鎖起來,讓任意時刻只能有一個線程執行即可。
在java中對於多線程的安全問題提供了專業的解決方式——synchronized(同步)
同步代碼塊
格式:
synchronized(對象){需要同步的代碼;}
同步可以解決安全問題的根本原因就在那個對象上。該對象如同鎖的功能。持有鎖的線程可以在同步中執行。沒有持有鎖的線程即使獲取cpu的執行權,也進不去,因爲沒有獲取鎖。
同步的前提:
1、必須要有兩個或者以上的線程。
2、必須是多個線程使用同一個鎖。
必須保證同步中只能有一個線程在進行
好處:解決了多線程的安全問題
弊端:對鎖進行判斷,較爲消耗資源(在允許消耗範圍內),
演示用例:
class {
public static void main(String[] args){
}
}
class Ticket implements Runnable{
private int tick = 100;
Object obj = new Object();
public void run(){
while (true){
synchronized (obj){
if (tick>0){
try{
Thread.sleep(10);
}
catch (Exception e){
}
}
}
}
同步函數
格式:
在函數上加上synchronized修飾符即可。
演示用例:
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();
}
}
class Bank{
private int sum;
//Object obj = new Object();
public synchronized void add(int n){
//synchronized{//同步在操作共同數據的多態語句
sum = sum+n;
System.out.println("sum="+sum);
// }
}
class Cus implements Runnable{
private Bank b = new Bank();
public void run(){
for (int i = 0;i<3 ;i++ ){
b.add(100);
}
}
那麼同步函數用的是哪一個鎖呢?
函數需要被對象調用。那麼函數都有一個所屬對象引用。就是this。所以同步函數使用的鎖是this。
驗證:
使用兩個線程賣票
一個再同步代碼塊中
一個再同步函數中
都在執行賣票動作,若同步就不會出現錯誤的票
class {
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{
Thread.sleep(p);
}
catch (Exception e){
}
t.flag = false;
t2.start();
}
}
class Ticket implements Runnable{
private int tick = 1000;
Object obj = new Object();
boolean flag = ture;
public void run(){
if (flag){
while (true){
synchronized (this){//obj
if (tick>0){
try{
Thread.sleep(10);
}
catch (Exception e){
} System.out.println(Thread.currentThread().getName()+"....code...."+tick--);
}
}
}
}else
while (true){
show();
}
}
public synchronized void show(){
if (tick>0){
try{
Thread.sleep(10);
}
catch (Exception e){
} System.out.println(Thread.currentThread().getName()+".....show..."+tick--);
}
}
}
}
如何尋找多線程中的安全問題
a,明確哪些代碼是多線程運行代碼。
b,明確共享數據。
c,明確多線程運行代碼中哪些語句是操作共享數據的。
靜態函數的同步方式
如果同步函數被靜態修飾後,使用的鎖是什麼呢?
通過驗證,發現不在是this。因爲靜態方法中也不可以定義this。靜態進內存時,內存中沒有本類對象,但是一定有該類對應的字節碼文件對象。如:
類名.class 該對象的類型是Class
這就是靜態函數所使用的鎖。而靜態的同步方法,使用的鎖是該方法所在類的字節碼文件對象。類名.class(內存中唯一)。
演示用例:
class {
public static void main(String[] args){
Ticket t = new Ticket();
Thread t1 = new Thread(t);
Thread t2 = new Thread(t);
t1.start();
try{
Thread.sleep(p);
}
catch (Exception e){
}
t.flag = false;
t2.start();
}
}
class Ticket implements Runnable{
private static int tick = 1000;
//Object obj = new Object();
boolean flag = ture;
public void run(){
if (flag){
while (true){
synchronized (Ticket.class){//obj
if (tick>0){
try{
Thread.sleep(10);
}
catch (Exception e){
}
System.out.println(Thread.currentThread().getName()+"....code...."+tick--);
}
}
}
}else
while (true){
show();
}
}
public static synchronized void show(){
if (tick>0){
try{
Thread.sleep(10);
}
catch (Exception e){
} System.out.println(Thread.currentThread().getName()+".....show..."+tick--);
}
}
}
}
單例設計模式——懶漢式
class Single{//保證一個類內存的唯一性
private static Single s = null;//s爲共享數據,若存在多個線程併發訪問getInstance方法,有多條語句在操作s
private Single(){}
public static synchronized Single getInstance{
if (s == null){
s = new Single();
return s;
}
A進B就不能進,A判斷滿足,創建對象,B進判斷不爲null,直接使用s
懶漢式加了同步每次都要判斷鎖,會比較低效
-------------------------------------------------------------------------------
public static Single getInstance{
if (s = =null){
synchronized(Single.class){
if (s == null){
只要有一個初始化,以後的救不用再判斷鎖,因爲不滿足null
s = new Single();
return s; }
}
懶漢式的特點在於延遲加載,會存在問題。
多線程訪問時會產生安全問題,可以加同步解決,同步函數,同步代碼塊都可以
但是有點低效,用雙重判斷的方式可以解決效率的問題,加同步的時候使用的鎖
爲該類所屬的字節碼文件對象
死鎖
是指兩個或者兩個以上的線程在執行的過程中,因爭奪資源產生的一種互相等待現象
當同步中嵌套同步時,就有可能出現死鎖現象。
演示用例:
/*同步代碼塊的鎖是obj,同步函數的鎖是this
this鎖中有obj鎖,obj鎖中有this鎖*/
class Test{
private boolean flag;
Tead (boolean flag){this.flag = flag;}
public void run(){if (flag){
while (true){
synchronized(MyLock.locka){
System.out.println("if locka");
synchronized(MyLock.lockb){
System.out.println("if lockb");}
}
}}else{
while (){
synchronized(MyLock.lockb){
System.out.println("else lockb");
synchronized(MyLock.locka){
System.out.println("else locka");
}
}}
}}
}
class MyLock{
Object locka = new Object();
Object lockb = new Object();
}
class DeadLockTest{
public static void main(String[] args){
Thread t1 = new Thread(new Test);
}
線程間通信
其實就是多個線程在操作同一個資源,但是操作的動作不同。
演示用例:
class {
public static void main(String[] args){
Res r = new Res();
Input in = new Input(r);
Output out = new Output(r);
Thread t1 = new Thread(in);
Thread t2 = new Thread(out);
t1.start();
t2.satrt();
}
}
class Res{
String name;
String sex;
}
class Input implements Runnable{
private Res r ;
Input (Res r){this.r = r;}
public void run(){
int x = 0;
while (true){
synchronized(r){//保證鎖相同
if (x == 0){
r.name = "";
r.sex = "";}
else{
r.name = "";
r.sex = "";}
x = (x+1)%2;
}//只同步一個無法解決問題
}
}
}
class Output implements Runnable{
private Res r ;
Output (Res r){this.r = r;}
public void run(){
while (true){
synchronized(r){
System.out.println(r.name+""+r.sex);
}//都在操作同一個對象
}
}
}
幾個小問題:
1)wait(),notify(),notifyAll(),用來操作線程爲什麼定義在了Object類中?
a,這些方法存在與同步中。
b,使用這些方法時必須要標識所屬的同步的鎖。同一個鎖上wait的線程,只可以被同一個鎖上的notify喚醒。
c,鎖可以是任意對象,所以任意對象調用的方法一定定義Object類中。
2)wait(),sleep()有什麼區別?
wait():釋放cpu執行權,釋放鎖。
sleep():釋放cpu執行權,不釋放鎖。
3)爲甚麼要定義notifyAll?
因爲在需要喚醒對方線程時。如果只用notify,容易出現只喚醒本方線程的情況。導致程序中的所以線程都等待。
等待喚醒機制:
class {
public static void main(String[] args){
new Thread(new Input(r)).start();
new Thread(new Output(r)).start();
}
}
class Res{
private String name;
private String sex;
private boolean flag = false;
public synchronized void set(){
if (flag){
try{
this.wait();
}
catch (Exception e){
}
this.name = name;
this.sex = sex;}
flag = true;
this.notify();
}//可能存在安全問題,賦值需要被同步,非靜態同步函數,鎖爲this
public synchronized void out(){
if (
1flag){
try{
this.wait();
}
catch (Exception e){
}
System.out.println(name+"..........."+sex);
flag = false;
this.notify();
}//兩個線程,也需要同步
}
class Input implements Runnable{
private Res r ;
Input (Res r){this.r = r;}
public void run(){
int x = 0;
while (true){
if (x == 0){
r.set("","");}
else{
r.set("","");}
x = (x+1)%2;
}
}
}
}
class Output implements Runnable{
private Res r ;
Output (Res r){this.r = r;}
public void run(){
while (true){
r.out();
}
}
}
線程池
程序啓動一個新線程成本是比較高的,因爲它涉及到要與操作系統進行交互。而使用線程池可以很好的提高性能,尤其是當程序中要創建大量生存期很短的線程時,更應該考慮使用線程池。
線程池裏的每一個線程代碼結束後,並不會死亡,而是再次回到線程池中成爲空閒狀態,等待下一個對象來使用。
JDK1.5中提供了多線程升級解決方案。
/*notifyAll不只喚醒了對方,連本方也被喚醒,
現在要求只喚醒對方線程*/
/*JDK1.5開始
java.util.concurrent.locks
|--Lock//替代了synchronized方法和語句的使用,接口
|--lock();獲取鎖
|--unlock();釋放鎖
|--Condition//替代了Object監視器方法的使用,notify,notifyAll,接口
|--await();
|--singnal();
|--singnalAll();
提供 了多線程的升級解決方案升級解決方案的示例:
class {
public static void main(String[] args){
Resource r = new Resource();
Producer pro = new Producer(r);
Consumer con = new Consumer(r);
Thread t1 = new Thread(pro);
Thread t2 = new Thread(con);
t1.start();
t2.start();
}
}
class Resource{
private String name;
private int count = 1;
private boolean flag = false;
private Lock lock = new ReentrantLock();//創建鎖
private Condition con_pro = lock.newCondition();//wait應該定義在同步代碼塊中,同步語句塊有鎖,每個wait都要標識自己所屬的鎖
private Condition con_con = lock.newCondition();
public void set(String name)throws InterruptedException{
Lock.lock();//拿到鎖,進入,執行被鎖代碼,過程中拋出異常,程序結束,功能結束,沒有讀到unlock,說明沒有放鎖,其他進程就無法進入了,所以一定要放鎖
try{
while (flag){//生產者進入,判斷爲真,執行/再回來判斷不爲真,等待
con_pro.await();
this.name = name+""+count++;//帶着編號設置數據
System.out.println(Thread.currentThread().getName()+"...生產者..."+this.name);
flag = true;
con_con.signal();}//喚醒消費者
finally{
Lock.unlock();}
public void out()throws InterruptedException{
Lock.lock();//消費者進入,拿到鎖,判斷不爲真,執行/再回來判斷爲真,等待
try{
while (!flag){
con_con.await();
System.out.println(Thread.currentThread().getName()+"...消費者..."+this.name);
flag = false;
con_pro.signal();}//喚醒生產者
finally{
Lock.unlock();}
}
class Producer implements Runnable{
private Resource res;
Producer(Resource res){
this.res = res;
}
public void run(){
while (true){
try{
res.set("+商品+");
}
catch ( InterruptedException e){
}
}
}
class Consumer implements Runnable{
private Resource res;
Consumer(Resource res){
this.res = res;
}
public void run(){
while (true){
try{
res.out();
}
catch (InterruptedException e){
}
}
}
守護線程
setDaemon()
將該線程標記爲守護線程或用戶線程。當正在運行的線程都是守護線程時,java虛擬機退出。
該方法必須在啓動線程前調用。
我們所看的都是前臺線程,當我們把某些線程標記成後臺線程後,它就就被了一些特殊的含義,後臺線程的特點就是,開啓後和前臺線程共同搶奪cpu的執行權,與前臺線程的區別在於執行結束:
當所有的前臺線程都結束時,後臺線程會自動結束。
演示用例:
class StopThreadDemo{
public static void main(String[] args){
StopThread st = new StopThread();
Thread t1 = new Thread(st);
Thread t2 = new Thread(st);
t1.setDaemon(true);
t2.setDaemon(true);//程序結束
--------------------------------
t1.start();
t2.start();//程序掛起
int num = 0;
while (true){
if (num++ == 60){
//st.changeFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
class StopThread implements Runnable{
private boolean flag = true;
public synchronized void run(){
while (flag){//線程0進入,拿到鎖,凍結,釋放資格;線程1進入,釋放資格
try{
wait();
}
catch (InterruptedException e){
System.out.println(Thread.currentThread().getName()+".........Exception");
flag = false;
}
System.out.println(Thread.currentThread().getName()+".........run");
}
}
public void changeFlag(){
flag = false;
}
}//主線程開啓了兩個線程後與兩個線程一塊搶奪資源,主線程是前臺
停止線程
停止線程
1、定義循環結束的標記
因爲線程運行代碼一般都是循環,只要控制了循環即可
2、使用interrupt(中斷)方法
該方法是結束線程的凍結狀態,使線程回到運行狀態中來
停止線程只有一種方法,run方法結束。
開啓多線程運行,運行代碼通常都是循環結構,只要控制住循環,就可以讓run方法結束,也就是線程結束
演示用例:
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 = 0;
while (true){
if (num++ == 60){
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
}
}
class StopThread implements Runnable{
private boolean flag = true;
public void run(){
while (flag){
System.out.println(Thread.currentThread().getName()+".........run");
}
}
public void changeFlag(){
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 = 0;
while (true){
if (num++ == 60){
//st.changeFlag();
t1.interrupt();
t2.interrupt();
break;
}
System.out.println(Thread.currentThread().getName()+"......."+num);
}
System.out.println("over");
}
}
class StopThread implements Runnable{
private boolean flag = true;
public synchronized void run(){
while (flag){//線程0進入,拿到鎖,凍結,釋放資格;線程1進入,釋放資格
try{
wait();
}
catch (InterruptedException e){
System.out.println(Thread.currentThread().getName()+".........Exception");
flag = false;
}
System.out.println(Thread.currentThread().getName()+".........run");
}
}
public void changeFlag(){
flag = false;
}
}
toString()&Priority
打印線程名稱、優先級和線程組
打印結果:
Thread[Thread-1,5,main]
所以線程默認優先級是5
MAX_PRIORITY 線程可以具有的最高優先級,10。
MIN_PRIORITY 線程可以具有的最低優先級,1。
NORM_PRIORITY 分配給線程的默認優先級,5。
setPriority(Thread.MAX_PRIORITY)設置優先級
yield()暫停當前正在執行的線程對象,並執行其他線程。減緩線程執行的頻率
而且能達到線程平均運行的效果
演示用例:
class JoinDemo{
public static void main(String[] args)throws Exception{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t1.setPriority(Thread.MAX_PRIORITY);
t2.start();
for (int x = 0;x<80 ;x++ ){
System.out.println("main......"+x);
}
System.out.println("over");
}
}
class Demo implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+""+x);
}
}
join方法
主線程碰到誰的join等誰,誰搶到不管
當A線程執行到了B線程的join方法是,A就會等待,等B線程執行完,A纔會執行。
join可以用來臨時加入線程執行
class JoinDemo{
public static void main(String[] args)throws Exception{
Demo d = new Demo();
Thread t1 = new Thread(d);
Thread t2 = new Thread(d);
t1.start();
t1.join();//搶奪cpu執行權,主線程放出執行權,main處於凍結狀態,t1執行結束,main才能執行。當我們在進行多線程運算時,若條件滿足我們可以臨時加入一個線程,讓該線程運算完,再繼續運行
t2.start();
1.join();//main開啓了·1,2,碰到1的join,釋放執行權,但是1,2 存活,那麼cpu就對12交替執行,1什麼時候結束main什麼時候活
for (int x = 0;x<80 ;x++ ){
System.out.println("main......"+x);
}
System.out.println("over");
}
}
class Demo implements Runnable{
public void run(){
System.out.println(Thread.currentThread().getName()+""+x);
}
}
Thread 0執行完,main和t2纔開始交替執行
開發中線程的創建:
擴展小知識:
class ThreadTest{
public static void main(String[] args){
new Thread(){
public void run(){
for (int x = 0;x<100 ;x++ ){
System.out.println("Thread.currentThread().getName()+""+x");
}
}
}.start();
Runnable r = new Runnable(){
for (int x = 0;x<100 ;x++ ){
public void run(){
System.out.println("Thread.currentThread().getName()+""+x");
}
}
};
new Thread(r).start();
}
-----------android培訓、java培訓、java學習型技術博客、期待與您交流!------------