天天看點

Java并發程式設計:線程建立

  Java中建立線程最常用的方法有繼承Thread類和實作Runnable兩種。Thread類實際也是實作了Runnable方法,由于無法繼承多個父類但是可以繼承多個接口,所有建立程序大多是實作Runnable接口

  • 繼承Thread類
    • 建立線程示例
    • Thread類源碼分析
  • 實作Runnable接口
    • 建立線程示例
    • Runnable接口源碼分析

1 繼承Thread類

1.1 建立線程示例

  例如有15張票,有三個視窗,每個視窗賣五張票,可以使用繼承Thread類實作多線程處理。

import java.io.*;
import java.lang.Thread;
public class ExtendThread {
    public static class MultiThread extends Thread {
    private int total = ;
    private String name;
    MultiThread(String name) {
        // TODO Auto-generated constructor stub
        super(name);
    }
    @Override
    public void run () {
        while (total > ) {
            System.out.println("Ticket:" + total-- + " is saled by Thread:" + Thread.currentThread().getName());
        }
    }
}
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MultiThread mt1 = new MultiThread("Thread1");
        MultiThread mt2 = new MultiThread("Thread2");
        MultiThread mt3 = new MultiThread("Thread3");
        mt1.start();
        mt2.start();
        mt3.start();
    }
}
運作結果如下:
Ticket: is saled by Thread:Thread2
Ticket: is saled by Thread:Thread1
Ticket: is saled by Thread:Thread3
Ticket: is saled by Thread:Thread3
Ticket: is saled by Thread:Thread3
Ticket: is saled by Thread:Thread1
Ticket: is saled by Thread:Thread2
Ticket: is saled by Thread:Thread1
Ticket: is saled by Thread:Thread3
Ticket: is saled by Thread:Thread3
Ticket: is saled by Thread:Thread1
Ticket: is saled by Thread:Thread1
Ticket: is saled by Thread:Thread2
Ticket: is saled by Thread:Thread2
Ticket: is saled by Thread:Thread2
           

1.2 Thread類源碼分析

1.2.1 類聲明

  從類聲明可以看出Thread實作了Runnable接口。

1.2.2 構造函數

  Thread類的構造函數有8個,這裡隻介紹他的無參構造函數Thread(),其他構造函數可以到http://docs.oracle.com/javase/7/docs/api/學習了解。

public Thread() {
    init(null, null, "Thread-" + nextThreadNum(), );
}
           

  由init得第三個參數可以看出線程名稱命名規則是Thread-加上線程數組合。 init函數的内部實作如下:

/**
     * Initializes a Thread.
     *
     * @param g the Thread group
     * @param target the object whose run() method gets called
     * @param name the name of the new Thread
     * @param stackSize the desired stack size for the new thread, or
     *        zero to indicate that this parameter is to be ignored.
     */
    //ThreadGroup:線程組表示一個線程的集合。此外,線程組也可以包含其他線程組。線程組構成一棵樹,在樹中,除了初始線程組外,每個線程組都有一個父線程組。
    private void init(ThreadGroup g, Runnable target, String name,
                      long stackSize) {
    Thread parent = currentThread();
    SecurityManager security = System.getSecurityManager();
    if (g == null) {
        /* Determine if it's an applet or not */

        /* If there is a security manager, ask the security manager
           what to do. */
        if (security != null) {
        g = security.getThreadGroup();
        }

        /* If the security doesn't have a strong opinion of the matter
           use the parent thread group. */
        if (g == null) {
        g = parent.getThreadGroup();
        }
    }

    /* checkAccess regardless of whether or not threadgroup is
           explicitly passed in. */
    g.checkAccess();

    /*
     * Do we have the required permissions?
     */
    if (security != null) {
        if (isCCLOverridden(getClass())) {
            security.checkPermission(SUBCLASS_IMPLEMENTATION_PERMISSION);
        }
    }

        g.addUnstarted();

    this.group = g;

    //每個線程都有一個優先級,高優先級線程的執行優先于低優先級線程。每個線程都可以或不可以标記為一個守護程式。當某個線程中運作的代碼建立一個新 Thread 對象時,該新線程的初始優先級被設定為建立線程的優先級,并且當且僅當建立線程是守護線程時,新線程才是守護程式。
    this.daemon = parent.isDaemon();
    this.priority = parent.getPriority();
    this.name = name.toCharArray();
    if (security == null || isCCLOverridden(parent.getClass()))
        this.contextClassLoader = parent.getContextClassLoader();
    else
        this.contextClassLoader = parent.contextClassLoader;
    this.inheritedAccessControlContext = AccessController.getContext();
    this.target = target;
    setPriority(priority);
        if (parent.inheritableThreadLocals != null)
        this.inheritableThreadLocals =
        ThreadLocal.createInheritedMap(parent.inheritableThreadLocals);
        /* Stash the specified stack size in case the VM cares */
        this.stackSize = stackSize;

        /* Set thread ID */
        tid = nextThreadID();
    }
           

1.2.3 run()方法

  run定義了線程實際完成的功能,具體源碼如下:

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

  target是接口Runnable實作的引用,由于run方法并未做任何實作,是以繼承Thread類必須實作run方法。

1.2.4 start()方法

  start方法作用為啟動一個線程,源碼如下:

public synchronized void start() {
        if (threadStatus !=  || this != me)
            throw new IllegalThreadStateException();
        group.add(this);
        start0();
        if (stopBeforeStart) {
        stop0(throwableFromStop);
    }
 }
           

  start方法内部調用了本地方法start0建立線程,在建立線程之前會檢查目前線程對象是否已經運作過start方法,確定一個線程對象隻會運作一次start方法。如果多次運作start方法,就會導緻有多個線程同時操作相同的堆棧計數器等,導緻無法預期的結果。

1.2.5 yield()、wait()、sleep() 方法

  yield方法使正在運作的線程變成就緒狀态,建議先運作其他線程。但是這種方式隻是建議,并不一定會讓其他線程先運作,也有可能目前線程繼續運作,yield方法隻會讓相同優先級的線程優先執行。

  wait方法不屬于Thread類,他是Object類的方法。這個方法釋放線程鎖,知道收到notify通知為止。

  sleep方法會使線程休眠一段時間,但是休眠期間不會主動釋放cpu資源。

  這裡面内容挺多,忙完這段時間,把和這幾個方法相關的線程排程、同步鎖等知識溫習分享下。

1.2.6 join()方法

  join方法的作用是強行運作要join的線程,阻塞目前線程知道join的線程執行完畢。如下示例:

import java.io.*;
import java.lang.Thread;

public class TestJoin {
    public static class MultiThreadA extends Thread {
        private String name;
        private int count = ;

        public MultiThreadA(String name) {
            this.name = name;
        }

        @Override
        public void run() {
            while (count-- > ) {
                try {
                    Thread.currentThread().sleep();
                } catch (Exception e) {
                    // TODO: handle exception
                }
                System.out.println(name + count);
            }
        }
    }
    public static class MultiThreadB extends Thread {
        private String name;
        private int count = ;
        MultiThreadA a;

        public MultiThreadB(MultiThreadA a) {
        // TODO Auto-generated constructor stub
            this.a = a;
        }

        @Override
        public void run() {
            try {
                Thread.currentThread().sleep();
            } catch (Exception e) {
                // TODO: handle exception
            }
            System.out.println("Begin ThreadB");
            try {
                a.join();
            } catch (InterruptedException e) {
                // TODO: handle exception
                System.out.println("getException");
            }
            System.out.println("End ThreadB");
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MultiThreadA tA = new MultiThreadA("ThreadA");
        MultiThreadB tB = new MultiThreadB(tA);
        tA.start();
        tB.start();
    }
}
執行結果如下:
ThreadA4
Begin ThreadB
ThreadA3
ThreadA2
ThreadA1
ThreadA0
End ThreadB
           

  ThreadA首先列印了一條,然後ThreadB運作,遇到a.join()後運作ThreadA直到運作結束才會再次運作ThreadB。

2、實作Runnable接口

  通過實作Runnable接口可以建立線程,實作的過程和Thread類内部實作很相似。

2.1 建立線程示例

  Thread類示範了每個視窗售票不互相影響,各自賣五張票。如果需要三個視窗協同賣5張票,可以通過Runnalble共享變量,示例如下:

import java.io.*;
import java.lang.Thread;

public class TestRunnable {
    public static class MyThread implements Runnable {
        private String name;
        private int total = ;

        public MyThread(String name) {
            // TODO Auto-generated constructor stub
            this.name = name;
        }

        @Override
        public synchronized void run() {
            try {
                Thread.currentThread().sleep();
            } catch (Exception e) {
                // TODO: handle exception
            }
            while (total > ) {
                System.out.println("ticket:" + total + " is sold!");
                total--;
            }
        }
    }
    public static void main(String[] args) {
        // TODO Auto-generated method stub
        MyThread mt = new MyThread("myThread");
        Thread a = new Thread(mt);
        Thread b = new Thread(mt);
        Thread c = new Thread(mt);
        a.start();
        b.start();
        c.start();
    }
}
運作結果如下:
ticket: is sold!
ticket: is sold!
ticket: is sold!
ticket: is sold!
ticket: is sold!
           

三個線程共同完成5張票的售賣。

2.2 Runnable源碼分析

  Runnable接口中隻有一個抽象run方法,是以不管是實作Runnable接口或者繼承Thread類都需要重寫run方法。

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

參考: