文章出處

一、線程同步

1、線程同步的目的是為了保護多個線程訪問一個資源時對資源的破壞。
2、線程同步方法是通過鎖來實現,每個對象都有切僅有一個鎖,這個鎖與一個特定的對象關聯,線程一旦獲取了對象鎖,其他訪問該對象的線程就無法再訪問該對象的其他同步方法。
二、實現同步機制的兩個方法
1。同步代碼塊: 
synchronized(同一個數據){} 同一個數據:就是N條線程同時訪問一個數據。 
2。 
同步方法: 
public synchronized 數據返回類型 方法名(){} 
就是使用 synchronized 來修飾某個方法,則該方法稱為同步方法。對于同步方法而言,無需顯示指定同步監視器,同步方法的同步監視器是 this 也就是該對象的本身(這里指的對象本身有點含糊,其實就是調用該同步方法的對象)通過使用同步方法,可非常方便的將某類變成線程安全的類,具有如下特征: 
1,該類的對象可以被多個線程安全的訪問。 
2,每個線程調用該對象的任意方法之后,都將得到正確的結果。 
3,每個線程調用該對象的任意方法之后,該對象狀態依然保持合理狀態。 
注:synchronized關鍵字可以修飾方法,也可以修飾代碼塊,但不能修飾構造器,屬性等。 
實現同步機制注意以下幾點: 安全性高,性能低,在多線程用。性能高,安全性低,在單線程用。 
 
關鍵代碼如下:
編寫打印機類:Printer  定義兩個方法
package cn.d.happy;

public class Printer {

    Object o=new Object();
    
    //或在void前加synchronized
    public void print1(){
        //同步代碼塊
         synchronized (o){
        System.out.print("線");
        System.out.print("程");
        System.out.print("同");
        System.out.print("步");
        System.out.println();
    }
    }
    public void print2(){
        synchronized (o){
        System.out.print("噢");
        System.out.print("呵");
        System.out.println();
    }
    }
}

定義兩個線程類 并重寫run方法。繼承Thread 和 實現Runnable接口 通過for循環遍歷次數

package cn.d.happy;

public class MyThread extends Thread{

    public Printer print;
    
    @Override
public void run() {
    //必須有該類的對象實例
    for (int i = 1; i <=10; i++) {
        print.print1();
    }
}
}
package cn.d.happy;

public class MyThread2 implements Runnable{
    public  Printer print;
    @Override
    public void run() {
         for (int i = 1; i <=10; i++) {
             print.print2();
        }
        
    }

}

測試類 創建打印機對象  以及兩個線程對象并進行賦值

package cn.d.happy;

public class Test {
public static void main(String[] args) {
    //購買一個打印機
    Printer p=new Printer();
    
    //創建第一個線程對象  并且給屬性賦值
    MyThread t1=new MyThread();
    t1.print=p;
    t1.start();
    
    
    //03.創建第二個線程對象 并且給屬性賦值
      MyThread2 t2=new MyThread2();
      t2.print=p;
      Thread tt=new Thread(t2);
      tt.start();
}
}

實現效果:


 

三、Java多線程之yield()、wait()、Notify()、Notifyall()

yield()、

1)    通過yield ()函數,可使線程進入可執行狀態,排程器從可執行狀態的線程中重新進行排程。所以調用了yield()的函數也有可能馬上被執行。
2)    當調用yield ()函數后,線程不會釋放它的“鎖標志”。

class TestThreadMethod extends Thread{
        public static int shareVar = 0;
        public TestThreadMethod(String name){
            super(name);
        }
        public synchronized void run(){
            for(int i=0; i<4; i++){
                System.out.print(Thread.currentThread().getName());
                System.out.println(" : " + i);
                Thread.yield();
            }
        }
    }
    public class TestThread{
        public static void main(String[] args){
            TestThreadMethod t1 = new TestThreadMethod("t1");
            TestThreadMethod t2 = new TestThreadMethod("t2");
            t1.start();
            t1.start(); //(1)
            //t2.start(); (2)
        }

運行結果為:
t1 : 0
t1 : 1
t1 : 2
t1 : 3
t1 : 0
t1 : 1
t1 : 2
t1 : 3
從結果可知調用yield()時并不會釋放對象的“鎖標志”。
如果把代碼(1)注釋掉,并去掉代碼(2)的注釋,結果為:
t1 : 0
t1 : 1
t2 : 0
t1 : 2
t2 : 1
t1 : 3
t2 : 2
t2 : 3
從結果可知,雖然t1線程調用了yield(),但它馬上又被執行了。 

 wait與notify是java同步機制中重要的組成部分。結合與synchronized關鍵字使用,可以建立很多優秀的同步模型。
 synchronized(this){ }等價于publicsynchronized void method(){.....}
 同步分為類級別和對象級別,分別對應著類鎖和對象鎖。類鎖是每個類只有一個,如果static的方法被synchronized關鍵字修飾,則在這個方法被執行前必須獲得類鎖;對象鎖類同。
   首先,調用一個Object的wait與notify/notifyAll的時候,必須保證調用代碼對該Object是同步的,也就是說必須在作用等同于synchronized(obj){......}的內部才能夠去調用obj的wait與notify/notifyAll三個方法,否則就會報錯:
  java.lang.IllegalMonitorStateException:current thread not owner
  在調用wait的時候,線程自動釋放其占有的對象鎖,同時不會去申請對象鎖。當線程被喚醒的時候,它才再次獲得了去獲得對象鎖的權利。
  所以,notify與notifyAll沒有太多的區別,只是notify僅喚醒一個線程并允許它去獲得鎖,notifyAll是喚醒所有等待這個對象的線程并允許它們去獲得對象鎖,只要是在synchronied塊中的代碼,沒有對象鎖是寸步難行的。其實喚醒一個線程就是重新允許這個線程去獲得對象鎖并向下運行。

   notifyAll,雖然是對每個wait的對象都調用一次notify,但是這個還是有順序的,每個對象都保存這一個等待對象鏈,調用的順序就是這個鏈的順序。其實啟動等待對象鏈中各個線程的也是一個線程,在具體應用的時候,需要注意一下。

  wait(),notify(),notifyAll()不屬于Thread類,而是屬于Object基礎類,也就是說每個對像都有wait(),notify(),notifyAll()的功能。因為都個對像都有鎖,鎖是每個對像的基礎,當然操作鎖的方法也是最基礎了。

wait():

等待對象的同步鎖,需要獲得該對象的同步鎖才可以調用這個方法,否則編譯可以通過,但運行時會收到一個異常:IllegalMonitorStateException。

調用任意對象的 wait() 方法導致該線程阻塞,該線程不可繼續執行,并且該對象上的鎖被釋放。

notify():

喚醒在等待該對象同步鎖的線程(只喚醒一個,如果有多個在等待),注意的是在調用此方法的時候,并不能確切的喚醒某一個等待狀態的線程,而是由JVM確定喚醒哪個線程,而且不是按優先級。

調用任意對象的notify()方法則導致因調用該對象的 wait()方法而阻塞的線程中隨機選擇的一個解除阻塞(但要等到獲得鎖后才真正可執行)。

notifyAll():

喚醒所有等待的線程,注意喚醒的是notify之前wait的線程,對于notify之后的wait線程是沒有效果的。

 

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

例如:

 

synchronized(obj) {
  while(!condition) {
  obj.wait();
  }
  obj.doSomething();
  }

 

當線程A獲得了obj鎖后,發現條件condition不滿足,無法繼續下一處理,于是線程A就wait()。
在另一線程B中,如果B更改了某些條件,使得線程A的condition條件滿足了,就可以喚醒線程A :

synchronized(obj) {
  condition = true;
  obj.notify();
  }

synchronized和wait()、notify()等的關系: 

1.有synchronized的地方不一定有wait,notify

2.有wait,notify的地方必有synchronized.這是因為wait和notify不是屬于線程類,而是每一個對象都具有的方法,而且,這兩個方法都和對象鎖有關,有鎖的地方,必有synchronized。

另外,注意一點:如果要把notify和wait方法放在一起用的話,必須先調用notify后調用wait,因為如果調用完wait,該線程就已經不是currentthread了。

 

 

 

 

 

 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

文章列表


不含病毒。www.avast.com
arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()