隨筆-159  評(píng)論-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();
              }

          }

          首先,我們討論一下進(jìn)程,進(jìn)程是操作系統(tǒng)級(jí)別下,單獨(dú)執(zhí)行的一個(gè)任務(wù)。

          Win32 Unix都是多任務(wù)操作系統(tǒng)。

          多任務(wù),并發(fā)執(zhí)行是一個(gè)宏觀概念,實(shí)際微觀串行。

          CPU同一時(shí)間刻只能執(zhí)行一個(gè)任務(wù)。

          OS負(fù)責(zé)進(jìn)程調(diào)度,使用CPU,也就是獲得時(shí)間片。




          在一個(gè)進(jìn)程中,再可以分為多個(gè)程序順序執(zhí)行流,每個(gè)執(zhí)行流就是一個(gè)線程。

          分配CPU時(shí)間片的依然是CPU,多線程時(shí),程序會(huì)變慢,每個(gè)線程分配到的時(shí)間片少了。



          進(jìn)程與線程的區(qū)別,進(jìn)程是數(shù)據(jù)獨(dú)占的(獨(dú)立數(shù)據(jù)空間),線程是數(shù)據(jù)共享的(這也是線程之間通訊容易的原因,不需要傳遞數(shù)據(jù))。

          Java是語言級(jí)支持多線程的,體現(xiàn)在有現(xiàn)成的封裝類(java.lang.Thread)完成了必要的并發(fā)細(xì)節(jié)的工作(與操作系統(tǒng)打交道,分配PID等)。



          兩種方式來得到一個(gè)線程對(duì)象。

          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");
              }

          }


          一個(gè)線程對(duì)象--〉代表著一個(gè)線程--〉一個(gè)順序執(zhí)行流(run方法)

          這個(gè)程序有兩個(gè)線程,一個(gè)是main主線程,它調(diào)用了t1.start(),這是t1線程只是就緒狀態(tài),還沒有真正啟動(dòng)線程,main主線程結(jié)束了!!t1運(yùn)行。兩個(gè)線程都退出了,進(jìn)程完結(jié)。

          一個(gè)進(jìn)程退出,要等待進(jìn)程中所有線程都退出,再退出虛擬機(jī)。

          方式2,實(shí)現(xiàn)java.lang.Runable接口,這是這個(gè)類的對(duì)象是一個(gè)目標(biāo)對(duì)象,而不能理解為是一個(gè)線程對(duì)象。

          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");
              }

          }

          不要調(diào)用run()方法,它只是執(zhí)行一下普通的方法,并不會(huì)啟動(dòng)單獨(dú)的線程。

          Untitled-1.gif

          上面只是線程狀態(tài)圖。


          在某一個(gè)時(shí)間內(nèi),處于運(yùn)行狀態(tài)的線程,執(zhí)行代碼,注意可能多個(gè)線程多次執(zhí)行代碼。

          CPU會(huì)不斷從可運(yùn)行狀態(tài)線程調(diào)入運(yùn)行,不會(huì)讓CPU空閑。


          Thread.sleep(1000);當(dāng)前線程睡眠1秒鐘,休眠后->進(jìn)入阻塞->休眠結(jié)束->回到可運(yùn)行狀態(tài)

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

          能進(jìn)入運(yùn)行狀態(tài),只能由操作系統(tǒng)來調(diào)度。

          一旦sleep-〉阻塞->交出程序執(zhí)行權(quán)。


          等待用戶輸入,輸入輸出設(shè)備占用CPU,處于阻塞的線程沒有機(jī)會(huì)運(yùn)行,輸入完畢,重新進(jìn)入可運(yùn)行狀態(tài)。


          第三種進(jìn)入阻塞狀態(tài)的可能。

          t1.join()調(diào)用后,運(yùn)行狀態(tài)線程放出執(zhí)行權(quán),作為t1的后續(xù)線程,等待t1結(jié)束。也就是說至少得等t1線程run完畢,才可能進(jìn)入運(yùn)行狀態(tài)來執(zhí)行,可不是說t1執(zhí)行完,一定馬上就是調(diào)用t1.join()的線程馬上進(jìn)入可運(yùn)行行狀態(tài)。只有操作系統(tǒng)有權(quán)利決定誰進(jìn)入運(yùn)行狀態(tài)。

          join的實(shí)質(zhì)就是讓兩個(gè)線程和二為一,串行。

          t1.join();執(zhí)行這條語句現(xiàn)場(chǎng)是被保護(hù)起來的。t1結(jié)束,調(diào)用線程有機(jī)會(huì)運(yùn)行時(shí),會(huì)從上次的位置繼續(xù)運(yùn)行。

          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(
          "*");
                  }

              }

          }



          線程優(yōu)先級(jí),setPriotity(1--100),數(shù)越大,優(yōu)先級(jí)越高。

          開發(fā)中不提倡自省設(shè)置優(yōu)先級(jí),操作系統(tǒng)可能忽略優(yōu)先級(jí),不具有跨平臺(tái)性(兩方面,可運(yùn)行,執(zhí)行效果一致),因?yàn)檫@種方式很粗略。

          static void yield(),運(yùn)行狀態(tài)的線程(當(dāng)前線程),調(diào)用yield方法,馬上交出執(zhí)行權(quán)。回到可運(yùn)行狀態(tài)。

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

          Thread對(duì)象有個(gè)run方法,當(dāng)start()時(shí),Thread進(jìn)行系統(tǒng)級(jí)調(diào)用,系統(tǒng)分配一個(gè)線程空間,此時(shí)對(duì)象可以獲得CPU時(shí)間片,一個(gè)順序執(zhí)行流程可以獨(dú)立運(yùn)行,線程結(jié)束,對(duì)象還在,只是系統(tǒng)回收線程。

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


          兩個(gè)線程同時(shí)的資源,稱為臨界資源,會(huì)有沖突。

          堆棧數(shù)據(jù)結(jié)構(gòu),有一個(gè)char[]和一個(gè)index(表示實(shí)際長(zhǎng)度,也表示下一個(gè)要插入元素的位置)。

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

          但假設(shè)一個(gè)線程做了一個(gè)步,就交出執(zhí)行權(quán),別的線程,執(zhí)行同樣的代碼,會(huì)造成數(shù)據(jù)不一致

          數(shù)據(jù)完整性也是一個(gè)要在開發(fā)中注意的地方。

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

          所以為了保證數(shù)據(jù)安全,要給數(shù)據(jù)加鎖。

          一個(gè)Java對(duì)象,不僅有屬性和方法,還有別的東西。

          任何一個(gè)對(duì)象,都有一個(gè)monitor,互斥鎖標(biāo)記,可以交給一個(gè)線程。

          只有拿到這個(gè)對(duì)象互斥鎖標(biāo)記的線程,才能訪問這個(gè)對(duì)象。

          synchronized可以修飾方法和代碼塊

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

          不是每個(gè)線程都能進(jìn)入這段代碼塊,只有拿到鎖標(biāo)記的線程才能進(jìn)入執(zhí)行完,釋放鎖標(biāo)記,給下一個(gè)線程。

          記住,鎖標(biāo)記是對(duì)對(duì)象來說的,鎖的是對(duì)象。

          當(dāng)synchronized標(biāo)識(shí)方法時(shí),那么就是鎖當(dāng)前對(duì)象。

          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這個(gè)臨界資源類中的push方法中,有一個(gè)Thread.sleep,它讓當(dāng)前進(jìn)程阻塞,也就是讓擁有Stack對(duì)象s鎖標(biāo)記的線程阻塞,但這時(shí)它并不釋放鎖標(biāo)記

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

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

          一個(gè)線程可以擁有很多對(duì)象鎖標(biāo)記,但一個(gè)對(duì)象的鎖標(biāo)記只能給一個(gè)線程。

          等待鎖標(biāo)記的線程,進(jìn)入該對(duì)象的鎖池。

          1.jpg

          每個(gè)對(duì)象都有一個(gè)空間,鎖池,里面都是等待拿到該對(duì)象的鎖標(biāo)記的線程。

          當(dāng)然還是操作系統(tǒng)來決定誰來獲得鎖標(biāo)記,在上一個(gè)鎖標(biāo)記釋放掉后。



          死鎖,線程A拿到resourceA標(biāo)記,去請(qǐng)求resourceB;線程B拿到resourceB標(biāo)記,去請(qǐng)求resourceA;

          線程間通訊機(jī)制->協(xié)調(diào)機(jī)制

          一個(gè)對(duì)象不僅有鎖和鎖池,另外還有一個(gè)空間[等待隊(duì)列]。

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

          調(diào)用obj.wait(),表示某一個(gè)線程釋放所有鎖標(biāo)記并進(jìn)入obj這個(gè)對(duì)象的等待隊(duì)列。

          等待隊(duì)列也是阻塞狀態(tài)。一個(gè)線程調(diào)用obj對(duì)象的notify(),會(huì)通知等待隊(duì)列中的一個(gè)線程可以出來,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;
              }

          }

          上面為經(jīng)典的生產(chǎn)者消費(fèi)者問題,生產(chǎn)者使用SyncStack的push方法,消費(fèi)者使用pop方法。


          push方法:

            while (index==data.length)  {
             try{
              this.wait();//<-----------釋放所有鎖標(biāo)記,阻塞現(xiàn)場(chǎng)保留
             }
             catch (InterruptedException e){}
            }

          如果貨架滿了,生產(chǎn)者即使擁有鎖標(biāo)記,也不能再生產(chǎn)商品了,必須wait()。等待消費(fèi)者消費(fèi)物品,否則永遠(yuǎn)不會(huì)從SyncStack對(duì)象的等待隊(duì)列中出來。

          等待通知,何時(shí)通知呢?

          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(); //<-------------所有等待隊(duì)列中的生產(chǎn)者都出了隊(duì)列,因?yàn)闆]有鎖標(biāo)記,只能進(jìn)入鎖池。

            return c;
           }

          為什么判斷是一個(gè)while循環(huán),而不是一個(gè)if呢?

          注意:我們假設(shè)一種情形:

          1) 有十個(gè)生產(chǎn)者線程,貨架已經(jīng)滿了,10生產(chǎn)者依次獲得鎖標(biāo)記,依次都調(diào)用this.wait(),都進(jìn)入同一個(gè)SyncStack對(duì)象的等待隊(duì)列,10個(gè)進(jìn)程阻塞住,

          2) 有一個(gè)消費(fèi)者線程獲得該SyncStack對(duì)象鎖標(biāo)記,一個(gè)消費(fèi)者消費(fèi)一個(gè),執(zhí)行完消費(fèi),調(diào)用this.notifyAll()
          釋放鎖標(biāo)記。

          3) 剛才消費(fèi)者調(diào)用SyncStack對(duì)象的notifyAll()后,10個(gè)線程都出來了,準(zhǔn)備生產(chǎn)商品,全部進(jìn)入鎖池。

          這十個(gè)線程的代碼現(xiàn)場(chǎng),還在wait()這個(gè)函數(shù)調(diào)用后面,也就是一旦或者鎖標(biāo)記,要繼續(xù)從這里執(zhí)行。

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

          4) 但如果有一個(gè)生產(chǎn)者push的話,貨架已經(jīng)就滿了,但這時(shí)還有9個(gè)在鎖池中,依次獲得鎖標(biāo)記,但由于是while需要再次判斷是否貨架滿不滿,才能繼續(xù)前行進(jìn)行生產(chǎn)。如果是if,就會(huì)直接push,數(shù)組越界。


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

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

          讓線程結(jié)束,就是想辦法讓run方法結(jié)束。

          注意下面的bStop,標(biāo)志位,可以在線程進(jìn)入wait狀態(tài)時(shí),對(duì)某一線程調(diào)用interrupt(),線程拋出InterruptedException,然后根據(jù)標(biāo)志位,方法返回。

          ;
          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....
           
          數(shù)字與字母依次打印。用線程完成。

          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)
                                      
          {}
                                  }

                              }
                          
                          
                      }

                  }

              }


          }



          評(píng)論:
          # re: Java 多線程編程 2008-03-21 09:18 | hhzhaoheng
          非常感謝!  回復(fù)  更多評(píng)論
            
          # re: Java 多線程編程 2008-11-17 21:11 | yiminghe
          看看 java 多線程設(shè)計(jì)模式  回復(fù)  更多評(píng)論
            
          主站蜘蛛池模板: 锡林郭勒盟| 龙泉市| 汶上县| 乌兰浩特市| 武义县| 乐至县| 读书| 乌兰察布市| 连州市| 蕲春县| 宣恩县| 增城市| 珲春市| 滨海县| 蓝田县| 湖口县| 昌都县| 新泰市| 台南县| 柳州市| 交城县| 利津县| 噶尔县| 布尔津县| 界首市| 理塘县| 册亨县| 宝坻区| 辽中县| 通化县| 富阳市| 南昌市| 和平县| 金堂县| 通城县| 霞浦县| 涿鹿县| 安宁市| 乐安县| 万荣县| 博白县|