java學習之Thread

一、前言

      Thread是線程,要學習線程,必須先知道進程。

      百度進程:進程(Process)是計算機中的程序關於某數據集合上的一次運行活動,是系統進行資源分配和調度的基本單位,是操作系統結構的基礎。

      我理解的進程:進程是電腦或者手機上運行的一個應用。

      百度線程:線程,有時被稱爲輕量級進程(Lightweight Process,LWP),是程序執行流的最小單元。

      我理解的線程:一個應用程序中的執行路徑,或者說是應用中的某一項任務。

二、主要內容

     1、Thread類的定義有兩種方式

           繼承Thread類

public class MyThread extends Thread {

	@Override
	public void run() {
		super.run();
	}
}

                          實現Runnable接口

public class MyRunnable implements Runnable {

	@Override
	public void run() {
		
	}
}
     2、自定義線程不共享數據與共享數據。

        不共享數據

        給線程定義一個成員變量並初始化,每次創建線程對象,該成員變量都會被初始化,在每個線程中,都是各自的成員變量。

public class MyThread extends Thread {
	private int count = 5;
	public MyThread(String name){
		super();
		this.setName(name);
	}
	@Override
	public  void run() {
		super.run();
		while(count>0){
			count--;
			System.out.println("由"+this.getName()+"該線程計算的count的結果是:"+count);
		}
	}
}
       測試代碼
public static void main(String[] args) {
		MyThread t1 = new MyThread("A");
		MyThread t2 = new MyThread("B");
		MyThread t3 = new MyThread("C");
		MyThread t4 = new MyThread("D");
		MyThread t5 = new MyThread("E");
		t1.start();
		t2.start();
		t3.start();
		t4.start();
		t5.start();
	}

       測試結果
由A該線程計算的count的結果是:4
由B該線程計算的count的結果是:4
由B該線程計算的count的結果是:3
由B該線程計算的count的結果是:2
由B該線程計算的count的結果是:1
由A該線程計算的count的結果是:3
由B該線程計算的count的結果是:0
由A該線程計算的count的結果是:2
由A該線程計算的count的結果是:1
由A該線程計算的count的結果是:0
由C該線程計算的count的結果是:4
由C該線程計算的count的結果是:3
由C該線程計算的count的結果是:2
由E該線程計算的count的結果是:4
由C該線程計算的count的結果是:1
由D該線程計算的count的結果是:4
由D該線程計算的count的結果是:3
由D該線程計算的count的結果是:2
由E該線程計算的count的結果是:3
由D該線程計算的count的結果是:1
由C該線程計算的count的結果是:0
由D該線程計算的count的結果是:0
由E該線程計算的count的結果是:2
由E該線程計算的count的結果是:1
由E該線程計算的count的結果是:0


       共享數據

       給該成員變量加上static的修飾符,則創建的線程都會共用該成員變量,從而達到共享數據的效果 ,但在多線程時,會出現併發的問題(不同的線程在不同的時間訪問到的數據是相同)。

public class MyThreadTwo extends Thread {
    private static int count = 5;
    
    public MyThreadTwo(String name){
        super();
        this.setName(name);
    }
    
    @Override
    public void run() {
        super.run();
        count--;
        System.out.println("由"+Thread.currentThread().getName()+"線程計算count的結果是:"+count);
    }
}

      測試結果
由B線程計算count的結果是:3
由C線程計算count的結果是:2
由A線程計算count的結果是:3
由E線程計算count的結果是:1
由D線程計算count的結果是:0

     

       還有一種共享數據的方法,在創建線程對象時,使用下面的構造方法

		MyThreadOne mThread = new MyThreadOne();
		Thread t1 = new Thread(mThread,"A");
		Thread t2 = new Thread(mThread,"B");
		Thread t3 = new Thread(mThread,"C");
		Thread t4 = new Thread(mThread,"D");
		Thread t5 = new Thread(mThread,"E");

       MyThreadOne的代碼
public class MyThreadOne extends Thread{
	
	private int count = 5;
	
	@Override
	public void run() {
		super.run();
		count--;
		System.out.println("由"+Thread.currentThread().getName()+"線程計算count的結果是:"+count);
	}
}

       測試結果
由A線程計算count的結果是:4
由B線程計算count的結果是:2
由C線程計算count的結果是:2
由D線程計算count的結果是:0
由E線程計算count的結果是:1

       同樣會出現併發問題

       解決多線程併發問題

       使用synchronized關鍵字加鎖

       使用第一種方式共享數據,解決併發問題,需要使用同步代碼塊,這裏要鎖的對象必須是唯一的。

		synchronized (MyThreadTwo.class) {
			count--;
			System.out.println("由" + Thread.currentThread().getName() + "線程計算count的結果是:" + count);
		}

        測試結果
由A線程計算count的結果是:4
由E線程計算count的結果是:3
由D線程計算count的結果是:2
由B線程計算count的結果是:1
由C線程計算count的結果是:0

        使用第二種方式共享數,解決併發問題,可以在run方法前面加上synchronized關鍵字進行加鎖操作。
	@Override
	synchronized public void run() {
		super.run();
		count--;
		System.out.println("由"+Thread.currentThread().getName()+"線程計算count的結果是:"+count);
	}

       3、線程的幾個常用的方法

          3.1、靜態方法currentThread()。

                  獲取當前程序所在的線程對象。

          3.2、isAlive()

                  判斷線程是否是活躍狀態。

                  活動狀態:已經啓動且尚未結束。

          3.3、靜態方法sleep(long time).

                  讓當前運行的線程休眠time毫秒

          3.4、getId()

                  獲取線程的唯一標識。

        4、停止線程

             4.1、停止線程的幾種方式    

                  1、線程正常停止,即run()方法中的程序執行完畢。 

                  2、使用stop()方法,強制線程停止,但不安全,不推薦使用。

                      使用stop()停止線程的弊端:

                      (1)、可能使一些釋放資源的 工作得不到完成 。

                      (2)、對鎖的對象解鎖,導致數據得不到同步的處理。

                  3、使用interrupt()方法中斷線程。

                     當使用該方法停止線程時,如果進入了停止狀態,可以拋一個InterruptedException異常,達到停止線程的目的。

                     當線程在休眠狀態時,調用該方法,會進入catch語句,並會清除停止狀態。

                     當線程在運行狀態時,調用該方法,是不會進入catch語句。

            4.2、判斷線程是否是停止狀態

                interrupted()線程的靜態方法,測試當前線程是否已經中斷,線程的中斷狀態由該方法清除。

                isInterrupted(),測試線程是否已經中斷,不清除中斷狀態。

  5、線程的暫停與恢復(已過時,不推薦使用)
              5.1、暫停線程的方法 suspend();
              5.2、使用它的弊端有,當線程暫停時,是不釋放鎖的,導致別的線程不能訪問公共同步對象;還有容易造成線程不同步的現象。
         6、線程禮讓
             6.1、禮讓線程的方法 yield();主動放棄CPU資源,讓其它線程得以運行。
         7、線程的優先級
             7.1、設置線程優先級的方法 setPriority(int priority);
             7.2、獲取線程優先級的方法 getPriority();
             7.3、線程優先級的取值範圍:1~10;
             7.4、線程優先級常量:
                     最低優先級:Thread.MIN_PRIORITY 1
                     常規優先級:Thread.NORM_PRIORITY 5
                     最高優先級:Thread.MAX_PRIORITY 10
             7.5、在A線程裏啓動B線程,則B線程的優先級與A線程的優先級相同;
             7.6、線程的默認優先級是Thread.NORM_PRIORITY;
             7.7、優先級具體規則性:CPU儘量的把資源分配給優先級高的線程。
             7.8、優先級具有隨機性:並不一定是優先級高的線程先執行完。

       8、守護線程

            8.1、調用線程的serDecmon(boolean b)方法,可以設置該線程是不是守護線程。

            8.2、當進行中不存在非守護線程時,守護線程也將銷燬。

       9、給方法加關鍵字synchronized同步

          9.1、方法中的私有變量是不存在線程安全問題

          9.2、多個線程訪問同一對象的成員變量纔會出現線程安全問題

          9.3、在方法加鎖,鎖的是對象,所以不同線程訪問不同對象的成員變量,仍然是異步。

          9.4、A線程已經持有object對象的鎖了,B線程可以調用object對象中的非同步方法。

          9.5、當線程已經持有object對象的鎖了,可以繼續調用該對象中的其他同步方法。

          9.6、當一個線程持有object對象的鎖時,另外一線程想訪問該對象的另外一個同步方法時,必須等待第一個線程釋放鎖之後,才能訪問。

          9.7、當線程已經持有object對象的鎖時,再訪問其他該對象的同步方法時,是不需要等待該對象釋放鎖的。

          9.8、當一個線程執行代碼發生異常時,它所持有的鎖也會釋放掉。

          9.9、同步不具有繼承性的,如果子類需要同步,必須自己加上synchronized關鍵字。

        10、使用關鍵字synchronized同步代碼塊

          10.1、同步方法的缺點:當方法有耗時操作時,其它線程必須等待當前線程執行完畢之後釋放鎖之後才能執行,比較浪費時間。

          10.2、在synchronized(){}內的代碼同步,其它的就不同步。

          10.3、使用同一對象的鎖,它們之間是同步的。

          10.4、使用synchronized(this)時,鎖對象是當前對象。

          10.5、鎖對象不同,代碼塊之間是異步的。

          10.6、同步代碼塊在非同步方法中調用,並不能保證調用非同步方法的線程同步。

          10.7、當關鍵字synchronized加在靜態方法上的時候,鎖對象是該類所對應的Class類。

          10.8、使用synchronized(MyObject.class){}同步代碼塊,與使用關鍵字synchronized同步靜態方法是一樣的。

          10.9、儘量避免使用String的對象加鎖,因爲String有個常量池功能,String str1="A",String str2 = "A"。str1與str2是同一對象。

          10.10、互相等待對方釋放鎖,就有可能出現死鎖情況。

         11、關鍵字volatile

           11.1、關鍵字volatile主要作用是使變量在多線程之間可見。沒用關鍵字volatile修飾之前,變量是在私有堆棧與公有堆棧中,但是它們之間是不同步的;使用了關鍵字volatile修飾,程序會強制從公有堆棧中取值。

           11.2、使用關鍵字volatile的缺點是:不支持原子性。

           11.3、線程安全包含原子性和可見性。

           11.4、使用原子類進行原則操作,AtomicInteger、AtomicLong...

          12、線程間通信。

            12.1、等待通知機制,wait()方法與notify()方法只能在同步方法或同步代碼塊中調用,wait()方法調用之後會立即釋放鎖,notify()方法調用之後不會立即釋放鎖 。

            12.2、線程的生命週期,就緒狀態、可運行狀態、運行狀態、暫停狀態、銷燬狀態;就緒狀態:線程創建之後進入就緒狀態;可運行狀態:線程調用了start()方法,但未搶到CPU資源進入可運行狀態;運行狀態:線程在可運行狀態下,搶到了CPU資源進入運行狀態;暫停狀態:線程在運行狀態下,調用了suspend()/sleep()/wait()方法進入暫停狀態;銷燬狀態:當線程運行完run()方法中的代碼後進入銷燬狀態。

            12.3、鎖對象有兩個隊列,就緒隊列與阻塞隊列;線程調用了wait()方法,進入阻塞隊列,線程被喚醒後,進入就緒隊列;就緒隊列中存放了將要獲得鎖的線程,阻塞隊列存放了被阻塞的線程。

            12.4、喚醒所有線程notifyAll();等待N毫秒之後自動喚醒方法wait(long N),當然也可以在這之前調用notify()或notifyAll()方法喚醒。

            12.5、wait()條件發生變化時,應該將if判斷改爲while判斷,再判斷一次,看條件是否發生變化。

            12.6、多個生產者或者多個消費者,出現“假死”狀態(不停的喚醒同類),將notity()方法改爲notifyAll()。

            12.7、可以通過管道實現線程間通信;

                     字節流:

                     PipedInputStream pis = new PipedInputStream();

                     PipedOutputStream pos = new PipedOutputStream();

                     通過pis.connect(pos)或者pos.connect(pis)連接通道;

                     字符流:

                     PipedReader pr = new PipedReader();

                     PipedWriter pw = new PipedWriter();

                     連接pr.connect(pw)或者pw.connect(pr);

                     將這兩個流分別傳入兩個線程中,一個用來讀,一個用來寫,從而實現線程間通信。

          13、線程加入join()

            13.1、使線程正常執行run裏面的代碼,無限阻塞當前線程知道run方法執行完畢。 

            13.2、有個重載的join(long time)方法,阻塞時間有限制,如果到了阻塞時間且線程還未運行完,會執行後邊的代碼。

            13.3、方法join(long time)與sleep(long time)的區別,join(long time)內部使用的是wait(long time),所以join(long time)方法釋放鎖,而sleep(long time)不釋放鎖。

          14、類ThreadLocal

            14.1、與線程綁定,存儲綁定線程的私有值。

            14.2、具有線程變量的隔離性。

            14.3、第一次使用該類對象的get方法獲取值返回都爲null。

            14.4、解決第一次get總爲null問題,自定義一個類繼承ThreadLocal,重寫initialValue(),返回一個任意對象,則它就爲默認獲取到的對象。

          15、類InheritableThreadLocal

            15.1、可以在子線程中繼承父線程中的值。

            15.2、繼承父線程中的值且修改。繼承同14.4,修改重寫childValue()方法,返回parentValue+要添加的值。

          16、類Lock

            16.1、使用方式:

                      創建對象   Lock  lock = new ReentrantLock();

                      上鎖          lock.lock();

                      同步代碼塊;

                      釋放鎖      lock.unlock();

            16.2、類Condition,等待與喚醒

                  使用Condition讓線程等待

                      創建Lock對象   Lock  lock = new ReentrantLock();

                      創建Condition對象 Condition condition = new Condition();

                      上鎖          lock.lock();

                      同步代碼塊;

                      等待          condition.await();

                      同步代碼塊;

                      釋放鎖      lock.unlock();

                  使用Condition喚醒正在等待的線程。

                      將上邊的代碼中的

                      等待          condition.await();  替換爲

                      喚醒          condition.signal();

                  與Object的方法進行對比

                      Object類的wait()方法相當於Condition類的await()方法;

                      Object類的wait(long t)方法相當於Condition類的await(long t)方法;

                      Object類的notify()方法相當於Condition類的signal()方法;

                     Object類的notifyAll()方法相當於Condition類的signalAll()方法;

               16.3、公平鎖與非公平鎖

                    構造方法 ReentrantLock(boolean fair),參數就是表示是否是公平鎖。

                    公平鎖:誰先運行誰先獲得鎖

                    非公平鎖:隨機獲得鎖

               16.4、ReentrantLock幾個常用的方法

                    getHoldCount():查詢當前線程持有該鎖的個數;

                    getQueueLength():獲取等待該鎖的個數;

                    getWaitQueueLenght():獲取等待喚醒線程的個數;

                    hasQueueThread():查詢指定線程是否等待獲取該鎖;

                    hasQueueThreads():查詢是否有線程等待獲取該鎖;

                    hasWaiters():查詢是否有線程等待喚醒;

                    isFair():判斷是不是公平鎖;

                    isHeldByThread():查詢當前線程是否持有此鎖;

                    isLocked():查詢此鎖是否被任意線程鎖持有;

                    lockInterruptibly():如果當前線程未被中斷,則獲取該鎖,如果中斷則拋出異常。

                    tryLock():在沒有線程持有該鎖的情況下,獲得該鎖。

                    tryLock(long timeout,TimeUnit tu):如果在給定時間內沒有被其它線程持有該鎖且沒有被中斷,則獲取該鎖;參1:時長、參2:單位(枚舉值)。

             16.5、Condition常用的幾個方法

                    awaitUninterruptibly():在不管什麼情況下,讓線程在接收到喚醒之前一直處於等待狀態。

                    awaitUntial(Date date):在時間到來之時,自動喚醒,也可以在時間到來之前,主動喚醒。

             16.6、類ReenrantReadWriteLock(讀寫鎖)

                  讀寫鎖有兩個,一個是讀鎖也可叫共享鎖,一個是寫鎖也叫排他鎖。


                  讀鎖的使用:

                  創建對象 ReenrantReadWriteLock lock = ReenrantReadWriteLock();

                  獲取鎖     lock.readLock.lock();

                  要鎖的代碼塊

                  釋放讀鎖  lock.readLock.unlock();

                  讀鎖與讀鎖之間是不互斥的。


                  寫鎖的使用:

                  創建對象 ReenrantReadWriteLock lock = ReenrantReadWriteLock();

                  獲取鎖     lock.writeLock.lock();

                  要鎖的代碼塊

                  釋放讀鎖  lock.writeLock.unlock();

                  寫鎖與寫鎖之間是互斥的。

                  讀鎖與寫鎖之間是互斥的。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章