黑馬程序員--Java基礎學習筆記【序列化、多線程】

 

 ------Java培訓、Android培訓、iOS培訓、.Net培訓、期待與您交流! -------   

 

  • 序列化流與反序列化流

    ObjectOutputStream 對象輸出流

    writeObject(Object obj) 可對參數指定的obj對象進行序列化,把得到的字節序列寫到一個目標輸出流中

ObjectInputStream對象輸入流

readObject(Objectobj) 從源輸入流中讀取字節序列,反序列化爲一個對象並返回

    序列化:將數據分解成字節流,以便存儲在文件中或在網絡上傳輸

    反序列化:打開字節流並重構對象

    主要用途:把對象的字節序列永久地保存到硬盤上,通常放在一個文件中;

        在網絡上傳送對象的字節序列。

    序列化的實現:將需要被序列化的類實現java.io.Serializable 接口(標記型接口,沒有抽象方法),該接口沒有需要實現的方法,implementsSerializable 只是爲了標註該對象是可被序列化的。然後使用一個輸出流(如FileOutputStream )來構造一個ObjectOutputStream(對象輸出流)對象,接着,使用 ObjectOutputStream 對象的 writeObject(Objectobj)方法就可以將參數爲obj的對象寫出,要恢復的話則用輸入流。

  • 對象序列化的步驟:

  • 創建一個對象輸出流

  • 通過對象輸出流的 writeObject()方法寫對象

對象序列化的特性:

1.static 成員不屬於對象,屬於自己的類,靜態是對象共享數據,靜態無法序列化

2.設計線程的類不能序列化

3.爲保證序列化安全,可將敏感數據標記爲 transient ,阻止成員變量序列化

表示序列化版本標識符的靜態變量 serialVersionUID

Java運行時環境根據類的內部細節自動生成的

顯示定義 serialVersionUID 的主要用途:

希望類的不同版本對序列化兼容;不希望類的不同版本對序列化兼容

 

  • Properties 集合類

    繼承 Hashtable,實現 Map 接口

    String getProperty(String key) // 用指定的鍵在此屬性列表中搜索屬性

    Object setProperty(String key, String value) // 掉用Hashtable 的方法put

    Set<String> stringPropertyNames() // 返回此屬性列表中的鍵集

 

    可以和IO流結合使用

    public void load(Reader reader)

// 傳遞字節輸入流或字符輸入流,從流中直接加載鍵值對

    public void store(Writer writer, String comments)

    // 將集合中的鍵值對,通過流保存迴文件中

 

       // 創建字節輸入流

       FileInputStreamfis= newFileInputStream("C:\\properties.txt");

       // 創建對象

       Propertiespro= newProperties();

       // 從輸入流中加載鍵值對

       pro.load(fis);

       // 關閉輸入流

       fis.close();

 

       // 判斷鍵是否存在

       if (pro.getProperty("lisi") != null) {

           // 創建字節輸出流

           FileOutputStreamfos= newFileOutputStream("C:\\properties.txt");

           // 設置鍵對應的值

           pro.setProperty("lisi", "100");

 

           // 將鍵值對重新保存迴文件

           pro.store(fos, "");

           // 關閉輸出流

           fos.close();

 

 

  • 多線程

    進程是OS 中運行的一個任務,一個應用程序運行在一個進程中。

    進程中所包含的一個或多個執行單元稱爲線程。一個線程是進程的一個順序執行流。

    多線程是一種機制,它允許在程序中併發執行多個指令流,每個指令流都成爲一個線程,彼此間互相獨立。

 

  • 實現多線程的三種方式:

  • 繼承 java.lang.Thread

在創建Thread類的子類中重寫 run() 方法,加入線程所要執行的代碼

new 創建一個線程子類的實例出來,並調用 start() 方法啓動線程

2. 實現 java.lang.Runnable 接口

聲明自己的類實現 Runnable 接口並實現接口中的run()方法,將線程代碼寫入其中

(好處:將線程與線程要執行的任務分離開減少耦合,適合讓線程對象中的數據共享

Java是單繼承的,定義一個類實現Runnable接口可以避免由於單繼承帶來的侷限性

    3. 實現java.util.concurrent.Callable<V> 接口

    聲明自己的類實現 Callable 接口並實現接口中的call()方法,將線程代碼寫入其中

    調用線程池的靜態方法獲取線程池對象

線程池管理對象提交一個接口的實現類對象用於執行

獲取到線程運行結束後的返回值結果Future

調用返回值結果對象的get方法獲取結果

    (可以有返回值,可以拋出異常,但代碼比較複雜,一般不用)

-----------------------extends Thread 實現多線程----------------------

// 獲取當前線程名稱

Thread.currentThread().getName()

// 定義線程子類繼承Thread,重寫run()方法

class MyThread extends Thread {

    /*

     * 如果想要在線程子類創建對象時傳遞參數設置線程名,

     * 必須重寫父類Thread類中的帶參數的構造方法,super調用

     */

    public MyThread(String string) {

       super(string);

    }

 

    @Override

    publicvoid run() {

       for (inti = 0; i < 50; i++) {

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

       }

    }

}

 

        // 創建線程子類的對象

        MyThread myThread = new MyThread("呵呵");

        // 設置線程名稱

        //myThread.setName("myThread");

        MyThread myThread2 = new MyThread("呵呵2");

        //myThread2.setName("myThread2");

        MyThread myThread3 = new MyThread("呵呵3");

        //myThread3.setName("myThread3");

      

        // 調用 start 方法啓動線程

        myThread.start();

        // 不能多次啓動同一線程

//     myThread.start(); // java.lang.IllegalThreadStateException

        myThread2.start();

        myThread3.start();

 

--------------------implements Runnable 實現多線程--------------------

// 聲明類實現Runnable接口,實現run()方法

class MyRunnable implements Runnable {

    @Override

    publicvoid run() {

       for (inti = 0; i < 50; i++) {

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

       }

    }

}

 

        // 創建實現類對象

        MyRunnable target = new MyRunnable();

      

        // 創建線程對象傳遞實現類對象

        // 構造方法的參數可設置線程名

        Thread thread = new Thread(target, "thread");

        Thread thread2 = new Thread(target, "thread2");

        Thread thread3 = new Thread(target, "thread3");

      

        // 調用 start 方法啓動線程

        thread.start();

        thread2.start();

        thread3.start();

 

-------------------implements Callable<V> 實現多線程------------------

// 聲明類實現Callable接口,實現call()方法

class MyCallable implements Callable<String> {

    @Override

    public String call() throws Exception {

       for (inti = 0; i < 50; i++) {

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

       }

       returnnull;

    }

}

 

       // Executors類的靜態方法獲取線程池對象

       // 創建一個使用單個工作線程的Executor(線程池)

       ExecutorServicees = Executors.newSingleThreadExecutor();

       // 創建一個可重用固定線程數的線程池

//     ExecutorServicees2 = Executors.newFixedThreadPool(2);

       // 創建一個可根據需要創建新線程的線程池

//     ExecutorServicees3 = Executors.newCachedThreadPool();

      

       // 線程管理對象,提交一個返回值的任務(接口的實現類對象)用於執行

       // 返回一個表示任務的未決結果的Future(異步計算的結果)

       Future<String>future= es.submit(new MyCallable());

//     Future<String>future = es2.submit(new MyCallable());

//     Future<String>future2 = es2.submit(new MyCallable());

      

       // 調用Future接口的get方法獲取結果

       Stringstring= future.get();

//     Stringstring2 = future2.get();

      

       System.out.println(string);

--------------------------實現多線程的三種方式------------------------

線程調度

    Java 使用的是搶佔式調度模型,優先級高的線程先執行

    設置和獲取線程優先級,線程的優先級有繼承關係

    public final intgetPriority()

    public finalsetPriority(int newPriority)

    優先級的範圍:110,低 -->

    static intMAX_PRIORITY   最高優先級10

    static intMIN_PRIORITY   最低優先級1

    static intNORM_PRIORITY  默認優先級5

    主線程的默認優先級爲Thread.NORM_PRIORITY

 

常用線程操作 API

    Thread Thread.currentThread() // 獲取運行當前代碼片段的線程

    long getId() // 返回該線程的標識符

    String getName() // 返回該線程的名稱

    Thread.currentThread().getName() // 獲取當前線程名稱

    void setName(String name) // 設置線程名稱

    Thread(String name) // 構造方法,創建線程時設置名稱

    int getPriority() // 獲取線程優先級

    void setPriority(int priority) // 設置線程優先級

    boolean isDaemon() // 判斷是否爲守護線程

    void setDaemon(boolean b) // 當參數爲 true 設置爲守護線程

    (守護線程:當進程中只剩下守護線程時,所有守護線程強制終止。)

    static void sleep(long ms) // 使線程進入阻塞狀態指定毫秒值

    static void yield() // 主動退讓當次 CPU 時間片

    void join() // 等待當前線程結束

 

線程的5種狀態

阻塞狀態的進入方式及對應的恢復方法:

進入              恢復

睡眠(sleep)           自動恢復

掛起(suspend)     恢復(resume)

等待(wait)        通知(notify)

說明: 1335150456_1151

 

  • 線程同步

    多個線程併發讀寫同一個臨界資源時會發生“線程併發安全問題”。

    常見的臨界資源:多線程共享實例變量,多線程共享靜態公共變量。

    若想解決線程安全問題,需要將異步的操作變爲同步操作。

  • 同步代碼塊

    同步代碼塊包含兩部分:一個作爲鎖的對象的引用,一個作爲由這個鎖保護的代碼塊。

    synchronized(同步監視器——鎖對象引用) {

       // 需要同步的代碼塊

}

若方法所有代碼都需要同步也可以給方法直接加鎖,就成爲同步方法。

每個Java對象都可以用做一個實現同步的鎖,線程進入同步代碼塊之前會自動獲得鎖,並且在退出同步代碼塊時自動釋放鎖,而且無論是通過正常途徑退出還是通過異常退出都一樣,獲得內置鎖的唯一途徑就是進入由這個鎖保護的同步代碼塊和或方法。

選擇合適的鎖對象:使用 synchronized 需要對一個對象上鎖以保證線程同步。那麼這個鎖對象應當注意:

    -- 多個需要同步的線程在訪問該同步塊時,看到的應該是同一個鎖對象引用

    -- 通常使用 this 來作爲鎖對象

    選擇合適的鎖範圍:在使用同步塊時,應當儘量在允許的情況下減少同步範圍,以提高併發的執行效率。

 

  • 同步方法

    靜態方法鎖:publicsynchronized static void xxx() {…}

    該方法鎖的對象是類對象。每個類都有唯一的一個類對象:類名.class

    如果靜態方法與非靜態方法同時聲明瞭synchronized,他們之間是非互斥關係的。原因在於,靜態方法鎖的是類對象,而非靜態方法鎖的是當前方法所屬對象

    鎖對象的選擇:

    如果鎖對象是 this,就可以考慮使用同步方法;

    否則能使用同步代碼塊的儘量使用同步代碼塊。

 

Lock 接口java.util.concurrent.locks.Lock

    since JDK1.5 替換同步技術,接口的實現類是ReentrantLock(可重入、互斥的鎖)

synchronized Lock 的異同:

相同點:都可以實現線程同步

不同點:Lock 更精確的線程語義和更好的性能。

    synchronized 會自動釋放鎖,而 Lock 需要手工在finally語句中釋放。

 

  • 死鎖問題與線程間通信

    死鎖問題:兩個或多個線程在執行過程中因爭奪資源產生的一種互相等待的現象。

--------------------------同步代碼塊的嵌套案例---------------------------

package cn.itcast;

 

/*

 * 線程死鎖案例

 *  多個線程,爭奪同一個鎖(相同的資源),出現相互等待的現象

 */

publicclassThreadDeadDemo {

 

    publicstaticvoid main(String[] args) {

       RunnableDeadd1 = new RunnableDead(true);

       RunnableDeadd2 = new RunnableDead(false);

 

       Threadt1 = new Thread(d1);

       Threadt2 = new Thread(d2);

 

       t1.start();

       t2.start();

    }

 

}

 

class RunnableDead implements Runnable {

 

    privatebooleanb; // 設置標誌位

 

    public RunnableDead(booleanb) {

       this.b = b;

    }

 

    @Override

    publicvoid run() {

       while (true) {

           if (b) { // b==true,線程先獲取A鎖,同步代碼塊的鎖是LockA對象

              synchronized (LockA.locka) {

                  System.out.println("if...locka");

                  // 線程沒有出去A鎖,進入B鎖,同步代碼塊的鎖是LockB對象

                  synchronized (LockB.lockb) {

                     System.out.println("if...lockb");

                  }

              }

           }else{ // b==false,線程先獲取B鎖,同步代碼塊的鎖是LockB對象

              synchronized (LockB.lockb) {

                  System.out.println("else...lockb");

                  // 線程沒有出去B鎖,進入A鎖,同步代碼塊的鎖是LockA對象

                  synchronized (LockA.locka) {

                     System.out.println("else...locka");

                  }

              }

           }

       }// endwhile

    }// endrun

 

}

 

// 創建兩個對象鎖,且都是唯一的,只能通過類的靜態成員獲取對象當鎖

class LockA {

    publicstaticfinal LockA locka = new LockA();

 

    private LockA() {

    }

}

 

class LockB {

    publicstaticfinal LockB lockb = new LockB();

 

    private LockB() {

    }

}

線程間通信:通過設置線程(生產者)和獲取線程(消費者)針對同一個對象進行操作

wait notify :多線程之間需要協調工作。如果條件不滿足,則等待。當條件滿足時,等待該條件的線程將被喚醒。在Java中,這個機制的實現依賴於 wait/notify。等待機制與鎖機制是密切聯繫的。

IllegalMonitorStateException無效的監視器狀態異常

執行方法wait/notify時,缺少所對象,沒有鎖的情況下,不能使用等待遇喚醒機制,必須有鎖的支持才能使用。無論等待還是喚醒,都必須在本鎖內操作,喚醒必須同一把鎖的線程,只能作爲鎖的對象,才能調用方法 wait notify

線程間通信的代碼改進:

---------------------通過等待喚醒機制實現數據依次出現---------------------

package cn.itcast;

 

/*

 * 線程間通信

 * 通過等待喚醒機制和設置修改標誌位實現數據依次出現

 */

publicclassThreadComnDemo {

 

    publicstaticvoid main(String[] args) {

       Students = new Student();

       Producerp = new Producer(s);

       Consumerc = new Consumer(s);

 

       new Thread(p).start();

       new Thread(c).start();

    }

 

}

 

class Student {

    Stringname;

    Stringsex;

    booleanflag;

}

 

class Producer implements Runnable {

 

    private Student s;

 

    public Producer(Student s) {

       this.s = s;

    }

 

    @Override

    publicvoid run() {

       intx = 0;

       while (true) {

           synchronized (s) {

              // 對成員標記判斷,如果true,賦值完成,等待

              if (s.flag){

                  try {

                     s.wait();

                  }catch(InterruptedException e) {

                     e.printStackTrace();

                  }

              }

              if (x % 2 == 0) {

                  s.name = "張三";

                  s.sex = "";

              }else{

                  s.name = "李四";

                  s.sex = "";

              }

              x++;

              // 改標記

              s.flag = true;

              // 喚醒對方線程

              s.notify();

           }

       }

    }

}

 

class Consumer implements Runnable {

 

    private Student s;

 

    public Consumer(Student s) {

       this.s = s;

    }

 

    @Override

    publicvoid run() {

       while (true) {

           synchronized (s) {

              // 對成員進行判斷,如果false,打印完成,等待

              if (!s.flag) {

                  try {

                     s.wait();

                  }catch(InterruptedException e) {

                     e.printStackTrace();

                  }

              }

              System.out.println(s.name + " " + s.sex);

              // 改標記

              s.flag = false;

              // 喚醒對方線程

              s.notify();

           }

       }

    }

 

}

 

----------------------把同步代碼塊改進爲同步方法實現----------------------

package cn.itcast;

 

/*

 * 線程間通信

 * 同步技術改進,把同步代碼塊改進爲同步方法實現

 */

publicclassThreadComnDemo2 {

 

    publicstaticvoid main(String[] args) {

       Students = new Student();

 

       new Thread(new Producer(s)).start();

       new Thread(new Consumer(s)).start();

    }

 

}

 

class Student {

    private String name;

    private String sex;

    privatebooleanflag; // 設置標誌位

 

    // 對私有成員,提供公共訪問方式,對2個成員變量進行賦值

    publicsynchronizedvoid set(String name, String sex) {

       // 判斷標記,如果true,等待,不能賦值

       if (flag) {

           try {

              this.wait();

           }catch(InterruptedException e) {

              e.printStackTrace();

           }

       }

       this.name = name;

       this.sex = sex;

      

       flag = true;

       this.notify();

    }

 

    publicsynchronizedvoid get() {

       // 判斷標記,如果false,等待,不能打印

       if (!flag) {

           try {

              this.wait();

           }catch(InterruptedException e) {

              e.printStackTrace();

           }

       }

       System.out.println(name + " " + sex);

      

       flag = false;

       this.notify();

      

    }

}

 

// 生產者線程

class Producer implements Runnable {

 

    private Student s;

 

    public Producer(Student s) {

       this.s = s;

    }

 

    @Override

    publicvoid run() {

       intx = 0;

       while (true) {

           if (x % 2 == 0) {

              s.set("張三", "");

           }else{

              s.set("李四", "");

           }

           x++;

       }

    }

}

 

// 消費者線程

class Consumer implements Runnable {

 

    private Student s;

 

    public Consumer(Student s) {

       this.s = s;

    }

 

    @Override

    publicvoid run() {

       while (true) {

           s.get();

       }

    }

 

}

 

-----------------------匿名內部類方式實現多線程-----------------------

       new Thread(new Runnable() {

           publicvoid run() {

              System.out.println("匿名內部類實現多線程");

           }

       }).start();

 

       new Thread() {

           publicvoid run() {

              System.out.println("多線程匿名內部類");

           };

       }.start();

 

  • 啓動一個線程是用run()還是start()

    調用start()方法,使線程所代表的虛擬處理機處於可運行狀態,這意味着它可以由JVM調度並執行,但並不意味着線程就會立即執行。

    run()方法可以產生必須退出的標誌來停止一個線程。

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

    sleep方法是Thread類的靜態方法,導致此線程暫停執行指定時間,把執行機會給其它線程,但是監控狀態依然保持,到時後會自動恢復。調用sleep不會釋放對象鎖

    wait方法是Object類的非靜態方法,對象調用 wait 方法導致本線程放棄對象鎖,進入等待此對象的等待鎖定池,只有針對此對象發出notify方法後本線程才進入對象鎖定池準備獲得對象鎖進入運行狀態。

  • 爲什麼 wait(),notify(), notifyAll()等方法都定義在 Object類中?

    線程控制方法不定義在 Thread 類中,原因是鎖的問題造成的。

鎖肯定是不確定的對象,而線程控制方法必須是鎖對象才能調用,因此線程控制方法定義在 Object類中,保證所有對象都可以調用。

 


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