day11線程

線程的概述

進程:正在運行的程序,負責了這個程序的內存空間分配,表明了內存中的執行區域。html

線程:就是在一個進程中負責一個執行路徑。java

多線程:就是在一個進程中多個執行路徑同時執行。安全

 

 

 

 

圖上的一鍵優化與垃圾清除同時在運行,在一個進程中同時在執行了多個任務。多線程

假象:ide

電腦上的程序同時在運行多任務操做系統能同時運行多個進程(程序)——但實際是因爲CPU分時機制的做用,使每一個進程都能循環得到本身的CPU時間片。但因爲輪換速度很是快,使得全部程序好象是在同時運行同樣。函數

 

 

多線程的好處:性能

  1. 解決了一個進程裏面能夠同時運行多個任務(執行路徑)。
  2. 提供資源的利用率,而不是提供效率。

多線程的弊端:測試

  1. 下降了一個進程裏面的線程的執行頻率。
  2. 對線程進行管理要求額外的 CPU開銷。線程的使用會給系統帶來上下文切換的額外負擔。
  3. 公有變量的同時讀或寫。當多個線程須要對公有變量進行寫操做時,後一個線程每每會修改掉前一個線程存放的數據,發生線程安全問題。
  4. 線程的死鎖。即較長時間的等待或資源競爭以及死鎖等多線程症狀。
  5. 繼承Thread

建立線程的方式

2.1 建立線程的方式一

 

 

getName()是獲取線程的名字。優化

執行後的效果:this

 

 

問題: 先按照順序運行完了張三,而後接着再按照順序運行完李四,咱們想要的效果是張三和李四作資源的爭奪戰,也就是先是張三而後李四,沒有順序的執行。這就證實多線程沒有起到效果。

  1. 須要複寫run方法,把要執行的任務放在run方法中。

 

 

運行效果:

 

 

問題: 先按照順序運行完了張三,而後接着再按照順序運行完李四,咱們想要的效果是張三和李四作資源的爭奪戰,也就是先是張三而後李四,沒有順序的執行。這就證實多線程沒有起到效果。

 

  1. 調用start()方法啓動線程

 

 

效果:

 

 

達到了咱們預期的效果。

線程的使用細節:

  1. 線程的啓動使用父類的start()方法
  2. 若是線程對象直接調用run(),那麼JVN不會看成線程來運行,會認爲是普通的方法調用。
  3. 線程的啓動只能由一次,不然拋出異常
  4. 能夠直接建立Thread類的對象並啓動該線程,可是若是沒有重寫run(),什麼也不執行。
  5. 匿名內部類的線程實現方式

2.2 線程的狀態

 

 

建立:新建立了一個線程對象

可運行:線程對象建立後,其餘線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取cpu執行權

運行:就緒狀態的線程獲取了CPU執行權,執行程序代碼。

阻臨時塞阻塞狀態是線程由於某種緣由放棄CPU使用權,暫時中止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態

死亡:線程執行完它的任務時。

2.3 常見線程的方法

Thread(String name)     初始化線程的名字

 getName()             返回線程的名字

 setName(String name)    設置線程對象名

 sleep()                 線程睡眠指定的毫秒數。

 getPriority()             返回當前線程對象的優先級   默認線程的優先級是5

 setPriority(int newPriority) 設置線程的優先級    雖然設置了線程的優先級,可是具體的實現取決於底層的操做系統的實現(最大的優先級是10 ,最小的1 , 默認是5)。

 currentThread()      返回CPU正在執行的線程的對象

class ThreadDemo1 extends Thread

{

public ThreadDemo1(){

  

}

public ThreadDemo1( String name ){

   super( name );

}

    

public void run(){

   int i = 0;

   while(i < 30){

  i++;

      System.out.println( this.getName() + " "+ " : i = " + i);

  System.out.println( Thread.currentThread().getName() + " "+ " : i = " + i);

  System.out.println( Thread.currentThread() == this );

  System.out.println( "getId()" + " "+ " : id = " + super.getId() );

  System.out.println( "getPriority()" + " "+ " : Priority = " + super.getPriority() );

   }

}

}

class Demo3 

{

public static void main(String[] args)

{

        ThreadDemo1 th1 = new ThreadDemo1("線程1");

ThreadDemo1 th2 = new ThreadDemo1("線程2");

        // 設置線程名

        th1.setName( "th1" );

th2.setName( "th2" );

        // 設置線程優先級  1 ~ 10

th1.setPriority( 10 );

th2.setPriority( 7 );

// 查看SUN定義的線程優先級範圍

System.out.println("max : " + Thread.MAX_PRIORITY );

System.out.println("min : " + Thread.MIN_PRIORITY );

        System.out.println("nor : " + Thread.NORM_PRIORITY );

th1.start();

th2.start();

System.out.println("Hello World!");

}

}

 

練習:模擬賣票

 

 

 

存在問題:這時候啓動了四個線程,那麼tickets是一個成員變量,也就是在一個線程對象中都維護了屬於本身的tickets屬性,那麼就總共存在了四份。

解決方案一:tickets使用staitc修飾,使每一個線程對象都是共享一份屬性。

 

 

解決方案2編寫一個類實現Runnable接口。

2.4 建立線程的方式二

建立線程的第二種方式.使用Runnable接口.

該類中的代碼就是對線程要執行的任務的定義.

1:定義了實現Runnable接口

2:重寫Runnable接口中的run方法,就是將線程運行的代碼放入在run方法中

3:經過Thread類創建線程對象

4:將Runnable接口的子類對象做爲實際參數,傳遞給Thread類構造方法

5:調用Thread類的start方法開啓線程,並調用Runable接口子類run方法

爲何要將Runnable接口的子類對象傳遞給Thread的構造函數,由於自定義的run方法所屬對象是Runnable接口的子類對象,因此要讓線程去執行指定對象的run方法

package cn.itcast.gz.runnable;

public class Demo1 {

public static void main(String[] args) {

MyRun my = new MyRun();

Thread t1 = new Thread(my);

t1.start();

for (int i = 0; i < 200; i++) {

System.out.println("main:" + i);

}

}

}

class MyRun implements Runnable {

public void run() {

for (int i = 0; i < 200; i++) {

System.err.println("MyRun:" + i);

}

}

}

 

 

理解Runnable:

Thread類能夠理解爲一個工人,Runnable的實現類的對象就是這個工人的工做(經過構造方法傳遞).Runnable接口中只有一個方法run方法,該方法中定義的事會被新線程執行的代碼.當咱們把Runnable的子類對象傳遞給Thread的構造時,實際上就是讓給Thread取得run方法,就是給了Thread一項任務.

 

 

買票例子使用Runnable接口實現

在上面的代碼中故意照成線程執行完後,執行Thread.sleep100),以讓cpu讓給別的線程,該方法會出現非運行時異常須要處理,這裏必須進行try{}catch(){},由於子類不能比父類拋出更多的異常,接口定義中沒有異常,實現類也不能拋出異常。

運行發現票號出現了負數,顯示了同一張票被賣了4次的狀況。

出現了一樣的問題。如何解決?

class MyTicket implements Runnable {

int tickets = 100;

public void run() {

while (true) {

if (tickets > 0) {

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + "窗口@銷售:"

+ tickets + "號票");

tickets--;

 

} else {

System.out.println("票已賣完。。。");

break;

}

}

}

}

public class Demo6 {

public static void main(String[] args) {

MyTicket mt = new MyTicket();

Thread t1 = new Thread(mt);

Thread t2 = new Thread(mt);

Thread t3 = new Thread(mt);

Thread t4 = new Thread(mt);

t1.start();

t2.start();

t3.start();

t4.start();

}

}

鎖對象

什麼是鎖對象?

每一個java對象都有一個鎖對象.並且只有一把鑰匙.

如何建立鎖對象:

    可使用this關鍵字做爲鎖對象,也可使用所在類的字節碼文件對應的Class對象做爲鎖對象

1. 類名.class       

2. 對象.getClass()   

Java中的每一個對象都有一個內置鎖,只有當對象具備同步方法代碼時,內置鎖纔會起做用,當進入一個同步的非靜態方法時,就會自動得到與類的當前實例(this)相關的鎖,該類的代碼就是正在執行的代碼。得到一個對象的鎖也成爲獲取鎖、鎖定對象也能夠稱之爲監視器來指咱們正在獲取的鎖對象。

由於一個對象只有一個鎖,全部若是一個線程得到了這個鎖,其餘線程就不能得到了,直到這個線程釋放(或者返回)鎖。也就是說在鎖釋放以前,任何其餘線程都不能進入同步代碼(不能夠進入該對象的任何同步方法)。釋放鎖指的是持有該鎖的線程退出同步方法,此時,其餘線程能夠進入該對象上的同步方法。

1:只能同步方法(代碼塊),不能同步變量或者類

2:每一個對象只有一個鎖

3:沒必要同步類中的全部方法,類能夠同時具備同步方法和非同步方法

4:若是兩個線程要執行一個類中的一個同步方法,而且他們使用的是了類的同一個實例(對象)來調用方法,那麼一次只有一個線程可以執行該方法,另外一個線程須要等待,直到第一個線程完成方法調用,總結就是:一個線程得到了對象的鎖,其餘線程不能夠進入該對象的同步方法。

5:若是類同時具備同步方法和非同步方法,那麼多個線程仍然能夠訪問該類的非同步方法。

同步會影響性能(甚至死鎖),優先考慮同步代碼塊。

6:若是線程進入sleep() 睡眠狀態,該線程會繼續持有鎖,不會釋放。

死鎖

 

經典的「哲學家就餐問題」,5個哲學家吃中餐,坐在圓卓子旁。每人有5根筷子(不是5雙),每兩我的中間放一根,哲學家時而思考,時而進餐。每一個人都須要一雙筷子才能吃到東西,吃完後將筷子放回原處繼續思考,若是每一個人都馬上抓住本身左邊的筷子,而後等待右邊的筷子空出來,同時又不放下已經拿到的筷子,這樣每一個人都沒法獲得1雙筷子,沒法吃飯都會餓死,這種狀況就會產生死鎖:每一個人都擁有其餘人須要的資源,同時又等待其餘人擁有的資源,而且每一個人在得到全部須要的資源以前都不會放棄已經擁有的資源。

當多個線程完成功能須要同時獲取多個共享資源的時候可能會致使死鎖。

1:兩個任務以相反的順序申請兩個鎖,死鎖就可能出現

2:線程T1得到鎖L1,線程T2得到鎖L2,而後T1申請得到鎖L2,同時T2申請得到鎖L1,此時兩個線程將要永久阻塞,死鎖出現

若是一個類可能發生死鎖,那麼並不意味着每次都會發生死鎖,只是表示有可能。要避免程序中出現死鎖。

例如,某個程序須要訪問兩個文件,當進程中的兩個線程分別各鎖住了一個文件,那它們都在等待對方解鎖另外一個文件,而這永遠不會發生。

3:要避免死鎖

public class DeadLock {

public static void main(String[] args) {

new Thread(new Runnable() { // 建立線程, 表明中國人

public void run() {

synchronized ("刀叉") { // 中國人拿到了刀叉

System.out.println(Thread.currentThread().getName()

+ ": 你不給我筷子, 我就不給你刀叉");

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized ("筷子") {

System.out.println(Thread.currentThread()

.getName() + ": 給你刀叉");

}

}

}

}, "中國人").start();

new Thread(new Runnable() { // 美國人

public void run() {

synchronized ("筷子") { // 美國人拿到了筷子

System.out.println(Thread.currentThread().getName()

+ ": 你先給我刀叉, 我再給你筷子");

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized ("刀叉") {

System.out.println(Thread.currentThread()

.getName() + ": 好吧, 把筷子給你.");

}

}

}

}, "美國人").start();

}

}

線程的通信

線程間通訊其實就是多個線程在操做同一個資源,但操做動做不一樣

生產者消費者

若是有多個生產者和消費者,必定要使用while循環判斷標記,而後在使用notifyAll喚醒,否者容易只用notify容易出現只喚醒本方線程狀況,致使程序中的全部線程都在等待。

例如:有一個數據存儲空間,劃分爲兩個部分,一部分存儲人的姓名,一部分存儲性別,咱們開啓一個線程,不停地想其中存儲姓名和性別(生產者),開啓另外一個線程從數據存儲空間中取出數據(消費者)。

因爲是多線程的,就須要考慮,假如生產者剛向數據存儲空間中添加了一我的名,尚未來得及添加性別,cpu就切換到了消費者的線程,消費者就會將這我的的姓名和上一我的的性別進行了輸出。

還有一種狀況是生產者生產了若干次數據,消費者纔開始取數據,或者消費者取出數據後,沒有等到消費者放入新的數據,消費者又重複的取出本身已經去過的數據。

public class Demo10 {

public static void main(String[] args) {

Person p = new Person();

Producer pro = new Producer(p);

Consumer con = new Consumer(p);

Thread t1 = new Thread(pro, "生產者");

Thread t2 = new Thread(con, "消費者");

t1.start();

t2.start();

}

}

 

// 使用Person做爲數據存儲空間

class Person {

String name;

String gender;

}

 

// 生產者

class Producer implements Runnable {

Person p;

 

public Producer() {

 

}

 

public Producer(Person p) {

this.p = p;

}

 

@Override

public void run() {

int i = 0;

while (true) {

if (i % 2 == 0) {

p.name = "jack";

p.gender = "man";

} else {

p.name = "小麗";

p.gender = "";

}

i++;

}

 

}

 

}

 

// 消費者

class Consumer implements Runnable {

Person p;

 

public Consumer() {

 

}

 

public Consumer(Person p) {

this.p = p;

}

 

@Override

public void run() {

 

while (true) {

System.out.println("name:" + p.name + "---gnder:" + p.gender);

}

}

 

}

 

在上述代碼中,ProducerConsumer 類的內部都維護了一個Person類型的p成員變量,經過構造函數進行賦值,在man方法中建立了一個Person對象,將其同時傳遞給ProducerConsumer對象,因此ProducerConsumer訪問的是同一個Person對象。並啓動了兩個線程。

 

輸出:

 

顯然屏幕輸出了小麗 man 這樣的結果是出現了線程安全問題。因此須要使用synchronized來解決該問題。

 

package cn.itcast.gz.runnable;

 

public class Demo10 {

public static void main(String[] args) {

Person p = new Person();

Producer pro = new Producer(p);

Consumer con = new Consumer(p);

Thread t1 = new Thread(pro, "生產者");

Thread t2 = new Thread(con, "消費者");

t1.start();

t2.start();

}

}

 

// 使用Person做爲數據存儲空間

class Person {

String name;

String gender;

}

 

// 生產者

class Producer implements Runnable {

Person p;

 

public Producer() {

 

}

 

public Producer(Person p) {

this.p = p;

}

 

@Override

public void run() {

int i = 0;

while (true) {

synchronized (p) {

if (i % 2 == 0) {

p.name = "jack";

p.gender = "man";

} else {

p.name = "小麗";

p.gender = "";

}

i++;

}

 

}

 

}

 

}

 

// 消費者

class Consumer implements Runnable {

Person p;

 

public Consumer() {

 

}

 

public Consumer(Person p) {

this.p = p;

}

 

@Override

public void run() {

 

while (true) {

synchronized (p) {

System.out.println("name:" + p.name + "---gnder:" + p.gender);

}

 

}

}

 

}

 

編譯運行:屏幕沒有再輸出jack –  或者小麗- man 這種狀況了。說明咱們解決了線程同步問題,可是仔細觀察,生產者生產了若干次數據,消費者纔開始取數據,或者消費者取出數據後,沒有等到消費者放入新的數據,消費者又重複的取出本身已經去過的數據。這個問題依然存在。

升級:在Person類中添加兩個方法,setread方法並設置爲synchronized讓生產者和消費者調用這兩個方法。

public class Demo10 {

public static void main(String[] args) {

Person p = new Person();

Producer pro = new Producer(p);

Consumer con = new Consumer(p);

Thread t1 = new Thread(pro, "生產者");

Thread t2 = new Thread(con, "消費者");

t1.start();

t2.start();

}

}

 

// 使用Person做爲數據存儲空間

class Person {

String name;

String gender;

 

 

public synchronized void set(String name, String gender) {

this.name = name;

this.gender = gender;

}

 

public synchronized void read() {

System.out.println("name:" + this.name + "----gender:" + this.gender);

}

 

}

 

// 生產者

class Producer implements Runnable {

Person p;

 

public Producer() {

 

}

 

public Producer(Person p) {

this.p = p;

}

 

@Override

public void run() {

int i = 0;

while (true) {

 

if (i % 2 == 0) {

p.set("jack", "man");

} else {

p.set("小麗", "");

}

i++;

 

}

 

}

 

}

 

// 消費者

class Consumer implements Runnable {

Person p;

 

public Consumer() {

 

}

 

public Consumer(Person p) {

this.p = p;

}

 

@Override

public void run() {

 

while (true) {

p.read();

 

}

}

 

}

 

 

需求:咱們須要生產者生產一次,消費者就消費一次。而後這樣有序的循環。

這就須要使用線程間的通訊了。Java經過Object類的waitnotifynotifyAll這幾個方法實現線程間的通訊。

1.1.1. 等待喚醒機制

wait:告訴當前線程放棄執行權,並放棄監視器(鎖)並進入阻塞狀態,直到其餘線程持有得到執行權,並持有了相同的監視器(鎖)並調用notify爲止。

notify:喚醒持有同一個監視器(鎖)中調用wait的第一個線程,例如,餐館有空位置後,等候就餐最久的顧客最早入座。注意:被喚醒的線程是進入了可運行狀態。等待cpu執行權。

notifyAll:喚醒持有同一監視器中調用wait的全部的線程。

 

如何解決生產者和消費者的問題?

能夠經過設置一個標記,表示數據的(存儲空間的狀態)例如,當消費者讀取了(消費了一次)一次數據以後能夠將標記改成false,當生產者生產了一個數據,將標記改成true

,也就是隻有標記爲true的時候,消費者才能取走數據,標記爲false時候生產者才生產數據。

代碼實現:

package cn.itcast.gz.runnable;

 

public class Demo10 {

public static void main(String[] args) {

Person p = new Person();

Producer pro = new Producer(p);

Consumer con = new Consumer(p);

Thread t1 = new Thread(pro, "生產者");

Thread t2 = new Thread(con, "消費者");

t1.start();

t2.start();

}

}

 

// 使用Person做爲數據存儲空間

class Person {

String name;

String gender;

boolean flag = false;

 

public synchronized void set(String name, String gender) {

if (flag) {

try {

wait();

} catch (InterruptedException e) {

 

e.printStackTrace();

}

}

this.name = name;

this.gender = gender;

flag = true;

notify();

}

 

public synchronized void read() {

if (!flag) {

try {

wait();

} catch (InterruptedException e) {

 

e.printStackTrace();

}

}

System.out.println("name:" + this.name + "----gender:" + this.gender);

flag = false;

notify();

}

 

}

 

// 生產者

class Producer implements Runnable {

Person p;

 

public Producer() {

 

}

 

public Producer(Person p) {

this.p = p;

}

 

@Override

public void run() {

int i = 0;

while (true) {

 

if (i % 2 == 0) {

p.set("jack", "man");

} else {

p.set("小麗", "");

}

i++;

 

}

 

}

 

}

 

// 消費者

class Consumer implements Runnable {

Person p;

 

public Consumer() {

 

}

 

public Consumer(Person p) {

this.p = p;

}

 

@Override

public void run() {

 

while (true) {

p.read();

 

}

}

 

}

 

 

線程間通訊其實就是多個線程在操做同一個資源,但操做動做不一樣,waitnotify(),notifyAll()都使用在同步中,由於要對持有監視器(鎖)的線程操做,因此要使用在同步中,由於只有同步才具備鎖。

爲何這些方法定義在Object類中

由於這些方法在操做線程時,都必需要標識他們所操做線程持有的鎖,只有同一個鎖上的被等待線程,能夠被統一鎖上notify喚醒,不能夠對不一樣鎖中的線程進行喚醒,就是等待和喚醒必須是同一個鎖。而鎖因爲可使任意對象,因此能夠被任意對象調用的方法定義在Object類中

wait() sleep()有什麼區別?

wait():釋放資源,釋放鎖。是Object的方法

sleep():釋放資源,不釋放鎖。是Thread的方法

定義了notify爲何還要定義notifyAll,由於只用notify容易出現只喚醒本方線程狀況,致使程序中的全部線程都在等待。

 

2. 線程生命週期

任何事物都是生命週期,線程也是,

1. 正常終止  當線程的run()執行完畢,線程死亡。

2. 使用標記中止線程

注意:Stop方法已過期,就不能再使用這個方法。

如何使用標記中止線程中止線程。

開啓多線程運行,運行代碼一般是循環結構,只要控制住循環,就可讓run方法結束,線程就結束。

 

class StopThread implements Runnable {

public boolean tag = true;

@Override

public void run() {

int i = 0;

 

while (tag) {

i++;

System.out.println(Thread.currentThread().getName() + "i:" + i);

}

}

}

public class Demo8 {

public static void main(String[] args) {

StopThread st = new StopThread();

Thread th = new Thread(st, "線程1");

th.start();

for (int i = 0; i < 100; i++) {

if (i == 50) {

System.out.println("main i:" + i);

st.tag = false;

}

}

}

}

上述案例中定義了一個計數器i,用來控制main方法(主線程)的循環打印次數,在i50這段時間內,兩個線程交替執行,當計數器變爲50,程序將標記改成false,也就是終止了線程1while循環,run方法結束,線程1也隨之結束。注意:當計數器i變爲50的,將標記改成false的時候,cpu不必定立刻回到線程1,因此線程1並不會立刻終止。

3. 後臺線程

後臺線程:就是隱藏起來一直在默默運行的線程,直到進程結束。

 實現:

      setDaemon(boolean on)

 特色:

當全部的非後臺線程結束時,程序也就終止了同時還會殺死進程中的全部後臺線程,也就是說,只要有非後臺線程還在運行,程序就不會終止,執行main方法的主線程就是一個非後臺線程。

必須在啓動線程以前(調用start方法以前)調用setDaemontrue)方法,才能夠把該線程設置爲後臺線程。

一旦main()執行完畢,那麼程序就會終止,JVM也就退出了。

可使用isDaemon() 測試該線程是否爲後臺線程(守護線程)。

該案例:開啓了一個qq檢測升級的後臺線程,經過while真循環進行不停檢測,當計數器變爲100的時候,表示檢測完畢,提示是否更新,線程同時結束。

爲了驗證,當非後臺線程結束時,後臺線程是否終止,故意讓該後臺線程睡眠一會。發現只要main線程執行完畢,後臺線程也就隨之消亡了。

class QQUpdate implements Runnable {

int i = 0;

 

@Override

public void run() {

while (true) {

 

System.out.println(Thread.currentThread().getName() + " 檢測是否有可用更新");

i++;

try {

Thread.sleep(10);

} catch (InterruptedException e) {

 

e.printStackTrace();

}

if (i == 100) {

System.out.println("有可用更新,是否升級?");

break;

}

}

}

}

public class Demo9 {

public static void main(String[] args) {

QQUpdate qq = new QQUpdate();

Thread th = new Thread(qq, "qqupdate");

th.setDaemon(true);

th.start();

System.out.println(th.isDaemon());

System.out.println("hello world");

}

}

     

Threadjoin方法

A線程執行到了B線程Join方法時A就會等待,等B線程都執行完A纔會執行,Join能夠用來臨時加入線程執行

本案例,啓動了一個JoinThread線程,main(主線程)進行for循環,當計數器爲50時,讓JoinThread,經過join方法,加入到主線程中,發現只有JoinThread線程執行完,主線程纔會執行完畢.

能夠刻意讓JoinThread線程sleep,若是JoinThread沒有調用join方法,那麼確定是主線程執行完畢,可是因爲JoinThread線程加入到了main線程,必須等JoinThread執行完畢主線程才能繼續執行。

class JoinThread implements Runnable {

 

@Override

public void run() {

int i = 0;

while (i < 300) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + " i:" + i);

i++;

}

}

}

 

public class Demo10 {

public static void main(String[] args) throws InterruptedException {

JoinThread jt = new JoinThread();

Thread th = new Thread(jt, "one");

th.start();

int i = 0;

while (i < 200) {

if (i == 100) {

th.join();

}

System.err.println(Thread.currentThread().getName() + " i:" + i);

i++;

 

}

}

}

上述程序用到了Thread類中的join方法,即th.join語句,做用是將th對應的線程合併到嗲用th.join語句的線程中,main方法的線程中計數器到達100以前,main線程和one線程是交替執行的。在main線程中的計數器到達100後,只有one線程執行,也就是one線程此時被加進了mian線程中,one線程不執行完,main線程會一直等待

帶參數的join方法是指定合併時間,有納秒和毫秒級別。

 

 

線程的概述

進程:正在運行的程序,負責了這個程序的內存空間分配,表明了內存中的執行區域。

線程:就是在一個進程中負責一個執行路徑。

多線程:就是在一個進程中多個執行路徑同時執行。

 

 

 

圖上的一鍵優化與垃圾清除同時在運行,在一個進程中同時在執行了多個任務。

假象:

電腦上的程序同時在運行多任務操做系統能同時運行多個進程(程序)——但實際是因爲CPU分時機制的做用,使每一個進程都能循環得到本身的CPU時間片。但因爲輪換速度很是快,使得全部程序好象是在同時運行同樣。

 

 

多線程的好處:

  1. 解決了一個進程裏面能夠同時運行多個任務(執行路徑)。
  2. 提供資源的利用率,而不是提供效率。

多線程的弊端:

  1. 下降了一個進程裏面的線程的執行頻率。
  2. 對線程進行管理要求額外的 CPU開銷。線程的使用會給系統帶來上下文切換的額外負擔。
  3. 公有變量的同時讀或寫。當多個線程須要對公有變量進行寫操做時,後一個線程每每會修改掉前一個線程存放的數據,發生線程安全問題。
  4. 線程的死鎖。即較長時間的等待或資源競爭以及死鎖等多線程症狀。
  5. 繼承Thread

建立線程的方式

2.1 建立線程的方式一

 

getName()是獲取線程的名字。

執行後的效果:

 

問題: 先按照順序運行完了張三,而後接着再按照順序運行完李四,咱們想要的效果是張三和李四作資源的爭奪戰,也就是先是張三而後李四,沒有順序的執行。這就證實多線程沒有起到效果。

  1. 須要複寫run方法,把要執行的任務放在run方法中。

 

運行效果:

 

問題: 先按照順序運行完了張三,而後接着再按照順序運行完李四,咱們想要的效果是張三和李四作資源的爭奪戰,也就是先是張三而後李四,沒有順序的執行。這就證實多線程沒有起到效果。

 

  1. 調用start()方法啓動線程

 

效果:

 

達到了咱們預期的效果。

線程的使用細節:

  1. 線程的啓動使用父類的start()方法
  2. 若是線程對象直接調用run(),那麼JVN不會看成線程來運行,會認爲是普通的方法調用。
  3. 線程的啓動只能由一次,不然拋出異常
  4. 能夠直接建立Thread類的對象並啓動該線程,可是若是沒有重寫run(),什麼也不執行。
  5. 匿名內部類的線程實現方式

2.2 線程的狀態

 

建立:新建立了一個線程對象

可運行:線程對象建立後,其餘線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取cpu執行權

運行:就緒狀態的線程獲取了CPU執行權,執行程序代碼。

阻臨時塞阻塞狀態是線程由於某種緣由放棄CPU使用權,暫時中止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態

死亡:線程執行完它的任務時。

2.3 常見線程的方法

Thread(String name)     初始化線程的名字

 getName()             返回線程的名字

 setName(String name)    設置線程對象名

 sleep()                 線程睡眠指定的毫秒數。

 getPriority()             返回當前線程對象的優先級   默認線程的優先級是5

 setPriority(int newPriority) 設置線程的優先級    雖然設置了線程的優先級,可是具體的實現取決於底層的操做系統的實現(最大的優先級是10 ,最小的1 , 默認是5)。

 currentThread()      返回CPU正在執行的線程的對象

class ThreadDemo1 extends Thread

{

public ThreadDemo1(){

  

}

public ThreadDemo1( String name ){

   super( name );

}

    

public void run(){

   int i = 0;

   while(i < 30){

  i++;

      System.out.println( this.getName() + " "+ " : i = " + i);

  System.out.println( Thread.currentThread().getName() + " "+ " : i = " + i);

  System.out.println( Thread.currentThread() == this );

  System.out.println( "getId()" + " "+ " : id = " + super.getId() );

  System.out.println( "getPriority()" + " "+ " : Priority = " + super.getPriority() );

   }

}

}

class Demo3 

{

public static void main(String[] args)

{

        ThreadDemo1 th1 = new ThreadDemo1("線程1");

ThreadDemo1 th2 = new ThreadDemo1("線程2");

        // 設置線程名

        th1.setName( "th1" );

th2.setName( "th2" );

        // 設置線程優先級  1 ~ 10

th1.setPriority( 10 );

th2.setPriority( 7 );

// 查看SUN定義的線程優先級範圍

System.out.println("max : " + Thread.MAX_PRIORITY );

System.out.println("min : " + Thread.MIN_PRIORITY );

        System.out.println("nor : " + Thread.NORM_PRIORITY );

th1.start();

th2.start();

System.out.println("Hello World!");

}

}

 

練習:模擬賣票

 

 

存在問題:這時候啓動了四個線程,那麼tickets是一個成員變量,也就是在一個線程對象中都維護了屬於本身的tickets屬性,那麼就總共存在了四份。

解決方案一:tickets使用staitc修飾,使每一個線程對象都是共享一份屬性。

 

解決方案2編寫一個類實現Runnable接口。

2.4 建立線程的方式二

建立線程的第二種方式.使用Runnable接口.

該類中的代碼就是對線程要執行的任務的定義.

1:定義了實現Runnable接口

2:重寫Runnable接口中的run方法,就是將線程運行的代碼放入在run方法中

3:經過Thread類創建線程對象

4:將Runnable接口的子類對象做爲實際參數,傳遞給Thread類構造方法

5:調用Thread類的start方法開啓線程,並調用Runable接口子類run方法

爲何要將Runnable接口的子類對象傳遞給Thread的構造函數,由於自定義的run方法所屬對象是Runnable接口的子類對象,因此要讓線程去執行指定對象的run方法

package cn.itcast.gz.runnable;

public class Demo1 {

public static void main(String[] args) {

MyRun my = new MyRun();

Thread t1 = new Thread(my);

t1.start();

for (int i = 0; i < 200; i++) {

System.out.println("main:" + i);

}

}

}

class MyRun implements Runnable {

public void run() {

for (int i = 0; i < 200; i++) {

System.err.println("MyRun:" + i);

}

}

}

 

 

理解Runnable:

Thread類能夠理解爲一個工人,Runnable的實現類的對象就是這個工人的工做(經過構造方法傳遞).Runnable接口中只有一個方法run方法,該方法中定義的事會被新線程執行的代碼.當咱們把Runnable的子類對象傳遞給Thread的構造時,實際上就是讓給Thread取得run方法,就是給了Thread一項任務.

 

 

買票例子使用Runnable接口實現

在上面的代碼中故意照成線程執行完後,執行Thread.sleep100),以讓cpu讓給別的線程,該方法會出現非運行時異常須要處理,這裏必須進行try{}catch(){},由於子類不能比父類拋出更多的異常,接口定義中沒有異常,實現類也不能拋出異常。

運行發現票號出現了負數,顯示了同一張票被賣了4次的狀況。

出現了一樣的問題。如何解決?

class MyTicket implements Runnable {

int tickets = 100;

public void run() {

while (true) {

if (tickets > 0) {

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + "窗口@銷售:"

+ tickets + "號票");

tickets--;

 

} else {

System.out.println("票已賣完。。。");

break;

}

}

}

}

public class Demo6 {

public static void main(String[] args) {

MyTicket mt = new MyTicket();

Thread t1 = new Thread(mt);

Thread t2 = new Thread(mt);

Thread t3 = new Thread(mt);

Thread t4 = new Thread(mt);

t1.start();

t2.start();

t3.start();

t4.start();

}

}

鎖對象

什麼是鎖對象?

每一個java對象都有一個鎖對象.並且只有一把鑰匙.

如何建立鎖對象:

    可使用this關鍵字做爲鎖對象,也可使用所在類的字節碼文件對應的Class對象做爲鎖對象

1. 類名.class       

2. 對象.getClass()   

Java中的每一個對象都有一個內置鎖,只有當對象具備同步方法代碼時,內置鎖纔會起做用,當進入一個同步的非靜態方法時,就會自動得到與類的當前實例(this)相關的鎖,該類的代碼就是正在執行的代碼。得到一個對象的鎖也成爲獲取鎖、鎖定對象也能夠稱之爲監視器來指咱們正在獲取的鎖對象。

由於一個對象只有一個鎖,全部若是一個線程得到了這個鎖,其餘線程就不能得到了,直到這個線程釋放(或者返回)鎖。也就是說在鎖釋放以前,任何其餘線程都不能進入同步代碼(不能夠進入該對象的任何同步方法)。釋放鎖指的是持有該鎖的線程退出同步方法,此時,其餘線程能夠進入該對象上的同步方法。

1:只能同步方法(代碼塊),不能同步變量或者類

2:每一個對象只有一個鎖

3:沒必要同步類中的全部方法,類能夠同時具備同步方法和非同步方法

4:若是兩個線程要執行一個類中的一個同步方法,而且他們使用的是了類的同一個實例(對象)來調用方法,那麼一次只有一個線程可以執行該方法,另外一個線程須要等待,直到第一個線程完成方法調用,總結就是:一個線程得到了對象的鎖,其餘線程不能夠進入該對象的同步方法。

5:若是類同時具備同步方法和非同步方法,那麼多個線程仍然能夠訪問該類的非同步方法。

同步會影響性能(甚至死鎖),優先考慮同步代碼塊。

6:若是線程進入sleep() 睡眠狀態,該線程會繼續持有鎖,不會釋放。

死鎖

 

經典的「哲學家就餐問題」,5個哲學家吃中餐,坐在圓卓子旁。每人有5根筷子(不是5雙),每兩我的中間放一根,哲學家時而思考,時而進餐。每一個人都須要一雙筷子才能吃到東西,吃完後將筷子放回原處繼續思考,若是每一個人都馬上抓住本身左邊的筷子,而後等待右邊的筷子空出來,同時又不放下已經拿到的筷子,這樣每一個人都沒法獲得1雙筷子,沒法吃飯都會餓死,這種狀況就會產生死鎖:每一個人都擁有其餘人須要的資源,同時又等待其餘人擁有的資源,而且每一個人在得到全部須要的資源以前都不會放棄已經擁有的資源。

當多個線程完成功能須要同時獲取多個共享資源的時候可能會致使死鎖。

1:兩個任務以相反的順序申請兩個鎖,死鎖就可能出現

2:線程T1得到鎖L1,線程T2得到鎖L2,而後T1申請得到鎖L2,同時T2申請得到鎖L1,此時兩個線程將要永久阻塞,死鎖出現

若是一個類可能發生死鎖,那麼並不意味着每次都會發生死鎖,只是表示有可能。要避免程序中出現死鎖。

例如,某個程序須要訪問兩個文件,當進程中的兩個線程分別各鎖住了一個文件,那它們都在等待對方解鎖另外一個文件,而這永遠不會發生。

3:要避免死鎖

public class DeadLock {

public static void main(String[] args) {

new Thread(new Runnable() { // 建立線程, 表明中國人

public void run() {

synchronized ("刀叉") { // 中國人拿到了刀叉

System.out.println(Thread.currentThread().getName()

+ ": 你不給我筷子, 我就不給你刀叉");

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized ("筷子") {

System.out.println(Thread.currentThread()

.getName() + ": 給你刀叉");

}

}

}

}, "中國人").start();

new Thread(new Runnable() { // 美國人

public void run() {

synchronized ("筷子") { // 美國人拿到了筷子

System.out.println(Thread.currentThread().getName()

+ ": 你先給我刀叉, 我再給你筷子");

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized ("刀叉") {

System.out.println(Thread.currentThread()

.getName() + ": 好吧, 把筷子給你.");

}

}

}

}, "美國人").start();

}

}

線程的通信

線程間通訊其實就是多個線程在操做同一個資源,但操做動做不一樣

生產者消費者

若是有多個生產者和消費者,必定要使用while循環判斷標記,而後在使用notifyAll喚醒,否者容易只用notify容易出現只喚醒本方線程狀況,致使程序中的全部線程都在等待。

例如:有一個數據存儲空間,劃分爲兩個部分,一部分存儲人的姓名,一部分存儲性別,咱們開啓一個線程,不停地想其中存儲姓名和性別(生產者),開啓另外一個線程從數據存儲空間中取出數據(消費者)。

因爲是多線程的,就須要考慮,假如生產者剛向數據存儲空間中添加了一我的名,尚未來得及添加性別,cpu就切換到了消費者的線程,消費者就會將這我的的姓名和上一我的的性別進行了輸出。

還有一種狀況是生產者生產了若干次數據,消費者纔開始取數據,或者消費者取出數據後,沒有等到消費者放入新的數據,消費者又重複的取出本身已經去過的數據。

public class Demo10 {

public static void main(String[] args) {

Person p = new Person();

Producer pro = new Producer(p);

Consumer con = new Consumer(p);

Thread t1 = new Thread(pro, "生產者");

Thread t2 = new Thread(con, "消費者");

t1.start();

t2.start();

}

}

 

// 使用Person做爲數據存儲空間

class Person {

String name;

String gender;

}

 

// 生產者

class Producer implements Runnable {

Person p;

 

public Producer() {

 

}

 

public Producer(Person p) {

this.p = p;

}

 

@Override

public void run() {

int i = 0;

while (true) {

if (i % 2 == 0) {

p.name = "jack";

p.gender = "man";

} else {

p.name = "小麗";

p.gender = "";

}

i++;

}

 

}

 

}

 

// 消費者

class Consumer implements Runnable {

Person p;

 

public Consumer() {

 

}

 

public Consumer(Person p) {

this.p = p;

}

 

@Override

public void run() {

 

while (true) {

System.out.println("name:" + p.name + "---gnder:" + p.gender);

}

}

 

}

 

在上述代碼中,ProducerConsumer 類的內部都維護了一個Person類型的p成員變量,經過構造函數進行賦值,在man方法中建立了一個Person對象,將其同時傳遞給ProducerConsumer對象,因此ProducerConsumer訪問的是同一個Person對象。並啓動了兩個線程。

 

輸出:

 

顯然屏幕輸出了小麗 man 這樣的結果是出現了線程安全問題。因此須要使用synchronized來解決該問題。

 

package cn.itcast.gz.runnable;

 

public class Demo10 {

public static void main(String[] args) {

Person p = new Person();

Producer pro = new Producer(p);

Consumer con = new Consumer(p);

Thread t1 = new Thread(pro, "生產者");

Thread t2 = new Thread(con, "消費者");

t1.start();

t2.start();

}

}

 

// 使用Person做爲數據存儲空間

class Person {

String name;

String gender;

}

 

// 生產者

class Producer implements Runnable {

Person p;

 

public Producer() {

 

}

 

public Producer(Person p) {

this.p = p;

}

 

@Override

public void run() {

int i = 0;

while (true) {

synchronized (p) {

if (i % 2 == 0) {

p.name = "jack";

p.gender = "man";

} else {

p.name = "小麗";

p.gender = "";

}

i++;

}

 

}

 

}

 

}

 

// 消費者

class Consumer implements Runnable {

Person p;

 

public Consumer() {

 

}

 

public Consumer(Person p) {

this.p = p;

}

 

@Override

public void run() {

 

while (true) {

synchronized (p) {

System.out.println("name:" + p.name + "---gnder:" + p.gender);

}

 

}

}

 

}

 

編譯運行:屏幕沒有再輸出jack –  或者小麗- man 這種狀況了。說明咱們解決了線程同步問題,可是仔細觀察,生產者生產了若干次數據,消費者纔開始取數據,或者消費者取出數據後,沒有等到消費者放入新的數據,消費者又重複的取出本身已經去過的數據。這個問題依然存在。

升級:在Person類中添加兩個方法,setread方法並設置爲synchronized讓生產者和消費者調用這兩個方法。

public class Demo10 {

public static void main(String[] args) {

Person p = new Person();

Producer pro = new Producer(p);

Consumer con = new Consumer(p);

Thread t1 = new Thread(pro, "生產者");

Thread t2 = new Thread(con, "消費者");

t1.start();

t2.start();

}

}

 

// 使用Person做爲數據存儲空間

class Person {

String name;

String gender;

 

 

public synchronized void set(String name, String gender) {

this.name = name;

this.gender = gender;

}

 

public synchronized void read() {

System.out.println("name:" + this.name + "----gender:" + this.gender);

}

 

}

 

// 生產者

class Producer implements Runnable {

Person p;

 

public Producer() {

 

}

 

public Producer(Person p) {

this.p = p;

}

 

@Override

public void run() {

int i = 0;

while (true) {

 

if (i % 2 == 0) {

p.set("jack", "man");

} else {

p.set("小麗", "");

}

i++;

 

}

 

}

 

}

 

// 消費者

class Consumer implements Runnable {

Person p;

 

public Consumer() {

 

}

 

public Consumer(Person p) {

this.p = p;

}

 

@Override

public void run() {

 

while (true) {

p.read();

 

}

}

 

}

 

 

需求:咱們須要生產者生產一次,消費者就消費一次。而後這樣有序的循環。

這就須要使用線程間的通訊了。Java經過Object類的waitnotifynotifyAll這幾個方法實現線程間的通訊。

1.1.1. 等待喚醒機制

wait:告訴當前線程放棄執行權,並放棄監視器(鎖)並進入阻塞狀態,直到其餘線程持有得到執行權,並持有了相同的監視器(鎖)並調用notify爲止。

notify:喚醒持有同一個監視器(鎖)中調用wait的第一個線程,例如,餐館有空位置後,等候就餐最久的顧客最早入座。注意:被喚醒的線程是進入了可運行狀態。等待cpu執行權。

notifyAll:喚醒持有同一監視器中調用wait的全部的線程。

 

如何解決生產者和消費者的問題?

能夠經過設置一個標記,表示數據的(存儲空間的狀態)例如,當消費者讀取了(消費了一次)一次數據以後能夠將標記改成false,當生產者生產了一個數據,將標記改成true

,也就是隻有標記爲true的時候,消費者才能取走數據,標記爲false時候生產者才生產數據。

代碼實現:

package cn.itcast.gz.runnable;

 

public class Demo10 {

public static void main(String[] args) {

Person p = new Person();

Producer pro = new Producer(p);

Consumer con = new Consumer(p);

Thread t1 = new Thread(pro, "生產者");

Thread t2 = new Thread(con, "消費者");

t1.start();

t2.start();

}

}

 

// 使用Person做爲數據存儲空間

class Person {

String name;

String gender;

boolean flag = false;

 

public synchronized void set(String name, String gender) {

if (flag) {

try {

wait();

} catch (InterruptedException e) {

 

e.printStackTrace();

}

}

this.name = name;

this.gender = gender;

flag = true;

notify();

}

 

public synchronized void read() {

if (!flag) {

try {

wait();

} catch (InterruptedException e) {

 

e.printStackTrace();

}

}

System.out.println("name:" + this.name + "----gender:" + this.gender);

flag = false;

notify();

}

 

}

 

// 生產者

class Producer implements Runnable {

Person p;

 

public Producer() {

 

}

 

public Producer(Person p) {

this.p = p;

}

 

@Override

public void run() {

int i = 0;

while (true) {

 

if (i % 2 == 0) {

p.set("jack", "man");

} else {

p.set("小麗", "");

}

i++;

 

}

 

}

 

}

 

// 消費者

class Consumer implements Runnable {

Person p;

 

public Consumer() {

 

}

 

public Consumer(Person p) {

this.p = p;

}

 

@Override

public void run() {

 

while (true) {

p.read();

 

}

}

 

}

 

 

線程間通訊其實就是多個線程在操做同一個資源,但操做動做不一樣,waitnotify(),notifyAll()都使用在同步中,由於要對持有監視器(鎖)的線程操做,因此要使用在同步中,由於只有同步才具備鎖。

爲何這些方法定義在Object類中

由於這些方法在操做線程時,都必需要標識他們所操做線程持有的鎖,只有同一個鎖上的被等待線程,能夠被統一鎖上notify喚醒,不能夠對不一樣鎖中的線程進行喚醒,就是等待和喚醒必須是同一個鎖。而鎖因爲可使任意對象,因此能夠被任意對象調用的方法定義在Object類中

wait() sleep()有什麼區別?

wait():釋放資源,釋放鎖。是Object的方法

sleep():釋放資源,不釋放鎖。是Thread的方法

定義了notify爲何還要定義notifyAll,由於只用notify容易出現只喚醒本方線程狀況,致使程序中的全部線程都在等待。

 

2. 線程生命週期

任何事物都是生命週期,線程也是,

1. 正常終止  當線程的run()執行完畢,線程死亡。

2. 使用標記中止線程

注意:Stop方法已過期,就不能再使用這個方法。

如何使用標記中止線程中止線程。

開啓多線程運行,運行代碼一般是循環結構,只要控制住循環,就可讓run方法結束,線程就結束。

 

class StopThread implements Runnable {

public boolean tag = true;

@Override

public void run() {

int i = 0;

 

while (tag) {

i++;

System.out.println(Thread.currentThread().getName() + "i:" + i);

}

}

}

public class Demo8 {

public static void main(String[] args) {

StopThread st = new StopThread();

Thread th = new Thread(st, "線程1");

th.start();

for (int i = 0; i < 100; i++) {

if (i == 50) {

System.out.println("main i:" + i);

st.tag = false;

}

}

}

}

上述案例中定義了一個計數器i,用來控制main方法(主線程)的循環打印次數,在i50這段時間內,兩個線程交替執行,當計數器變爲50,程序將標記改成false,也就是終止了線程1while循環,run方法結束,線程1也隨之結束。注意:當計數器i變爲50的,將標記改成false的時候,cpu不必定立刻回到線程1,因此線程1並不會立刻終止。

3. 後臺線程

後臺線程:就是隱藏起來一直在默默運行的線程,直到進程結束。

 實現:

      setDaemon(boolean on)

 特色:

當全部的非後臺線程結束時,程序也就終止了同時還會殺死進程中的全部後臺線程,也就是說,只要有非後臺線程還在運行,程序就不會終止,執行main方法的主線程就是一個非後臺線程。

必須在啓動線程以前(調用start方法以前)調用setDaemontrue)方法,才能夠把該線程設置爲後臺線程。

一旦main()執行完畢,那麼程序就會終止,JVM也就退出了。

可使用isDaemon() 測試該線程是否爲後臺線程(守護線程)。

該案例:開啓了一個qq檢測升級的後臺線程,經過while真循環進行不停檢測,當計數器變爲100的時候,表示檢測完畢,提示是否更新,線程同時結束。

爲了驗證,當非後臺線程結束時,後臺線程是否終止,故意讓該後臺線程睡眠一會。發現只要main線程執行完畢,後臺線程也就隨之消亡了。

class QQUpdate implements Runnable {

int i = 0;

 

@Override

public void run() {

while (true) {

 

System.out.println(Thread.currentThread().getName() + " 檢測是否有可用更新");

i++;

try {

Thread.sleep(10);

} catch (InterruptedException e) {

 

e.printStackTrace();

}

if (i == 100) {

System.out.println("有可用更新,是否升級?");

break;

}

}

}

}

public class Demo9 {

public static void main(String[] args) {

QQUpdate qq = new QQUpdate();

Thread th = new Thread(qq, "qqupdate");

th.setDaemon(true);

th.start();

System.out.println(th.isDaemon());

System.out.println("hello world");

}

}

     

Threadjoin方法

A線程執行到了B線程Join方法時A就會等待,等B線程都執行完A纔會執行,Join能夠用來臨時加入線程執行

本案例,啓動了一個JoinThread線程,main(主線程)進行for循環,當計數器爲50時,讓JoinThread,經過join方法,加入到主線程中,發現只有JoinThread線程執行完,主線程纔會執行完畢.

能夠刻意讓JoinThread線程sleep,若是JoinThread沒有調用join方法,那麼確定是主線程執行完畢,可是因爲JoinThread線程加入到了main線程,必須等JoinThread執行完畢主線程才能繼續執行。

class JoinThread implements Runnable {

 

@Override

public void run() {

int i = 0;

while (i < 300) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + " i:" + i);

i++;

}

}

}

 

public class Demo10 {

public static void main(String[] args) throws InterruptedException {

JoinThread jt = new JoinThread();

Thread th = new Thread(jt, "one");

th.start();

int i = 0;

while (i < 200) {

if (i == 100) {

th.join();

}

System.err.println(Thread.currentThread().getName() + " i:" + i);

i++;

 

}

}

}

上述程序用到了Thread類中的join方法,即th.join語句,做用是將th對應的線程合併到嗲用th.join語句的線程中,main方法的線程中計數器到達100以前,main線程和one線程是交替執行的。在main線程中的計數器到達100後,只有one線程執行,也就是one線程此時被加進了mian線程中,one線程不執行完,main線程會一直等待

帶參數的join方法是指定合併時間,有納秒和毫秒級別。

 

 

線程的概述

進程:正在運行的程序,負責了這個程序的內存空間分配,表明了內存中的執行區域。

線程:就是在一個進程中負責一個執行路徑。

多線程:就是在一個進程中多個執行路徑同時執行。

 

 

 

圖上的一鍵優化與垃圾清除同時在運行,在一個進程中同時在執行了多個任務。

假象:

電腦上的程序同時在運行多任務操做系統能同時運行多個進程(程序)——但實際是因爲CPU分時機制的做用,使每一個進程都能循環得到本身的CPU時間片。但因爲輪換速度很是快,使得全部程序好象是在同時運行同樣。

 

 

多線程的好處:

  1. 解決了一個進程裏面能夠同時運行多個任務(執行路徑)。
  2. 提供資源的利用率,而不是提供效率。

多線程的弊端:

  1. 下降了一個進程裏面的線程的執行頻率。
  2. 對線程進行管理要求額外的 CPU開銷。線程的使用會給系統帶來上下文切換的額外負擔。
  3. 公有變量的同時讀或寫。當多個線程須要對公有變量進行寫操做時,後一個線程每每會修改掉前一個線程存放的數據,發生線程安全問題。
  4. 線程的死鎖。即較長時間的等待或資源競爭以及死鎖等多線程症狀。
  5. 繼承Thread

建立線程的方式

2.1 建立線程的方式一

 

getName()是獲取線程的名字。

執行後的效果:

 

問題: 先按照順序運行完了張三,而後接着再按照順序運行完李四,咱們想要的效果是張三和李四作資源的爭奪戰,也就是先是張三而後李四,沒有順序的執行。這就證實多線程沒有起到效果。

  1. 須要複寫run方法,把要執行的任務放在run方法中。

 

運行效果:

 

問題: 先按照順序運行完了張三,而後接着再按照順序運行完李四,咱們想要的效果是張三和李四作資源的爭奪戰,也就是先是張三而後李四,沒有順序的執行。這就證實多線程沒有起到效果。

 

  1. 調用start()方法啓動線程

 

效果:

 

達到了咱們預期的效果。

線程的使用細節:

  1. 線程的啓動使用父類的start()方法
  2. 若是線程對象直接調用run(),那麼JVN不會看成線程來運行,會認爲是普通的方法調用。
  3. 線程的啓動只能由一次,不然拋出異常
  4. 能夠直接建立Thread類的對象並啓動該線程,可是若是沒有重寫run(),什麼也不執行。
  5. 匿名內部類的線程實現方式

2.2 線程的狀態

 

建立:新建立了一個線程對象

可運行:線程對象建立後,其餘線程調用了該對象的start()方法。該狀態的線程位於可運行線程池中,變得可運行,等待獲取cpu執行權

運行:就緒狀態的線程獲取了CPU執行權,執行程序代碼。

阻臨時塞阻塞狀態是線程由於某種緣由放棄CPU使用權,暫時中止運行。直到線程進入就緒狀態,纔有機會轉到運行狀態

死亡:線程執行完它的任務時。

2.3 常見線程的方法

Thread(String name)     初始化線程的名字

 getName()             返回線程的名字

 setName(String name)    設置線程對象名

 sleep()                 線程睡眠指定的毫秒數。

 getPriority()             返回當前線程對象的優先級   默認線程的優先級是5

 setPriority(int newPriority) 設置線程的優先級    雖然設置了線程的優先級,可是具體的實現取決於底層的操做系統的實現(最大的優先級是10 ,最小的1 , 默認是5)。

 currentThread()      返回CPU正在執行的線程的對象

class ThreadDemo1 extends Thread

{

public ThreadDemo1(){

  

}

public ThreadDemo1( String name ){

   super( name );

}

    

public void run(){

   int i = 0;

   while(i < 30){

  i++;

      System.out.println( this.getName() + " "+ " : i = " + i);

  System.out.println( Thread.currentThread().getName() + " "+ " : i = " + i);

  System.out.println( Thread.currentThread() == this );

  System.out.println( "getId()" + " "+ " : id = " + super.getId() );

  System.out.println( "getPriority()" + " "+ " : Priority = " + super.getPriority() );

   }

}

}

class Demo3 

{

public static void main(String[] args)

{

        ThreadDemo1 th1 = new ThreadDemo1("線程1");

ThreadDemo1 th2 = new ThreadDemo1("線程2");

        // 設置線程名

        th1.setName( "th1" );

th2.setName( "th2" );

        // 設置線程優先級  1 ~ 10

th1.setPriority( 10 );

th2.setPriority( 7 );

// 查看SUN定義的線程優先級範圍

System.out.println("max : " + Thread.MAX_PRIORITY );

System.out.println("min : " + Thread.MIN_PRIORITY );

        System.out.println("nor : " + Thread.NORM_PRIORITY );

th1.start();

th2.start();

System.out.println("Hello World!");

}

}

 

練習:模擬賣票

 

 

存在問題:這時候啓動了四個線程,那麼tickets是一個成員變量,也就是在一個線程對象中都維護了屬於本身的tickets屬性,那麼就總共存在了四份。

解決方案一:tickets使用staitc修飾,使每一個線程對象都是共享一份屬性。

 

解決方案2編寫一個類實現Runnable接口。

2.4 建立線程的方式二

建立線程的第二種方式.使用Runnable接口.

該類中的代碼就是對線程要執行的任務的定義.

1:定義了實現Runnable接口

2:重寫Runnable接口中的run方法,就是將線程運行的代碼放入在run方法中

3:經過Thread類創建線程對象

4:將Runnable接口的子類對象做爲實際參數,傳遞給Thread類構造方法

5:調用Thread類的start方法開啓線程,並調用Runable接口子類run方法

爲何要將Runnable接口的子類對象傳遞給Thread的構造函數,由於自定義的run方法所屬對象是Runnable接口的子類對象,因此要讓線程去執行指定對象的run方法

package cn.itcast.gz.runnable;

public class Demo1 {

public static void main(String[] args) {

MyRun my = new MyRun();

Thread t1 = new Thread(my);

t1.start();

for (int i = 0; i < 200; i++) {

System.out.println("main:" + i);

}

}

}

class MyRun implements Runnable {

public void run() {

for (int i = 0; i < 200; i++) {

System.err.println("MyRun:" + i);

}

}

}

 

 

理解Runnable:

Thread類能夠理解爲一個工人,Runnable的實現類的對象就是這個工人的工做(經過構造方法傳遞).Runnable接口中只有一個方法run方法,該方法中定義的事會被新線程執行的代碼.當咱們把Runnable的子類對象傳遞給Thread的構造時,實際上就是讓給Thread取得run方法,就是給了Thread一項任務.

 

 

買票例子使用Runnable接口實現

在上面的代碼中故意照成線程執行完後,執行Thread.sleep100),以讓cpu讓給別的線程,該方法會出現非運行時異常須要處理,這裏必須進行try{}catch(){},由於子類不能比父類拋出更多的異常,接口定義中沒有異常,實現類也不能拋出異常。

運行發現票號出現了負數,顯示了同一張票被賣了4次的狀況。

出現了一樣的問題。如何解決?

class MyTicket implements Runnable {

int tickets = 100;

public void run() {

while (true) {

if (tickets > 0) {

try {

Thread.sleep(100);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + "窗口@銷售:"

+ tickets + "號票");

tickets--;

 

} else {

System.out.println("票已賣完。。。");

break;

}

}

}

}

public class Demo6 {

public static void main(String[] args) {

MyTicket mt = new MyTicket();

Thread t1 = new Thread(mt);

Thread t2 = new Thread(mt);

Thread t3 = new Thread(mt);

Thread t4 = new Thread(mt);

t1.start();

t2.start();

t3.start();

t4.start();

}

}

鎖對象

什麼是鎖對象?

每一個java對象都有一個鎖對象.並且只有一把鑰匙.

如何建立鎖對象:

    可使用this關鍵字做爲鎖對象,也可使用所在類的字節碼文件對應的Class對象做爲鎖對象

1. 類名.class       

2. 對象.getClass()   

Java中的每一個對象都有一個內置鎖,只有當對象具備同步方法代碼時,內置鎖纔會起做用,當進入一個同步的非靜態方法時,就會自動得到與類的當前實例(this)相關的鎖,該類的代碼就是正在執行的代碼。得到一個對象的鎖也成爲獲取鎖、鎖定對象也能夠稱之爲監視器來指咱們正在獲取的鎖對象。

由於一個對象只有一個鎖,全部若是一個線程得到了這個鎖,其餘線程就不能得到了,直到這個線程釋放(或者返回)鎖。也就是說在鎖釋放以前,任何其餘線程都不能進入同步代碼(不能夠進入該對象的任何同步方法)。釋放鎖指的是持有該鎖的線程退出同步方法,此時,其餘線程能夠進入該對象上的同步方法。

1:只能同步方法(代碼塊),不能同步變量或者類

2:每一個對象只有一個鎖

3:沒必要同步類中的全部方法,類能夠同時具備同步方法和非同步方法

4:若是兩個線程要執行一個類中的一個同步方法,而且他們使用的是了類的同一個實例(對象)來調用方法,那麼一次只有一個線程可以執行該方法,另外一個線程須要等待,直到第一個線程完成方法調用,總結就是:一個線程得到了對象的鎖,其餘線程不能夠進入該對象的同步方法。

5:若是類同時具備同步方法和非同步方法,那麼多個線程仍然能夠訪問該類的非同步方法。

同步會影響性能(甚至死鎖),優先考慮同步代碼塊。

6:若是線程進入sleep() 睡眠狀態,該線程會繼續持有鎖,不會釋放。

死鎖

 

經典的「哲學家就餐問題」,5個哲學家吃中餐,坐在圓卓子旁。每人有5根筷子(不是5雙),每兩我的中間放一根,哲學家時而思考,時而進餐。每一個人都須要一雙筷子才能吃到東西,吃完後將筷子放回原處繼續思考,若是每一個人都馬上抓住本身左邊的筷子,而後等待右邊的筷子空出來,同時又不放下已經拿到的筷子,這樣每一個人都沒法獲得1雙筷子,沒法吃飯都會餓死,這種狀況就會產生死鎖:每一個人都擁有其餘人須要的資源,同時又等待其餘人擁有的資源,而且每一個人在得到全部須要的資源以前都不會放棄已經擁有的資源。

當多個線程完成功能須要同時獲取多個共享資源的時候可能會致使死鎖。

1:兩個任務以相反的順序申請兩個鎖,死鎖就可能出現

2:線程T1得到鎖L1,線程T2得到鎖L2,而後T1申請得到鎖L2,同時T2申請得到鎖L1,此時兩個線程將要永久阻塞,死鎖出現

若是一個類可能發生死鎖,那麼並不意味着每次都會發生死鎖,只是表示有可能。要避免程序中出現死鎖。

例如,某個程序須要訪問兩個文件,當進程中的兩個線程分別各鎖住了一個文件,那它們都在等待對方解鎖另外一個文件,而這永遠不會發生。

3:要避免死鎖

public class DeadLock {

public static void main(String[] args) {

new Thread(new Runnable() { // 建立線程, 表明中國人

public void run() {

synchronized ("刀叉") { // 中國人拿到了刀叉

System.out.println(Thread.currentThread().getName()

+ ": 你不給我筷子, 我就不給你刀叉");

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized ("筷子") {

System.out.println(Thread.currentThread()

.getName() + ": 給你刀叉");

}

}

}

}, "中國人").start();

new Thread(new Runnable() { // 美國人

public void run() {

synchronized ("筷子") { // 美國人拿到了筷子

System.out.println(Thread.currentThread().getName()

+ ": 你先給我刀叉, 我再給你筷子");

try {

Thread.sleep(10);

} catch (InterruptedException e) {

e.printStackTrace();

}

synchronized ("刀叉") {

System.out.println(Thread.currentThread()

.getName() + ": 好吧, 把筷子給你.");

}

}

}

}, "美國人").start();

}

}

線程的通信

線程間通訊其實就是多個線程在操做同一個資源,但操做動做不一樣

生產者消費者

若是有多個生產者和消費者,必定要使用while循環判斷標記,而後在使用notifyAll喚醒,否者容易只用notify容易出現只喚醒本方線程狀況,致使程序中的全部線程都在等待。

例如:有一個數據存儲空間,劃分爲兩個部分,一部分存儲人的姓名,一部分存儲性別,咱們開啓一個線程,不停地想其中存儲姓名和性別(生產者),開啓另外一個線程從數據存儲空間中取出數據(消費者)。

因爲是多線程的,就須要考慮,假如生產者剛向數據存儲空間中添加了一我的名,尚未來得及添加性別,cpu就切換到了消費者的線程,消費者就會將這我的的姓名和上一我的的性別進行了輸出。

還有一種狀況是生產者生產了若干次數據,消費者纔開始取數據,或者消費者取出數據後,沒有等到消費者放入新的數據,消費者又重複的取出本身已經去過的數據。

public class Demo10 {

public static void main(String[] args) {

Person p = new Person();

Producer pro = new Producer(p);

Consumer con = new Consumer(p);

Thread t1 = new Thread(pro, "生產者");

Thread t2 = new Thread(con, "消費者");

t1.start();

t2.start();

}

}

 

// 使用Person做爲數據存儲空間

class Person {

String name;

String gender;

}

 

// 生產者

class Producer implements Runnable {

Person p;

 

public Producer() {

 

}

 

public Producer(Person p) {

this.p = p;

}

 

@Override

public void run() {

int i = 0;

while (true) {

if (i % 2 == 0) {

p.name = "jack";

p.gender = "man";

} else {

p.name = "小麗";

p.gender = "";

}

i++;

}

 

}

 

}

 

// 消費者

class Consumer implements Runnable {

Person p;

 

public Consumer() {

 

}

 

public Consumer(Person p) {

this.p = p;

}

 

@Override

public void run() {

 

while (true) {

System.out.println("name:" + p.name + "---gnder:" + p.gender);

}

}

 

}

 

在上述代碼中,ProducerConsumer 類的內部都維護了一個Person類型的p成員變量,經過構造函數進行賦值,在man方法中建立了一個Person對象,將其同時傳遞給ProducerConsumer對象,因此ProducerConsumer訪問的是同一個Person對象。並啓動了兩個線程。

 

輸出:

 

顯然屏幕輸出了小麗 man 這樣的結果是出現了線程安全問題。因此須要使用synchronized來解決該問題。

 

package cn.itcast.gz.runnable;

 

public class Demo10 {

public static void main(String[] args) {

Person p = new Person();

Producer pro = new Producer(p);

Consumer con = new Consumer(p);

Thread t1 = new Thread(pro, "生產者");

Thread t2 = new Thread(con, "消費者");

t1.start();

t2.start();

}

}

 

// 使用Person做爲數據存儲空間

class Person {

String name;

String gender;

}

 

// 生產者

class Producer implements Runnable {

Person p;

 

public Producer() {

 

}

 

public Producer(Person p) {

this.p = p;

}

 

@Override

public void run() {

int i = 0;

while (true) {

synchronized (p) {

if (i % 2 == 0) {

p.name = "jack";

p.gender = "man";

} else {

p.name = "小麗";

p.gender = "";

}

i++;

}

 

}

 

}

 

}

 

// 消費者

class Consumer implements Runnable {

Person p;

 

public Consumer() {

 

}

 

public Consumer(Person p) {

this.p = p;

}

 

@Override

public void run() {

 

while (true) {

synchronized (p) {

System.out.println("name:" + p.name + "---gnder:" + p.gender);

}

 

}

}

 

}

 

編譯運行:屏幕沒有再輸出jack –  或者小麗- man 這種狀況了。說明咱們解決了線程同步問題,可是仔細觀察,生產者生產了若干次數據,消費者纔開始取數據,或者消費者取出數據後,沒有等到消費者放入新的數據,消費者又重複的取出本身已經去過的數據。這個問題依然存在。

升級:在Person類中添加兩個方法,setread方法並設置爲synchronized讓生產者和消費者調用這兩個方法。

public class Demo10 {

public static void main(String[] args) {

Person p = new Person();

Producer pro = new Producer(p);

Consumer con = new Consumer(p);

Thread t1 = new Thread(pro, "生產者");

Thread t2 = new Thread(con, "消費者");

t1.start();

t2.start();

}

}

 

// 使用Person做爲數據存儲空間

class Person {

String name;

String gender;

 

 

public synchronized void set(String name, String gender) {

this.name = name;

this.gender = gender;

}

 

public synchronized void read() {

System.out.println("name:" + this.name + "----gender:" + this.gender);

}

 

}

 

// 生產者

class Producer implements Runnable {

Person p;

 

public Producer() {

 

}

 

public Producer(Person p) {

this.p = p;

}

 

@Override

public void run() {

int i = 0;

while (true) {

 

if (i % 2 == 0) {

p.set("jack", "man");

} else {

p.set("小麗", "");

}

i++;

 

}

 

}

 

}

 

// 消費者

class Consumer implements Runnable {

Person p;

 

public Consumer() {

 

}

 

public Consumer(Person p) {

this.p = p;

}

 

@Override

public void run() {

 

while (true) {

p.read();

 

}

}

 

}

 

 

需求:咱們須要生產者生產一次,消費者就消費一次。而後這樣有序的循環。

這就須要使用線程間的通訊了。Java經過Object類的waitnotifynotifyAll這幾個方法實現線程間的通訊。

1.1.1. 等待喚醒機制

wait:告訴當前線程放棄執行權,並放棄監視器(鎖)並進入阻塞狀態,直到其餘線程持有得到執行權,並持有了相同的監視器(鎖)並調用notify爲止。

notify:喚醒持有同一個監視器(鎖)中調用wait的第一個線程,例如,餐館有空位置後,等候就餐最久的顧客最早入座。注意:被喚醒的線程是進入了可運行狀態。等待cpu執行權。

notifyAll:喚醒持有同一監視器中調用wait的全部的線程。

 

如何解決生產者和消費者的問題?

能夠經過設置一個標記,表示數據的(存儲空間的狀態)例如,當消費者讀取了(消費了一次)一次數據以後能夠將標記改成false,當生產者生產了一個數據,將標記改成true

,也就是隻有標記爲true的時候,消費者才能取走數據,標記爲false時候生產者才生產數據。

代碼實現:

package cn.itcast.gz.runnable;

 

public class Demo10 {

public static void main(String[] args) {

Person p = new Person();

Producer pro = new Producer(p);

Consumer con = new Consumer(p);

Thread t1 = new Thread(pro, "生產者");

Thread t2 = new Thread(con, "消費者");

t1.start();

t2.start();

}

}

 

// 使用Person做爲數據存儲空間

class Person {

String name;

String gender;

boolean flag = false;

 

public synchronized void set(String name, String gender) {

if (flag) {

try {

wait();

} catch (InterruptedException e) {

 

e.printStackTrace();

}

}

this.name = name;

this.gender = gender;

flag = true;

notify();

}

 

public synchronized void read() {

if (!flag) {

try {

wait();

} catch (InterruptedException e) {

 

e.printStackTrace();

}

}

System.out.println("name:" + this.name + "----gender:" + this.gender);

flag = false;

notify();

}

 

}

 

// 生產者

class Producer implements Runnable {

Person p;

 

public Producer() {

 

}

 

public Producer(Person p) {

this.p = p;

}

 

@Override

public void run() {

int i = 0;

while (true) {

 

if (i % 2 == 0) {

p.set("jack", "man");

} else {

p.set("小麗", "");

}

i++;

 

}

 

}

 

}

 

// 消費者

class Consumer implements Runnable {

Person p;

 

public Consumer() {

 

}

 

public Consumer(Person p) {

this.p = p;

}

 

@Override

public void run() {

 

while (true) {

p.read();

 

}

}

 

}

 

 

線程間通訊其實就是多個線程在操做同一個資源,但操做動做不一樣,waitnotify(),notifyAll()都使用在同步中,由於要對持有監視器(鎖)的線程操做,因此要使用在同步中,由於只有同步才具備鎖。

爲何這些方法定義在Object類中

由於這些方法在操做線程時,都必需要標識他們所操做線程持有的鎖,只有同一個鎖上的被等待線程,能夠被統一鎖上notify喚醒,不能夠對不一樣鎖中的線程進行喚醒,就是等待和喚醒必須是同一個鎖。而鎖因爲可使任意對象,因此能夠被任意對象調用的方法定義在Object類中

wait() sleep()有什麼區別?

wait():釋放資源,釋放鎖。是Object的方法

sleep():釋放資源,不釋放鎖。是Thread的方法

定義了notify爲何還要定義notifyAll,由於只用notify容易出現只喚醒本方線程狀況,致使程序中的全部線程都在等待。

 

2. 線程生命週期

任何事物都是生命週期,線程也是,

1. 正常終止  當線程的run()執行完畢,線程死亡。

2. 使用標記中止線程

注意:Stop方法已過期,就不能再使用這個方法。

如何使用標記中止線程中止線程。

開啓多線程運行,運行代碼一般是循環結構,只要控制住循環,就可讓run方法結束,線程就結束。

 

class StopThread implements Runnable {

public boolean tag = true;

@Override

public void run() {

int i = 0;

 

while (tag) {

i++;

System.out.println(Thread.currentThread().getName() + "i:" + i);

}

}

}

public class Demo8 {

public static void main(String[] args) {

StopThread st = new StopThread();

Thread th = new Thread(st, "線程1");

th.start();

for (int i = 0; i < 100; i++) {

if (i == 50) {

System.out.println("main i:" + i);

st.tag = false;

}

}

}

}

上述案例中定義了一個計數器i,用來控制main方法(主線程)的循環打印次數,在i50這段時間內,兩個線程交替執行,當計數器變爲50,程序將標記改成false,也就是終止了線程1while循環,run方法結束,線程1也隨之結束。注意:當計數器i變爲50的,將標記改成false的時候,cpu不必定立刻回到線程1,因此線程1並不會立刻終止。

3. 後臺線程

後臺線程:就是隱藏起來一直在默默運行的線程,直到進程結束。

 實現:

      setDaemon(boolean on)

 特色:

當全部的非後臺線程結束時,程序也就終止了同時還會殺死進程中的全部後臺線程,也就是說,只要有非後臺線程還在運行,程序就不會終止,執行main方法的主線程就是一個非後臺線程。

必須在啓動線程以前(調用start方法以前)調用setDaemontrue)方法,才能夠把該線程設置爲後臺線程。

一旦main()執行完畢,那麼程序就會終止,JVM也就退出了。

可使用isDaemon() 測試該線程是否爲後臺線程(守護線程)。

該案例:開啓了一個qq檢測升級的後臺線程,經過while真循環進行不停檢測,當計數器變爲100的時候,表示檢測完畢,提示是否更新,線程同時結束。

爲了驗證,當非後臺線程結束時,後臺線程是否終止,故意讓該後臺線程睡眠一會。發現只要main線程執行完畢,後臺線程也就隨之消亡了。

class QQUpdate implements Runnable {

int i = 0;

 

@Override

public void run() {

while (true) {

 

System.out.println(Thread.currentThread().getName() + " 檢測是否有可用更新");

i++;

try {

Thread.sleep(10);

} catch (InterruptedException e) {

 

e.printStackTrace();

}

if (i == 100) {

System.out.println("有可用更新,是否升級?");

break;

}

}

}

}

public class Demo9 {

public static void main(String[] args) {

QQUpdate qq = new QQUpdate();

Thread th = new Thread(qq, "qqupdate");

th.setDaemon(true);

th.start();

System.out.println(th.isDaemon());

System.out.println("hello world");

}

}

     

Threadjoin方法

A線程執行到了B線程Join方法時A就會等待,等B線程都執行完A纔會執行,Join能夠用來臨時加入線程執行

本案例,啓動了一個JoinThread線程,main(主線程)進行for循環,當計數器爲50時,讓JoinThread,經過join方法,加入到主線程中,發現只有JoinThread線程執行完,主線程纔會執行完畢.

能夠刻意讓JoinThread線程sleep,若是JoinThread沒有調用join方法,那麼確定是主線程執行完畢,可是因爲JoinThread線程加入到了main線程,必須等JoinThread執行完畢主線程才能繼續執行。

class JoinThread implements Runnable {

 

@Override

public void run() {

int i = 0;

while (i < 300) {

try {

Thread.sleep(1000);

} catch (InterruptedException e) {

e.printStackTrace();

}

System.out.println(Thread.currentThread().getName() + " i:" + i);

i++;

}

}

}

 

public class Demo10 {

public static void main(String[] args) throws InterruptedException {

JoinThread jt = new JoinThread();

Thread th = new Thread(jt, "one");

th.start();

int i = 0;

while (i < 200) {

if (i == 100) {

th.join();

}

System.err.println(Thread.currentThread().getName() + " i:" + i);

i++;

 

}

}

}

上述程序用到了Thread類中的join方法,即th.join語句,做用是將th對應的線程合併到嗲用th.join語句的線程中,main方法的線程中計數器到達100以前,main線程和one線程是交替執行的。在main線程中的計數器到達100後,只有one線程執行,也就是one線程此時被加進了mian線程中,one線程不執行完,main線程會一直等待

帶參數的join方法是指定合併時間,有納秒和毫秒級別。

相關文章
相關標籤/搜索