Java se四大塊之線程(上)

一 程序 進程 線程

程序:就是靜態的代碼

進程:是正在運行的程序,系統會分配給他內存空間和資源,你打開兩個軟件時,其實在一個時間點上,只能有一個程序在cpu上運行,電腦是多核,但cpu的個數再多也不能多於進程數 ,你看着兩個軟件是在同時進行,實際上是電腦在頻繁快速切換,一會運行這個程序一會運行另一個,就像是視頻一樣,其實只是圖片,不過圖片的快速切換,讓你覺着他是一連串的動作。進程有自己的地址空間,進程具有動態性,併發性,獨立性。進程的間的切換開銷比較大,所以有時候當你想要切換程序時 會卡頓。

線程:進程內部的執行單元,是程序中一個單一的順序控制流程,也被稱爲輕量級進程,如果在一個進程中同時運行多個線程,用來完成不同工作,則稱之爲多線程。線程可併發執行,共享進程資源。例如wrd文檔,我在打印文檔的同時還能編寫其他 文檔內容。線程間的調度開銷比較小。

二 線程 單線程 多線程
線程:程序 的同一條執行路徑
單線程:程序只有一條執行路徑
多線程:程序有多條執行路徑

多線程不能提高運行速度;當一個程序有多條執行路徑(多線程),那麼就有更多機搶佔cpu,提高了處理任務的效率。舉個不恰當的例子,小時候,我們被罰抄作業,一根筆寫太慢,我們就一次拿着三根筆寫,寫的速度沒變,但效率卻大大提高。

三 並行 併發 java 如何實現多線程

並行:程序在同一時間點上進行,
併發:程序在同時(這個同時指的是時間段而不是時間點)執行,java的程序就是併發,

四 線程中的基本方法
之前寫的代碼,都是單線程,main方法就是一個主線程。
例一

   class Thread1 extends Thread{
@Override
public void run(){
while(true){
System.out.println("烏龜領先中");

}
}
}

public class Thread2{
public static void main(String []args){
Thread1 thread1=new Thread1();
Thread2 thread2=new Thread2();
thread1.start();
thread2.start();
}
}
class Thread2 extends Thread{
@Override
public void run(){
while (true){

System.out.println("兔子領先中");


}

}
}
上面的例子創建兩個自定義線程  繼承了Thread類,重寫run方法,創建對象,調用線程的 start方法,start方法就會調用run方法執行線程。
從上面的例子的執行結果,我們不難看出,線程間的執行順序是不受我們控制的。
每個線程 都有一個默認的名稱。注意當前線程類的名稱不是線程名稱。
我們可以通過this.getName()獲得當前線程的名稱,通過Thread.currentThread()得到當前線程。通過對象.setName();修改當前線程名稱。
將我們希望線程執行的代碼放到run方法中,然後通過start方法來啓動線程,start方法首先爲線程的執行準備好系統資源,然後再去調用run方法。當某個類繼承了Thread類之後,該類就叫做一個線程類。 
線程的實現有兩種方式,第一種方式是繼承Thread類,然後重寫run方法;第二種是實現Runnable接口,然後實現其run方法。下面我們會舉一個第二種方式的例子。





例二

public class ThreadTest2
{
public static void main(String[] args)
{
// Thread t1 = new Thread(new Runnable()
// {
// @Override
// public void run()
// {
// for(int i = 0; i < 100; i++)
// {
// System.out.println(“hello :” + i);
// }
// }
// });
//
// t1.start();

/*  Runnable MyThread=new MyThread();
    Runnable MyThread2=new MyThread2();
    Thread  thread=new Thread(MyThread);
    thread.start();
    thread=new Thread(MyThread2);
    thread.start(); */

Thread thread=new Thread(new MyThread());
thread.start();
thread=new Therad(new MyThread2());
thread.tart();//這四句 相當於上面星花內六行。

}

}

class MyThread implements Runnable
{
@Override
public void run()
{
for(int i = 0; i < 100; i++)
{
System.out.println(“hello :” + i);
}
}
}

class MyThread2 implements Runnable
{
@Override
public void run()
{
for(int i = 0; i < 100; i++)
{
System.out.println(“welcome: ” + i);
}

}

}


例三  

public class TicketThread extends Thread{
private int ticNum=200;
@Override
public void run(){
while(ticNum>0){
System.out.println(Thread.currentThread.getName()+”窗口 賣出,”+ticNum+“張票”);
ticNum–;
}
}

}
class TestThread{
public static void main(String []args){

TicketThread th1=new TicketThread();
TicketThread th2=new TicketThread();
TicketThread th3=new TicketThread();
TicketThread th4=new TicketThread();
th1.start();
th2.start();
th3.start();
th4.start();

}

}


例四

class ThreadRunnable implements Runnable{
@Override
public void run(){
private int ticNum=200;

while(ticNum>0){
System.out.println(Thread.currentThread.getName()+”窗口 賣出,”+ticNum+“張票”);
ticNum–;
}

}

}
public class Test{
public static void main(String[]args){
TicketThread tic=new TicketThread();
Thread th1=new Thread(tic);
Thread th2 =new Thread(tic);
Thread th3=new Thread(tic);
Thread th4=new Thread(tic);
th1.start();
th2.start();
th3.start();
th4.start();

}
}

當幾個線程共享一份資源的時候,應該使用Runnable接口比較好


五  線程的生命週期
新生狀態
使用new關鍵字建立一個線程對象以後,就會被分配內存空間,調用start方法就會進入就緒狀態
就緒狀態
具備了運行條件,還沒有分配到cpu,處於線程 就緒隊列,等待系統爲其分配cpu
運行狀態
執行自己的run方法內的代碼,直到等待因爲某資源二阻塞或者完成任務而死亡,如果在給定時間內沒有執行結束,就會被系統換下來回到等待執行狀態。
阻塞狀態
處於運行狀態 下的線程在某些情況下,如執行了sleep方法,或者等待IO設備,將讓出cpu並暫時停止自己的運行,進入阻塞狀態。當阻塞原因解除後 ,該線程便會進入就緒狀態
死亡狀態
線程死亡的原因有三個:
1、run()或者call()方法執行完成,線程正常結束;

2、線程拋出一個未捕獲的Exception或Error;

3、直接調用該線程的stop()方法來結束該線程;

一個線程在未正常結束之前, 被強制終止是很危險的事情. 因爲它可能帶來完全預料不到的嚴重後果. 所以你看到Thread.suspend, Thread.stop等方法都被Deprecated了.
那麼不能直接把一個線程搞掛掉, 但有時候又有必要讓一個線程死掉, 或者讓它結束某種等待的狀態 該怎麼辦呢? 優雅的方法就是, 給那個線程一箇中斷信號, 讓它自己決定該怎麼辦. 比如說, 在某個子線程中爲了等待一些特定條件的到來, 你調用了Thread.sleep(10000), 預期線程睡10秒之後自己醒來, 但是如果這個特定條件提前到來的話, 你怎麼通知一個在睡覺的線程呢? 又比如說, 主線程通過調用子線程的join方法阻塞自己以等待子線程結束, 但是子線程運行過程中發現自己沒辦法在短時間內結束, 於是它需要想辦法告訴主線程別等我了. 這些情況下, 就需要中斷. 
中斷是通過調用Thread.interrupt()方法來做的. 這個方法通過修改了被調用線程的中斷狀態來告知那個線程, 說它被中斷了. 對於非阻塞中的線程, 只是改變了中斷狀態, 即Thread.isInterrupted()將返回true; 對於可取消的阻塞狀態中的線程, 比如等待在這些函數上的線程, Thread.sleep(), Object.wait(), Thread.join(), 這個線程收到中斷信號後, 會拋出InterruptedException, 同時會把中斷狀態置回爲false.





六   線程控制方法:

線程的優先級底層代碼定義好的有大中小對應1051三個等級,默認等級是5,也可以設置其他 數字。查找和修改線程優先級的方法在API裏面已經定義好,可以直接調用。算了我還是提一句吧,Thread.currentThread().getPriority();  對象.setPriority(數字);
優先級高低只是意味着獲取調度的概率的高低。當一個優先級低的線程長時間沒有被調度的時候,他的優先級會慢慢變高。

Join方法:阻塞指定線程等到另一個線程完成之後在繼續執行。

public class TestJoin extends Thread{
@Override
public void run(){
for(int i=0;i<10;i++){
System.out.println(i+”線程執行中”);

}

}

public static void main(String []args){

for(int i=0;i<10;i++){
System.out.println(“main”);
if(i==5){

TestJoin t=new TestJoin();
t.start();
t.join();//此處有異常

}}}}

sleep方法:使線程停止一段時間,處於阻塞狀態。當調用sleep方法時,,就算沒有其他等待執行的線程,當前線程也不會恢復執行。

public class TestSleep{

public static void main(String []args){
System.out.println(“預備”);

for(int i=3;i>0;i–){
System.out.println(i);
Thread.sleep(1000);//此處異常
System.out.println(“go”);

}

}

}

import java.util.Date;
public class TestSleep{
public static void main(String []args){

DateFormat s=new SimpleDateFormat(“hh:mm:ss”);
while(true){
Date now =new Date();
String strDate =s.format(now);
System.out.println(strDate);
Thread.sleep(1000);//異常加try catch

}
}

}

yield方法:讓當前正在執行線程暫停;不是阻塞線程,而是讓其進入到就緒狀態。如果調用了yield方法,卻沒有其他等待執行的線程,該線程就會馬上恢復執行。這是個很虛僞的方法,就是我先讓你一下,然後我們都在就緒狀態。結果第二次可能我又搶到了。手慢無。。。。。。。

public class TestYeild{
public static void main(String []args){

First f=new First();
Second s=new Second();
f.start();
s.start();

}
}

class First extend Thread{
public void run(){

for(int i=1;i<=10;i++){
System.out.println(“First”);
Thread.yield();

}

}

}

class Second extends Thread{
public void run (){
for(int i=1;i<=10;i++){
System.out.printl(“Second”);
Thread.yield();
}

}

}

後臺線程(守護線程):


     1.  後臺線程會隨着主程序的結束而結束,但是前臺進程則不會;或者說,只要有一個前臺線程未退出,進程就不會終止。(下面的例子會充分說明這一點);
     2.  默認情況下,程序員創建的線程是用戶線程;用setDaemon(true)可以設置線程爲後臺線程(必須是在它啓動前設置);而用isDaemon( )則可以判斷一個線程是前臺線程還是後臺線程;
     3. jvm的垃圾回收器其實就是一個後臺線程;
     4. setDaemon函數必須在start函數之前設定,否則會拋出IllegalThreadStateException異常;

public class RunnableTest implements Runnable {
private String name;
private long delay;
public static void main(String args[]) {
RunnableTest r = new RunnableTest(“thread1”,200);
Thread t = new Thread(r);
t.setDaemon(true); //後臺線程
t.start();
try {
System.in.read();
}
catch(Exception e) {

}
System.out.println(“end main”);
}
public RunnableTest(String name,long delay) {
this.name = name;
this.delay = delay;
}
public void run(){
try {
while(true) {
System.out.println(name);
Thread.sleep(delay);
}
}
catch(Exception e) {
System.out.println(“Exception”);
}
}
}
/*
測試結果:
case 1: t.setDaemon(truer); //後臺線程
thread1
thread1
thread1
……

thread1
end main

case 2: t.setDaemon(false); //前臺線程,相當於不調用setDaemon
thread1
thread1
thread1
……

thread1
end main
thread1
thread1
thread1
……
“`

這裏寫圖片描述

發佈了37 篇原創文章 · 獲贊 10 · 訪問量 1萬+
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章