多線程高併發編程(1) -- 基礎及詳解

背景:

  進程和線程的區別:

  進程的內存大小爲:堆內存+線程數量*棧內存線程數量 =( 最大地址空間[MaxProcessMemory] - JVM堆內存 - 系統保留內存[ReservedOsMemory] )/ ThreadStackSize(XSS)從中可以看出,線程的數量隨棧內存的增多而減少。

  線程是程序執行的一個路徑,每一個線程都有自己的局部變量表、程序計數器(指向正在執行的指令指針)以及各自的生命週期。當啓動了一個Java虛擬機(JVM)時,從操作系統開始就會創建一個新的進程(JVM進程),JVM進程將會派生或者創建很多線程。

  • 一個線程的創建肯定是由另一個線程完成的;
  • 被創建線程的父線程是創建它的線程;

  線程會帶來額外的開銷,如CPU調度時間、併發控制開銷等;每個線程在自己的工作內存交互,加載和存儲主內存控制不當會造成數據不一致。

一.線程創建方式:

  • 構造Thread類:實現線程的執行單元run有兩種方式,分別是下面

    • 繼承Thread,重寫run方法:Thread實現了Runnable接口,使用start開啓線程,start開啓後線程會加入調度器,然後調用run方法,start會調用start0本地方法跟OS進行交互運行;下面是start源碼解析

      /**
       * Causes this thread to begin execution; the Java Virtual Machine
       * calls the <code>run</code> method of this thread.
       * 開啓線程,JVM會調用run方法【start使用了模板方法】
       * <p>
       * It is never legal to start a thread more than once.
       * 不能兩次啓動線程,否則報IllegalThreadStateException異常
       * In particular, a thread may not be restarted once it has completed
       * execution.
       * 一個線程生命週期結束,也就是到了TERMINATED狀態,再次調用start方法是不允許的,
       * 也就是TERMINATED狀態沒法回到RUNNABLE/RUNNING狀態。
       *
       * @exception  IllegalThreadStateException  if the thread was already
       *               started.
       * @see        #run()
       * @see        #stop()
       */
      public synchronized void start() {//線程安全的
          /**
           * This method is not invoked for the main method thread or "system"
           * group threads created/set up by the VM. Any new functionality added
           * to this method in the future may have to also be added to the VM.
           * 這個方法不會被主線程調用或通過虛擬機系統線程組創建起來。未來任何添加到該方法裏的新功能可能需要加入到虛擬機中
           *
           * A zero status value corresponds to state "NEW".
      * 線程被構造後的new狀態,threadStatus的屬性值是0
      */ if (threadStatus != 0) throw new IllegalThreadStateException(); /* Notify the group that this thread is about to be started * so that it can be added to the group's list of threads * and the group's unstarted count can be decremented.
      * 通知線程組新線程將要啓動,以便它可以添加到線程組列表並且線程組沒有開始計數
      */ group.add(this);//加入線程組 boolean started = false; try { start0();//調用本地方法 started = true; } finally { try { if (!started) {//啓動失敗 group.threadStartFailed(this);//線程啓動失敗,從組中移除該線程 } } catch (Throwable ignore) { /* do nothing. If start0 threw a Throwable then it will be passed up the call stack */ } } }
      void add(Thread t) {
      synchronized (this) {
      if (destroyed) {//線程組狀態校驗
      throw new IllegalThreadStateException();
      }
      if (threads == null) {
      threads = new Thread[4];//初始化長度爲4的線程組
      } else if (nthreads == threads.length) {
      threads = Arrays.copyOf(threads, nthreads * 2);//數組滿了就擴容2倍
      }
      threads[nthreads] = t;//當前線程添加到線程組中

      // This is done last so it doesn't matter in case the
      // thread is killed
      nthreads++;//線程數+1

      // The thread is now a fully fledged member of the group, even
      // though it may, or may not, have been started yet. It will prevent
      // the group from being destroyed so the unstarted Threads count is
      // decremented.
      nUnstartedThreads--;//未啓動線程數-1
      }
      }
      private native void start0();//本地方法調用重寫的run方法
      void threadStartFailed(Thread t) {
      synchronized(this) {
      remove(t);//移除當前線程
      nUnstartedThreads++;//沒有啓動的線程數量+1
      }
      }

      //=======================測試============================
      Thread t = new Thread(){
        @Override
        public void run(){
            try {
                TimeUnit.SECONDS.sleep(10);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
        }
      };
      t.start();
      t.start();//不能兩次啓動,第二次啓動是不允許的,報IllegalThreadStateException,此時該線程是處於運行狀態
      //=======================測試============================== Thread t = new Thread(){ @Override public void run(){ try { TimeUnit.SECONDS.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } } }; t.start(); TimeUnit.SECONDS.sleep(10);//設置休眠時間,上面的線程的生命週期已經終止,下面再次啓動報IllegalThreadStateException t.start();
    • 實現Runnable接口,重寫run方法並且將Runnable實例用作構造Thread的參數【單繼承有侷限性,推薦使用接口】:將線程的控制(start)和業務邏輯(run)的運行徹底分離開來,使用的是策略模式;Thread的run方法是不能共享的,但Runnbale的run方法可以共享,使用同一個Runnable的實例構造不同的Thread實例;把實現類對象(實現Runnable接口的類的實例化)放入代理類對象(Thread構造方法)中,使用的是代理模式;下面是靜態代理的代碼解釋:

      public class StaticProxy {
          public static void main(String[] args) {
              new Weeding(new Me()).happyMarry();
      //        new Thread(對象).start();類似
          }
      }
      
      interface Marry {
          void happyMarry();
      }
      //真實角色
      class Me implements Marry {
          @Override
          public void happyMarry() {
              System.out.println("me will marry!");
          }
      }
      //代理對象
      class Weeding implements Marry{
          //真實角色
          private Marry marry;
          public Weeding(Marry marry){
              this.marry=marry;
          }
          @Override
          public void happyMarry() {
              System.out.println("start");
              marry.happyMarry();
              System.out.println("end");
          }
      }
  • 實現Callable接口,重寫call方法,Future獲取返回值:Callable能接受一個泛型,然後在call方法中返回一個指定類型的值;

    public interface Callable<V> {
        V call() throws Exception;
    } 
    //線程池隊列開啓線程,不會產生髒讀數據
    //使用步驟:
    //1.創建目標對象new
    //2.創建執行服務線程池
    //3.提交執行submit
    //4.獲取結構get
    //5.關閉服務shutdownNow
    public class MyThread implements Callable {
        private static int count = 20;
    
        public static void main(String[] args) throws ExecutionException, InterruptedException {
            MyThread m1 = new MyThread();
            MyThread m2 = new MyThread();
            MyThread m3 = new MyThread();
            MyThread m4 = new MyThread();
            ScheduledExecutorService service = new ScheduledThreadPoolExecutor(2,
                    new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build());
            Future submit = service.submit(m1);
            Future submit1 = service.submit(m2);
            Future submit2 = service.submit(m3);
            Future submit3 = service.submit(m4);
            System.out.println(submit.get());
            System.out.println(submit1.get());
            System.out.println(submit2.get());
            System.out.println(submit3.get());
            service.shutdown();
    
        }
    
        @Override
        public Object call() throws Exception {
            count--;
            return count;
        }
    }
  • 匿名內部類;

            new Thread(){//相當於繼承Thread的方式
                public void run(){
                    System.out.println("thread1 start ... ");
                }
            }.start();
    
    
            new Thread(new Runnable() {//相當於實現Runnable接口的方式
                @Override
                public void run() {
                    System.out.println("thread2 start .... ");
                }
            }).start();
  • 定時器(Timer);

            Timer timer = new Timer();//創建時間器
            timer.schedule(new TimerTask() {//使用schedule,參數爲定時器任務並重寫run方法
                @Override
                public void run() {
                    System.out.println("timer task is run");
                }
            }, 0, 1000);
  • 線程池(內部使用隊列,所以加入線程池的線程是順序執行):使用execute和重寫Runnbale的run方法;

    ScheduledExecutorService service = new ScheduledThreadPoolExecutor(2,
                    new BasicThreadFactory.Builder().namingPattern("schedule-pool-%d").daemon(true).build());
            service.execute(new Runnable() {
                @Override
                public void run() {
                    System.out.println("run test");
                }
            });
  • lambda表達式;

            new Thread(()-> {
                for(int i = 1 ; i<10 ; i++){
                    System.out.println("It is a lambda function!");
                }
    
            }).start();
  • Spring方式(@Async註解);

        @Test
        public void test() {
            run();
        }
        @Async
        public void run(){
            System.out.println("Async Test");
        }

二.線程生命週期

  • new新生狀態:當用new創建一個Thread對象時,此時它並不處於執行狀態,因爲沒有調用star啓動該線程,那麼線程的狀態爲new狀態,也就是說,它只是Thread對象的狀態,在沒有start之前,該線程是不存在的

  • runnable就緒狀態:線程對象進入runnable就緒狀態必須調用start方法,那麼此時纔是真正地在JVM進程中創建了一個線程就緒狀態不會直接進入阻塞狀態和死亡狀態,即使是在線程的執行邏輯中調用wait、sleep或其他block的IO操作等,也必須先獲得CPU的調度執行權纔可以,嚴格來說,就緒狀態的線程只能意外終止或進入運行狀態;

  • running運行狀態:一旦CPU通過輪詢或其他方式從任務可執行隊列中選中了線程,此時它才能真正地執行自己的邏輯代碼一個正在running狀態的線程事實上也是一個runnable的,但是反過來則不成立;

 

  • sleep:使當前線程進入指定毫秒級的休眠,暫停執行,但不會放棄monitor鎖的所有權,即不會釋放鎖資源;使用TimeUnit來替代Thread.sleep,省去了時間單位的換算步驟;

  • yield:屬於一種啓發式的方法,其會提醒調度器我願意放棄當前的CPU資源,如果CPU的資源不緊張,則會忽略這種提醒;yield只是一個提示(hint),CPU調度器並不會擔保每次都能滿足yield提示;

  • sleep和yield的區別:

    • sleep會導致當前線程暫停指定的時間,沒有CPU時間片的消耗;

    • yield只是對CPU調度器的一個提示,如果CPU調度器沒有忽略這個提示,它會導致線程上下文的切換;

    • sleep會使線程短暫block,會在給定的時間內釋放CPU資源

    • yield會使running狀態的線程進入runnable狀態(如果CPU調度器沒有忽略這個提示的話);

    • sleep幾乎百分之百地完成了給定時間的休眠,但yield的提示並不能一定擔保;

    • 一個線程sleep另一個線程interrupt會捕獲到中斷信號,而yield則不會;

  • join:join某個線程A,會使當前線程B進入等待,直到線程A結束生命週期;可以使用join來達到線程順序執行的效果

  • wait:表示線程一直等待,直到其他線程通知,與sleep不同的是它會釋放鎖;調用wait會加入wait set中,notify會隨機喚醒一個,notifyAll會彈出所有線程;

  • notify:喚醒一個處於等待狀態的線程;

  • notifyAll:喚醒同一個對象上所有調用wait方法的線程,優先級高的線程優先調度

  • synchronized:同步,內置鎖、互斥鎖、可重入鎖,鎖定共享資源(共享資源對象不能爲null,使用static修飾保持對象引用地址只有一份),依賴JVM,JVM指令是monitor enter和monitor exit;synchronized的指令嚴格遵守java happens-before規則,一個monitor exit指令之前必定要有一個monitor enter;不可中斷鎖,適合競爭不激烈,可讀性好

    • 鎖信息存在對象頭中:

      • Mark Word

        • 線程id

        • Epoch

        • 對象的分代年齡信息

        • 是否是偏向鎖

        • 鎖標誌位

      • Class Metadata Address

    • 使用範圍:
      • 修飾代碼塊:大括號括起來的代碼,作用於調用的對象
            @Override
            public void run() {
                synchronized (this) {//類A實現了Runnable,重寫了run方法,實例化對象a,b分別加入到Thread構造方法中並開啓線程,this是兩個不同的對象
                    System.out.println(this.hashCode());
                }
            }
                MyThread thread = new MyThread();
                MyThread thread2 = new MyThread();
                Thread t1 = new Thread(thread, "t1");
                Thread t2 = new Thread(thread2, "t2");
                Thread t3 = new Thread(thread, "t3");
                Thread t4 = new Thread(thread, "t4");
        
                t1.start();
                t2.start();
                t3.start();
                t4.start();
        ===========結果=================
        693024158
        1259146238
        1259146238
        1259146238
      • 修飾方法:整個方法,作用於調用的對象
            @Override
            public synchronized void run() {//類A實現了Runnable,重寫了run方法,實例化對象a,b分別加入到Thread構造方法中並開啓線程,this是兩個不同的對象
                System.out.println(this.hashCode());
            }
                MyThread thread = new MyThread();
                MyThread thread2 = new MyThread();
                Thread t1 = new Thread(thread, "t1");
                Thread t2 = new Thread(thread2, "t2");
                Thread t3 = new Thread(thread, "t3");
                Thread t4 = new Thread(thread, "t4");
        
                t1.start();
                t2.start();
                t3.start();
                t4.start();
        ===============結果===================
        487590100
        697138600
        697138600
        697138600
      • 修飾靜態方法:整個靜態方法,作用於所有對象
            @Override
            public void run() {
                    test1();
            }
            public static synchronized void test1(){
                System.out.println(MyThread.class.hashCode());
            }
                MyThread thread = new MyThread();
                MyThread thread2 = new MyThread();
                Thread t1 = new Thread(thread, "t1");
                Thread t2 = new Thread(thread2, "t2");
                Thread t3 = new Thread(thread, "t3");
                Thread t4 = new Thread(thread, "t4");
        
                t1.start();
                t2.start();
                t3.start();
                t4.start();
        ===============結果===================
        6566818
        6566818
        6566818
        6566818
      • 修飾類:括號括起來的部分,作用於所有對象
            @Override
            public void run() {
                    test1();
            }
            public static  void test1(){
                synchronized (MyThread.class) {
                    System.out.println(MyThread.class.hashCode());
                }
            }
                MyThread thread = new MyThread();
                MyThread thread2 = new MyThread();
                Thread t1 = new Thread(thread, "t1");
                Thread t2 = new Thread(thread2, "t2");
                Thread t3 = new Thread(thread, "t3");
                Thread t4 = new Thread(thread, "t4");
        
                t1.start();
                t2.start();
                t3.start();
                t4.start();
        ==========結果=============
        6566818
        6566818
        6566818
        6566818
  • Lock:顯示鎖,依賴特殊的CPU指令;可中斷鎖,多樣化同步,競爭激烈時能維持常態;
    public class MyLock implements Lock {//自定義Lock
        private boolean isLocked = false;
        @Override
        public void lock() {
            while (isLocked){//已經獲得鎖
                try {
                    wait();//等待
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
            isLocked=true;//獲鎖成功
    
        }
    
        @Override
        public void unlock() {
            isLocked = false;//釋放鎖
            notify();//喚醒等待線程
        }
    }

     

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