程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> 關於JAVA >> Java 多線程實例講授(一)

Java 多線程實例講授(一)

編輯:關於JAVA

Java 多線程實例講授(一)。本站提示廣大學習愛好者:(Java 多線程實例講授(一))文章只能為提供參考,不一定能成為您想要的結果。以下是Java 多線程實例講授(一)正文


Java多線程(一)

多線程作為Java中很主要的一個常識點,在此照樣有需要總結一下的。

一.線程的性命周期及五種根本狀況

關於Java中線程的性命周期,起首看一下上面這張較為經典的圖:

上圖中根本上包括了Java中多線程各主要常識點。控制了上圖中的各常識點,Java中的多線程也就根本上控制了。重要包含:

Java線程具有五中根本狀況

新建狀況(New):當線程對象對創立後,即進入了新建狀況,如:Thread t = new MyThread();

停當狀況(Runnable):當挪用線程對象的start()辦法(t.start();),線程即進入停當狀況。處於停當狀況的線程,只是解釋此線程曾經做好了預備,隨時期待CPU調劑履行,其實不是說履行了t.start()此線程立刻就會履行;

運轉狀況(Running):當CPU開端調劑處於停當狀況的線程時,此時線程才得以真正履行,即進入到運轉狀況。注:就     緒狀況是進入到運轉狀況的獨一進口,也就是說,線程要想進入運轉狀況履行,起首必需處於停當狀況中;

壅塞狀況(Blocked):處於運轉狀況中的線程因為某種緣由,臨時廢棄對CPU的應用權,停滯履行,此時進入壅塞狀況,直到其進入到停當狀況,才 無機會再次被CPU挪用以進入到運轉狀況。依據壅塞發生的緣由分歧,壅塞狀況又可以分為三種:

1.期待壅塞:運轉狀況中的線程履行wait()辦法,使本線程進入到期待壅塞狀況;

2.同步壅塞 -- 線程在獲得synchronized同步鎖掉敗(由於鎖被其它線程所占用),它會進入同步壅塞狀況;

3.其他壅塞 -- 經由過程挪用線程的sleep()或join()或收回了I/O要求時,線程會進入到壅塞狀況。當sleep()狀況超時、join()期待線程終止或許超時、或許I/O處置終了時,線程從新轉入停當狀況。

逝世亡狀況(Dead):線程履行完了或許因異常加入了run()辦法,該線程停止性命周期。

二. Java多線程的創立及啟動

Java中線程的創立罕見有如三種根本情勢

1.繼續Thread類,重寫該類的run()辦法。

class MyThread extends Thread {
 
 private int i = 0;

 @Override
 public void run() {
 for (i = 0; i < 100; i++) {
  System.out.println(Thread.currentThread().getName() + " " + i);
 }
 }
}
public class ThreadTest {

 public static void main(String[] args) {
 for (int i = 0; i < 100; i++) {
  System.out.println(Thread.currentThread().getName() + " " + i);
  if (i == 30) {
  Thread myThread1 = new MyThread(); // 創立一個新的線程 myThread1 此線程進入新建狀況
  Thread myThread2 = new MyThread(); // 創立一個新的線程 myThread2 此線程進入新建狀況
  myThread1.start();   // 挪用start()辦法使得線程進入停當狀況
  myThread2.start();   // 挪用start()辦法使得線程進入停當狀況
  }
 }
 }
}

 如上所示,繼續Thread類,經由過程重寫run()辦法界說了一個新的線程類MyThread,個中run()辦法的辦法體代表了線程須要完成的義務,稱之為線程履行體。當創立此線程類對象時一個新的線程得以創立,並進入到線程新建狀況。經由過程挪用線程對象援用的start()辦法,使得該線程進入到停當狀況,此時此線程其實不必定會立時得以履行,這取決於CPU調劑機會。

2.完成Runnable接口,偏重寫該接口的run()辦法,該run()辦法異樣是線程履行體,創立Runnable完成類的實例,並以此實例作為Thread類的target來創立Thread對象,該Thread對象才是真實的線程對象。

class MyRunnable implements Runnable {
 private int i = 0;

 @Override
 public void run() {
 for (i = 0; i < 100; i++) {
  System.out.println(Thread.currentThread().getName() + " " + i);
 }
 }
}
public class ThreadTest {

 public static void main(String[] args) {
 for (int i = 0; i < 100; i++) {
  System.out.println(Thread.currentThread().getName() + " " + i);
  if (i == 30) {
  Runnable myRunnable = new MyRunnable(); // 創立一個Runnable完成類的對象
  Thread thread1 = new Thread(myRunnable); // 將myRunnable作為Thread target創立新的線程
  Thread thread2 = new Thread(myRunnable);
  thread1.start(); // 挪用start()辦法使得線程進入停當狀況
  thread2.start();
  }
 }
 }
}

信任以上兩種創立新線程的方法年夜家都很熟習了,那末Thread和Runnable之間究竟是甚麼關系呢?我們起首來看一下上面這個例子。

public class ThreadTest {

 public static void main(String[] args) {
 for (int i = 0; i < 100; i++) {
  System.out.println(Thread.currentThread().getName() + " " + i);
  if (i == 30) {
  Runnable myRunnable = new MyRunnable();
  Thread thread = new MyThread(myRunnable);
  thread.start();
  }
 }
 }
}

class MyRunnable implements Runnable {
 private int i = 0;

 @Override
 public void run() {
 System.out.println("in MyRunnable run");
 for (i = 0; i < 100; i++) {
  System.out.println(Thread.currentThread().getName() + " " + i);
 }
 }
}

class MyThread extends Thread {

 private int i = 0;
 
 public MyThread(Runnable runnable){
 super(runnable);
 }

 @Override
 public void run() {
 System.out.println("in MyThread run");
 for (i = 0; i < 100; i++) {
  System.out.println(Thread.currentThread().getName() + " " + i);
 }
 }
}

異樣的,與完成Runnable接口創立線程方法類似,分歧的處所在於

1 Thread thread = new MyThread(myRunnable);

那末這類方法可以順遂創立出一個新的線程麼?謎底是確定的。至於此時的線程履行體究竟是MyRunnable接口中的run()辦法照樣MyThread類中的run()辦法呢?經由過程輸入我們曉得線程履行體是MyThread類中的run()辦法。其實緣由很簡略,由於Thread類自己也是完成了Runnable接口,而run()辦法最早是在Runnable接口中界說的辦法。

 public interface Runnable {
 
 public abstract void run();
 
 }

我們看一下Thread類中對Runnable接口中run()辦法的完成:

@Override
 public void run() {
 if (target != null) {
  target.run();
 }
 }

也就是說,當履行到Thread類中的run()辦法時,會起首斷定target能否存在,存在則履行target中的run()辦法,也就是完成了Runnable接口偏重寫了run()辦法的類中的run()辦法。然則上述給到的列子中,因為多態的存在,基本就沒有履行到Thread類中的run()辦法,而是直接先履行了運轉時類型即MyThread類中的run()辦法。

3.應用Callable和Future接口創立線程。詳細是創立Callable接口的完成類,並完成clall()辦法。並應用FutureTask類來包裝Callable完成類的對象,且以此FutureTask對象作為Thread對象的target來創立線程。

 看著似乎有點龐雜,直接來看一個例子就清楚了。

public class ThreadTest {

 public static void main(String[] args) {

 Callable<Integer> myCallable = new MyCallable(); // 創立MyCallable對象
 FutureTask<Integer> ft = new FutureTask<Integer>(myCallable); //應用FutureTask來包裝MyCallable對象

 for (int i = 0; i < 100; i++) {
  System.out.println(Thread.currentThread().getName() + " " + i);
  if (i == 30) {
  Thread thread = new Thread(ft); //FutureTask對象作為Thread對象的target創立新的線程
  thread.start();   //線程進入到停當狀況
  }
 }

 System.out.println("主線程for輪回履行終了..");
 
 try {
  int sum = ft.get();  //獲得新創立的新線程中的call()辦法前往的成果
  System.out.println("sum = " + sum);
 } catch (InterruptedException e) {
  e.printStackTrace();
 } catch (ExecutionException e) {
  e.printStackTrace();
 }

 }
}


class MyCallable implements Callable<Integer> {
 private int i = 0;

 // 與run()辦法分歧的是,call()辦法具有前往值
 @Override
 public Integer call() {
 int sum = 0;
 for (; i < 100; i++) {
  System.out.println(Thread.currentThread().getName() + " " + i);
  sum += i;
 }
 return sum;
 }

}

起首,我們發明,在完成Callable接口中,此時不再是run()辦法了,而是call()辦法,此call()辦法作為線程履行體,同時還具有前往值!在創立新的線程時,是經由過程FutureTask來包裝MyCallable對象,同時作為了Thread對象的target。那末看下FutureTask類的界說: 

public class FutureTask<V> implements RunnableFuture<V> {
 
 //....
 
 }
public interface RunnableFuture<V> extends Runnable, Future<V> {
 
 void run();
 
 }

因而,我們發明FutureTask類現實上是同時完成了Runnable和Future接口,由此才使得其具有Future和Runnable兩重特征。經由過程Runnable特征,可以作為Thread對象的target,而Future特征,使得其可以獲得新創立線程中的call()辦法的前往值。

履行下此法式,我們發明sum = 4950永久都是最初輸入的。而“主線程for輪回履行終了..”則極可能是在子線程輪回中央輸入。由CPU的線程調劑機制,我們曉得,“主線程for輪回履行終了..”的輸入機會是沒有任何成績的,那末為何sum =4950會永久最初輸入呢?

緣由在於經由過程ft.get()辦法獲得子線程call()辦法的前往值時,當子線程此辦法還未履行終了,ft.get()辦法會一向壅塞,直到call()辦法履行終了能力取到前往值。

上述重要講授了三種罕見的線程創立方法,關於線程的啟動而言,都是挪用線程對象的start()辦法,須要特殊留意的是:不克不及對統一線程對象兩次挪用start()辦法。

三. Java多線程的停當、運轉和逝世亡狀況

停當狀況轉換為運轉狀況:當此線程獲得處置器資本;

運轉狀況轉換為停當狀況:當此線程自動挪用yield()辦法或在運轉進程中掉行止理器資本。

運轉狀況轉換為逝世亡狀況:當此線程線程履行體履行終了或產生了異常。

此處須要特殊留意的是:當挪用線程的yield()辦法時,線程從運轉狀況轉換為停當狀況,但接上去CPU調劑停當狀況中的哪一個線程具有必定的隨機性,是以,能夠會湧現A線程挪用了yield()辦法後,接上去CPU依然調劑了A線程的情形。

因為現實的營業須要,經常會碰到須要在特准時機終止某一線程的運轉,使其進入到逝世亡狀況。今朝最通用的做法是設置一boolean型的變量,當前提知足時,使線程履行體疾速履行終了。如:

public class ThreadTest {

 public static void main(String[] args) {

 MyRunnable myRunnable = new MyRunnable();
 Thread thread = new Thread(myRunnable);
 
 for (int i = 0; i < 100; i++) {
  System.out.println(Thread.currentThread().getName() + " " + i);
  if (i == 30) {
  thread.start();
  }
  if(i == 40){
  myRunnable.stopThread();
  }
 }
 }
}

class MyRunnable implements Runnable {

 private boolean stop;

 @Override
 public void run() {
 for (int i = 0; i < 100 && !stop; i++) {
  System.out.println(Thread.currentThread().getName() + " " + i);
 }
 }

 public void stopThread() {
 this.stop = true;
 }

}

後續持續整頓相干文章,感謝年夜家對本站的支撐!

 系列文章:

java 多線程實例講授 (一)
Java 多線程實例詳解(二)
Java 多線程實例詳解(三)

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved