隨筆-159  評論-114  文章-7  trackbacks-0

          class Stack{
              
          int index=0;
              
          char data[]=new char[6];
              
          public synchronized void push(char c){
                      data[index]
          =c;
                      System.out.println(
          "Push char: "+c+" ->");
                      
                      
          try{
                          Thread.sleep(
          1000);
                      }

                      
          catch(Exception e){}
                      index
          ++;
                  System.out.println(
          "--> Push "+c+" Completed  index="+index+"   Stack is "+data[0]+" "+data[1]+" "+data[2]);
              }

              
              
          public void pop(){
                      index
          --;
              
                      
          try{
                          Thread.sleep(
          10);
                      }

                      
          catch(Exception e){}    
                      
                      
          char c=data[index];
                      data[index]
          =' ';
                      System.out.println(
          "** Pop  "+c+"**  index="+index+"   Stack is "+data[0]+" "+data[1]+" "+data[2]);    

              }

          }
               

          class PushRunner implements Runnable{
              
          private Stack s;
              
          public PushRunner(Stack s){
                  
          this.s=s;
              }

              
              
          public void run(){
                      s.push(
          'c');
              }


              
          }


          class PopRunner implements Runnable{
              
          private Stack s;
              
          public PopRunner(Stack s){
                  
          this.s=s;
              }

              
          public void run(){
                  
          synchronized(s){
                      s.pop();
                  }

              }

          }

          public class TestSynchronize{
              
          public static void main(String[] args){
                  Stack s
          =new Stack();
                  s.push(
          'a');
                  s.push(
          'b');
                  
          new Thread(new PushRunner(s)).start();
                  
          new Thread(new PopRunner(s)).start();
              }

          }

          首先,我們討論一下進程,進程是操作系統級別下,單獨執行的一個任務。

          Win32 Unix都是多任務操作系統。

          多任務,并發執行是一個宏觀概念,實際微觀串行。

          CPU同一時間刻只能執行一個任務。

          OS負責進程調度,使用CPU,也就是獲得時間片。




          在一個進程中,再可以分為多個程序順序執行流,每個執行流就是一個線程。

          分配CPU時間片的依然是CPU,多線程時,程序會變慢,每個線程分配到的時間片少了。



          進程與線程的區別,進程是數據獨占的(獨立數據空間),線程是數據共享的(這也是線程之間通訊容易的原因,不需要傳遞數據)。

          Java是語言級支持多線程的,體現在有現成的封裝類(java.lang.Thread)完成了必要的并發細節的工作(與操作系統打交道,分配PID等)。



          兩種方式來得到一個線程對象。

          public class TestThread{
              
          public static void main(String[] args)
              
          {
                  Thread t1 
          = new MyThread();
                  t1.start();
              }

          }


          class MyThread extends Thread
          {
              
          public void run()
              
          {
                  System.out.println(
          "thread run");
              }

          }


          一個線程對象--〉代表著一個線程--〉一個順序執行流(run方法)

          這個程序有兩個線程,一個是main主線程,它調用了t1.start(),這是t1線程只是就緒狀態,還沒有真正啟動線程,main主線程結束了!!t1運行。兩個線程都退出了,進程完結。

          一個進程退出,要等待進程中所有線程都退出,再退出虛擬機。

          方式2,實現java.lang.Runable接口,這是這個類的對象是一個目標對象,而不能理解為是一個線程對象。

          public class TestThread{
              
          public static void main(String[] args)
              
          {
                  Thread t1 
          = new MyThread();
                  t1.start();
                  
                  Runnable target 
          = new MyRunnable();
                  Thread t2 
          = new Thread(target);
                  t2.start();
              }

          }


          class MyThread extends Thread
          {
              
          public void run()
              
          {
                  System.out.println(
          "thread run");
              }

          }


          class MyRunnable implements Runnable
          {
              
          public void run()
              
          {
                  System.out.println(
          "runnable run");
              }

          }

          不要調用run()方法,它只是執行一下普通的方法,并不會啟動單獨的線程。

          Untitled-1.gif

          上面只是線程狀態圖。


          在某一個時間內,處于運行狀態的線程,執行代碼,注意可能多個線程多次執行代碼。

          CPU會不斷從可運行狀態線程調入運行,不會讓CPU空閑。


          Thread.sleep(1000);當前線程睡眠1秒鐘,休眠后->進入阻塞->休眠結束->回到可運行狀態

          在run(),有異常拋出,必須try{}catch(Exception e){},不能throws Exception,因為run()方法覆蓋不能拋例外。

          能進入運行狀態,只能由操作系統來調度。

          一旦sleep-〉阻塞->交出程序執行權。


          等待用戶輸入,輸入輸出設備占用CPU,處于阻塞的線程沒有機會運行,輸入完畢,重新進入可運行狀態。


          第三種進入阻塞狀態的可能。

          t1.join()調用后,運行狀態線程放出執行權,作為t1的后續線程,等待t1結束。也就是說至少得等t1線程run完畢,才可能進入運行狀態來執行,可不是說t1執行完,一定馬上就是調用t1.join()的線程馬上進入可運行行狀態。只有操作系統有權利決定誰進入運行狀態。

          join的實質就是讓兩個線程和二為一,串行。

          t1.join();執行這條語句現場是被保護起來的。t1結束,調用線程有機會運行時,會從上次的位置繼續運行。

          public class TestThread{
              
          public static void main(String[] args)
              
          {
                  Thread t1 
          = new MyThread();
                  t1.start();
                  
                  
          for(int i = 0; i < 50; i++)
                  
          {
                      System.out.println(
          "#");
                      
          if(i == 24)
                          
          try{
                              t1.join();
                          }
          catch(InterruptedException e)
                          
          {
                              e.printStackTrace();
                          }

                  }

              }

          }


          class MyThread extends Thread
          {
              
          public void run()
              
          {
                  
          for(int i = 0; i < 35; i++)
                  
          {
                      System.out.println(
          "*");
                  }

              }

          }



          線程優先級,setPriotity(1--100),數越大,優先級越高。

          開發中不提倡自省設置優先級,操作系統可能忽略優先級,不具有跨平臺性(兩方面,可運行,執行效果一致),因為這種方式很粗略。

          static void yield(),運行狀態的線程(當前線程),調用yield方法,馬上交出執行權。回到可運行狀態。

          =============================

          Thread對象有個run方法,當start()時,Thread進行系統級調用,系統分配一個線程空間,此時對象可以獲得CPU時間片,一個順序執行流程可以獨立運行,線程結束,對象還在,只是系統回收線程。

          =============================


          兩個線程同時的資源,稱為臨界資源,會有沖突。

          堆棧數據結構,有一個char[]和一個index(表示實際長度,也表示下一個要插入元素的位置)。

          一個push操作,有兩個核心操作(加元素,修改index)。都執行和都沒執行,沒有問題。

          但假設一個線程做了一個步,就交出執行權,別的線程,執行同樣的代碼,會造成數據不一致

          數據完整性也是一個要在開發中注意的地方。

          ------------------------------------------

          所以為了保證數據安全,要給數據加鎖。

          一個Java對象,不僅有屬性和方法,還有別的東西。

          任何一個對象,都有一個monitor,互斥鎖標記,可以交給一個線程。

          只有拿到這個對象互斥鎖標記的線程,才能訪問這個對象。

          synchronized可以修飾方法和代碼塊

          synchronized(obj){
             obj.setValue(123);
          }

          不是每個線程都能進入這段代碼塊,只有拿到鎖標記的線程才能進入執行完,釋放鎖標記,給下一個線程。

          記住,鎖標記是對對象來說的,鎖的是對象。

          當synchronized標識方法時,那么就是鎖當前對象。

          class Stack{
              
          int index=0;
              
          char data[]=new char[6];
              
          public synchronized void push(char c){
                      data[index]
          =c;
                      System.out.println(
          "Push char: "+c+" ->");
                      
                      
          try{
                          Thread.sleep(
          1000);
                      }

                      
          catch(Exception e){}
                      index
          ++;
                  System.out.println(
          "--> Push "+c+" Completed  index="+index+"   Stack is "+data[0]+" "+data[1]+" "+data[2]);
              }

              
              
          public void pop(){
                      index
          --;
              
                      
          try{
                          Thread.sleep(
          10);
                      }

                      
          catch(Exception e){}    
                      
                      
          char c=data[index];
                      data[index]
          =' ';
                      System.out.println(
          "** Pop  "+c+"**  index="+index+"   Stack is "+data[0]+" "+data[1]+" "+data[2]);    

              }

          }
               

          class PushRunner implements Runnable{
              
          private Stack s;
              
          public PushRunner(Stack s){
                  
          this.s=s;
              }

              
              
          public void run(){
                      s.push(
          'c');
              }


              
          }


          class PopRunner implements Runnable{
              
          private Stack s;
              
          public PopRunner(Stack s){
                  
          this.s=s;
              }

              
          public void run(){
                  
          synchronized(s){
                      s.pop();
                  }

              }

          }

          public class TestSynchronize{
              
          public static void main(String[] args){
                  Stack s
          =new Stack();
                  s.push(
          'a');
                  s.push(
          'b');
                  
          new Thread(new PushRunner(s)).start();
                  
          new Thread(new PopRunner(s)).start();
              }

          }



          注意此代碼中,Stack這個臨界資源類中的push方法中,有一個Thread.sleep,它讓當前進程阻塞,也就是讓擁有Stack對象s鎖標記的線程阻塞,但這時它并不釋放鎖標記

          所以Synchronized使用是有代價的,犧牲效率換數據安全,要控制synchronized代碼塊,主要是數據寫,修改做同步限制,讀就不用了。

          還有一點要注意:synchronized不能繼承, 父類的方法是synchronized,那么其子類重載方法中就不會繼承“同步”。

          一個線程可以擁有很多對象鎖標記,但一個對象的鎖標記只能給一個線程。

          等待鎖標記的線程,進入該對象的鎖池。

          1.jpg

          每個對象都有一個空間,鎖池,里面都是等待拿到該對象的鎖標記的線程。

          當然還是操作系統來決定誰來獲得鎖標記,在上一個鎖標記釋放掉后。



          死鎖,線程A拿到resourceA標記,去請求resourceB;線程B拿到resourceB標記,去請求resourceA;

          線程間通訊機制->協調機制

          一個對象不僅有鎖和鎖池,另外還有一個空間[等待隊列]。

          synchronized(路南){
                想要獲得路北資源的線程,調用路南.wait();將自己的所有鎖標記都釋放。以便其他線程滿足條件運行程序后,自己也就可以正常通過了。
          }

          調用obj.wait(),表示某一個線程釋放所有鎖標記并進入obj這個對象的等待隊列。

          等待隊列也是阻塞狀態。一個線程調用obj對象的notify(),會通知等待隊列中的一個線程可以出來,notifyAll()是通知所有線程。

          Untitled-1.gif

           

           

          public class ProducerConsumer{
              
          public static void main(String[] args){
                  SyncStack s
          =new SyncStack();
                  Runnable p
          =new Producer(s);
                  Runnable c
          =new Consumer(s);
                  
          new Thread(p).start();
                  
          new Thread(c).start();
              }

          }



          class Producer implements Runnable{
              SyncStack s;
              
          public Producer(SyncStack s){
                  
          this.s=s;
              }

              
              
          public void run() {
                  
          for(int i=1;i<=20;i++){
                      
          char c=(char)(Math.random()*26+'A');
                      s.push(c);
                      
          try{
                          Thread.sleep(
          20);
                      }

                      
          catch (InterruptedException e){
                          e.printStackTrace();
                      }

                  }

              }

          }


          class Consumer implements Runnable{
              SyncStack s;
              
          public Consumer(SyncStack s){
                  
          this.s=s;
              }

              
              
          public void run(){
                  
          for(int i=1;i<=20;i++){
                      
          char c=s.pop();
                      
          try{
                          Thread.sleep(
          400);
                      }

                      
          catch (InterruptedException e){
                          e.printStackTrace();
                      }

                  }

              }

          }


          class SyncStack{
              
          private int index=0;  // the index next char added into, also presents the number of chars in stack
              private char[] data=new char[6];
              
              
          public synchronized void push(char c) {
                  
          while (index==data.length)  {
                      
          try{
                          
          this.wait();
                      }

                      
          catch (InterruptedException e){}
                  }

                  
                  data[index]
          =c;
                  index
          ++;
                  System.out.println(
          "Char "+c+" Pushed into Stack");
                  
          for(int k=0;k<data.length;k++) System.out.print(data[k]);
                  System.out.println();
                  
                  
          this.notifyAll();

              }

              
              
          public synchronized char pop()  {
                  
          while (index==0)  {
                      
          try{
                          
          this.wait();
                      }

                      
          catch (InterruptedException e){}
                  }

                  
                  index
          --;
                  
          char c=data[index];
                  data[index]
          =' ';
                  System.out.println(
          "Char "+c+" Poped  from Stack");
                  
                  
          for(int k=0;k<data.length;k++) System.out.print(data[k]);
                  System.out.println();
                  
                  
          this.notifyAll();

                  
          return c;
              }

          }

          上面為經典的生產者消費者問題,生產者使用SyncStack的push方法,消費者使用pop方法。


          push方法:

            while (index==data.length)  {
             try{
              this.wait();//<-----------釋放所有鎖標記,阻塞現場保留
             }
             catch (InterruptedException e){}
            }

          如果貨架滿了,生產者即使擁有鎖標記,也不能再生產商品了,必須wait()。等待消費者消費物品,否則永遠不會從SyncStack對象的等待隊列中出來。

          等待通知,何時通知呢?

          public synchronized char pop()  {
            while (index==0)  {
             try{
              this.wait();
             }
             catch (InterruptedException e){}
            }
            
            index--;
            char c=data[index];
            data[index]=' ';
            System.out.println("Char "+c+" Poped  from Stack");
            
            for(int k=0;k<data.length;k++) System.out.print(data[k]);
            System.out.println();
            
            this.notifyAll(); //<-------------所有等待隊列中的生產者都出了隊列,因為沒有鎖標記,只能進入鎖池。

            return c;
           }

          為什么判斷是一個while循環,而不是一個if呢?

          注意:我們假設一種情形:

          1) 有十個生產者線程,貨架已經滿了,10生產者依次獲得鎖標記,依次都調用this.wait(),都進入同一個SyncStack對象的等待隊列,10個進程阻塞住,

          2) 有一個消費者線程獲得該SyncStack對象鎖標記,一個消費者消費一個,執行完消費,調用this.notifyAll()
          釋放鎖標記。

          3) 剛才消費者調用SyncStack對象的notifyAll()后,10個線程都出來了,準備生產商品,全部進入鎖池。

          這十個線程的代碼現場,還在wait()這個函數調用后面,也就是一旦或者鎖標記,要繼續從這里執行。

          this.wait();//從這一句的后面繼續執行。

          4) 但如果有一個生產者push的話,貨架已經就滿了,但這時還有9個在鎖池中,依次獲得鎖標記,但由于是while需要再次判斷是否貨架滿不滿,才能繼續前行進行生產。如果是if,就會直接push,數組越界。


          ===================================

          釋放鎖標記只有兩種途徑,代碼執行完,wait()

          讓線程結束,就是想辦法讓run方法結束。

          注意下面的bStop,標志位,可以在線程進入wait狀態時,對某一線程調用interrupt(),線程拋出InterruptedException,然后根據標志位,方法返回。

          ;
          class TestInterrupt
          {
              
          public static void main(String[] args)
              
          {
                  Thread1 t1
          =new Thread1();
                  t1.start();
                  
          int index=0;
                  
          while(true)
                  
          {
                      
          if(index++==500)
                      
          {
                          t1.stopThread();
                          t1.interrupt();
                          
          break;
                      }

                      System.out.println(Thread.currentThread().getName());
                  }

                  System.out.println(
          "main() exit");
              }

          }


          class Thread1 extends Thread
          {
              
          private boolean bStop=false;
              
          public synchronized void run()
              
          {
                  
          while(!bStop)
                  
          {
                      
          try    
                      
          {
                          wait();
                      }

                      
          catch(InterruptedException e)
                      
          {
                          
          //e.printStackTrace();
                          if(bStop)  return;
                      }

                      
                      System.out.println(getName());
                  }

              }

              
          public void stopThread()
              
          {
                  bStop
          =true;
              }

          }

          Exc:

          AB1CD2....
           
          數字與字母依次打印。用線程完成。

          public class Test{
              
          public static void main(String[] args)
              
          {
                  Object o 
          = new Object();
                  PrintChar pc 
          = new PrintChar(o);
                  PrintNum pn 
          = new PrintNum(o);
                  pn.start();
                  pc.start();
                  
              }


          }

          class PrintChar extends Thread{
              
          private int index = 0;
              
          private Object obj = null;
              
              
          public PrintChar(Object o)
              
          {
                  
          this.obj = o;
              }

              
          public void run(){
              
              
          synchronized(obj){
                      
          for( ;index < 26; index++)
                      
          {
                          
                              System.out.print((
          char)(index+'A'));
                              obj.notifyAll();
                              
          if(index != 25)
                                  
          try{
                                          obj.wait();
                                  }
          catch(InterruptedException e){}
                      }
                  
              }
              
          }

          }


          class PrintNum extends Thread{
            
          private int index = 1;
            
          private Object obj = null;
            
          public PrintNum(Object o)
            
          {
                
          this.obj = o;
            }

            
              
          public void run(){
                  
                  
          synchronized(obj){
                      
          for( ;index < 53;index++)
                      
          {
                              System.out.print(index);                    
                              
          if(index%2==0){            
                                  obj.notifyAll();            
                                  
          if(index != 52){
                                      
          try{                        
                                              obj.wait();                        
                                      }
          catch(InterruptedException e)
                                      
          {}
                                  }

                              }
                          
                          
                      }

                  }

              }


          }


          posted on 2005-12-15 00:32 北國狼人的BloG 閱讀(583) 評論(2)  編輯  收藏 所屬分類: 達內學習總結

          評論:
          # re: Java 多線程編程 2008-03-21 09:18 | hhzhaoheng
          非常感謝!  回復  更多評論
            
          # re: Java 多線程編程 2008-11-17 21:11 | yiminghe
          看看 java 多線程設計模式  回復  更多評論
            
          主站蜘蛛池模板: 五寨县| 民丰县| 库车县| 莎车县| 吉木萨尔县| 正安县| 竹北市| 清水河县| 中阳县| 宣化县| 澜沧| 罗平县| 新田县| 三都| 赤水市| 郸城县| 壶关县| 皮山县| 花垣县| 吉水县| 徐水县| 横峰县| 城市| 临沭县| 百色市| 类乌齐县| 鹤岗市| 新泰市| 轮台县| 凉城县| 抚松县| 沾化县| 永泰县| 平远县| 侯马市| 隆尧县| 洛浦县| 衡阳县| 桐柏县| 洞头县| 壶关县|