線程

1.線程和進程的區別

在這裏插入圖片描述

  • 進程是資源分配的最小單位,線程是CPU調度的最小單位
  • 線程不能看作獨立應用,而進程可以看作獨立應用;
  • 進程有獨立的地址空間,相互不影響,線程只是進程執行的不同路徑;
  • 線程沒有獨立的地址空間,多進程的程序比多線程的程序健壯;
  • 進程的切換比線程的切換開銷大;

2.Java進程和線程的關係

  • java對操作系統提供的功能進行封裝,包括進程和線程;
  • 運行一個程序會產生一個進程,進程至少包含一個線程;
  • 每個進程對應一個JVM實例,多個線程共享JVM裏面的堆;
  • java採用單線程編程模型,程序會自動創建主線程;
  • 主線程可以創建子線程,原則上要後於子線程完成執行;

3.Thread.start()和Thread.run()方法的區別

在這裏插入圖片描述

  • 調用start()方法會創建一個新的子線程並啓動;
  • run()方法只是Thread的一個普通方法的調用;

4.Thread和Runnable的區別

  • Thread是實現了Runnable接口的類,使得run支持多線程;
  • 因類的單一繼承原則,推薦多使用Runnable接口;

5.如何給run()方法傳參

  • 構造函數傳參
  • 成員變量傳參
  • 回調函數傳參

6.如何實現處理線程的返回值

  • 主線程等待法
public class ThreadReturn implements Runnable{
   private String runResult;
   @Override
   public void run() {
       try {
           Thread.currentThread().sleep(5000);
       }catch (Exception e){
           e.printStackTrace();
       }
       runResult="我是子線程方法執行想要返回的結果值";
   }
   public String getRunResult() {
       return runResult;
   }

   public static void main(String[] args) {
       ThreadReturn threadReturn=new ThreadReturn();
       Thread thread=new Thread(threadReturn);
       thread.start();
       //主線程等待子線程執行完
       while (threadReturn.getRunResult()==null){
           try {
               Thread.currentThread().sleep(100);
           }catch (Exception e){
               e.printStackTrace();
           }

       }
       System.out.println(threadReturn.getRunResult());
       }
}

弊端: 無法準確地預估全部子線程執行完畢的時間。主線程sleep時間太久,主線程就需要空等;sleep時間太短,子線程又可能沒有全部執行完畢。

  • 使用Thread類的join()堵塞當前線程以等待子線程執行完畢
public class ThreadReturn implements Runnable{
    private String runResult;
    @Override
    public void run() {
        try {
            Thread.currentThread().sleep(5000);
        }catch (Exception e){
            e.printStackTrace();
        }
        runResult="我是子線程方法執行想要返回的結果值";
    }
    public String getRunResult() {
        return runResult;
    }

    public static void main(String[] args) {
        ThreadReturn threadReturn=new ThreadReturn();
        Thread thread=new Thread(threadReturn);
        thread.start();
        //主線程等待子線程執行完
//        while (threadReturn.getRunResult()==null){
//            try {
//                Thread.currentThread().sleep(100);
//            }catch (Exception e){
//                e.printStackTrace();
//            }
//        }
        try {
            thread.join();
        }catch (Exception e){
            e.printStackTrace();
        }
        System.out.println(threadReturn.getRunResult());
        }
}

  • 通過Callable接口實現:通過FutureTask或者線程池獲取
//通過FutureTask獲取線程返回值
public class MyCallable implements Callable<String>{
   @Override
   public String call() throws Exception {
       String value="test result";
       System.out.println("Ready to work");
       Thread.currentThread().sleep(5000);
       System.out.println("Task done");
       return value;
   }

   public static void main(String[] args) {
       FutureTask<String> futureTask=new FutureTask<>(new MyCallable());
       new Thread(futureTask).start();
       if(!futureTask.isDone()){
           System.out.println("Task has not finished,pleas wait");
       }
       try {
           System.out.println("Task return:"+futureTask.get());
       } catch (InterruptedException e) {
           e.printStackTrace();
       } catch (ExecutionException e) {
           e.printStackTrace();
       }
       System.out.println("All is done");
   }
}
//線程池獲取返回值
public class MyCallable implements Callable<String>{
    @Override
    public String call() throws Exception {
        String value="test result";
        System.out.println("Ready to work");
        Thread.currentThread().sleep(5000);
        System.out.println("Task done");
        return value;
    }

    public static void main(String[] args) {
//        FutureTask<String> futureTask=new FutureTask<>(new MyCallable());
//        new Thread(futureTask).start();
//        if(!futureTask.isDone()){
//            System.out.println("Task has not finished,pleas wait");
//        }
//        try {
//            System.out.println("Task return:"+futureTask.get());
//        } catch (InterruptedException e) {
//            e.printStackTrace();
//        } catch (ExecutionException e) {
//            e.printStackTrace();
//        }
//        System.out.println("All is done");
        ExecutorService executorService= Executors.newCachedThreadPool();
        Future future=executorService.submit(new MyCallable());//往線程池中提交線程
        if(!future.isDone()){
            System.out.println("Task has not finished,pleas wait");
        }
        try {
            System.out.println("Task return:"+future.get());
        } catch (InterruptedException e) {
            e.printStackTrace();
        } catch (ExecutionException e) {
            e.printStackTrace();
        }finally {
            executorService.shutdown();//關閉線程池
        }
        System.out.println("All is done");
    }
}

6.線程的6種狀態

在這裏插入圖片描述

  1. 初始(NEW):新創建了一個線程對象,但還沒有調用start()方法。
  2. 運行(RUNNABLE):Java線程中將就緒(ready)和運行中(running)兩種狀態籠統的稱爲“運行”。
    線程對象創建後,其他線程(比如main線程)調用了該對象的start()方法。該狀態的線程位於可運行線程池中,等待被線程調度選中,獲取CPU的使用權,此時處於就緒狀態(ready)。就緒狀態的線程在獲得CPU時間片後變爲運行中狀態(running)。
  3. 阻塞(BLOCKED):表示線程阻塞於鎖,等待獲取排它鎖。
  4. 等待(WAITING):不會分配CPU執行時間,進入該狀態的線程需要等待其他線程做出一些特定動作(通知或中斷)來喚醒。
    沒有設置TIMEOUT參數的Object.wait()方法;
    沒有設置TIMEOUT參數的Thread.join()方法;
    LockSupport.park()方法;
  5. 超時等待(TIMED_WAITING):該狀態不同於WAITING,它可以在指定的時間後自行返回。
    Thread.sleep()方法;
    設置了TIMEOUT參數的Object.wait()方法;
    設置了TIMEOUT參數的Thread.join()方法;
    LockSupport.parkNanos()方法;
    LockSupport.parkUntil()方法;
  6. 終止(TERMINATED):表示該線程已經執行完畢。

7.Thread.sleep()和Object.wait()的區別

  • Thread.sleep()是Thread類的方法,Object.wait是Object類中定義的方法。
  • Thread.sleep()方法可以在任何地方使用,Object.wait()方法只能在synchronized方法或synchronized塊中使用。
    本質區別:
  • Thread.sleep()只會讓出CPU,不會導致鎖行爲的改變。
  • Object.wait()不僅讓出CPU,還會釋放已經佔用的同步資源鎖。
public class WaitSleepDemo {
    public static void main(String[] args) {
        final Object lock=new Object();
        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread A is waiting to get lock");
                synchronized (lock){
                    System.out.println("thread A get lock");
                    try {
                        Thread.sleep(20);
                        System.out.println("thread A do wait method");
                        lock.wait(1000);
                        System.out.println("thread A is done");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();

        try {
            Thread.sleep(10);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        new Thread(new Runnable() {
            @Override
            public void run() {
                System.out.println("thread b is waiting to get lock");
                synchronized (lock){
                    System.out.println("thread b get lock");
                    try {
                        System.out.println("thread b is sleeping 10000ms");
                        Thread.sleep(10000);
                        System.out.println("thread b is done");
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
            }
        }).start();
    }
}

在這裏插入圖片描述

8.Object.notify()和Object.notifyAll()的區別

先談兩個概念:

  • 鎖池EntryList
    假設線程A已經擁有了某個對象(不是類)的鎖,而其它線程B、C想調用這個對象的synchronized方法或塊,由於B、C線程在進入對象的synchronized方法或塊之前必須獲得該對象鎖的擁有權,而恰巧該對象的鎖目前正被A所佔用,此時B、C線程就會被堵塞,進入一個地方去等待鎖的釋放,這個地方便是鎖池。
  • 等待池WaitSet
    假設線程A調用了某個對象的wait()方法,線程A就會釋放該對象的鎖,同時線程A就進入到了該對象的等待池中,進入到等待池中的線程不會去競爭該對象的鎖。
    notify:會隨機從等待池中選取一個線程進入鎖池去競爭獲取鎖的機會。
    notifyAll:會讓所有處於等待池的線程全部進入鎖池去競爭獲取鎖的機會。
public class NotificationDemo {
    private volatile boolean go=false;

    public static void main(String[] args) {
        NotificationDemo test=new NotificationDemo();
        Runnable waitTask=new Runnable() {
            @Override
            public void run() {
                try {
                    test.shouldGo();
                }catch (Exception e){
                    e.printStackTrace();
                }
                System.out.println(Thread.currentThread().getName()+"finished execution");
            }
        };

        Runnable notifyTask=new Runnable() {
            @Override
            public void run() {
                test.go();
                System.out.println(Thread.currentThread().getName()+"finished execution");
            }
        };

        Thread t1=new Thread(waitTask,"WT1");
        Thread t2=new Thread(waitTask,"WT2");
        Thread t3=new Thread(waitTask,"WT3");
        Thread t4=new Thread(notifyTask,"NT1");
        //starting all waiting thread
        t1.start();
        t2.start();
        t3.start();
        //確保所有等待線程都能成功開始執行

        try {
            Thread.sleep(200);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        t4.start();
    }

    private synchronized void shouldGo() throws InterruptedException {
        while (go!=true){
            System.out.println(Thread.currentThread()+"is going to wait on this object");
            wait();//釋放鎖,線程進入等待池,等待喚醒
            System.out.println(Thread.currentThread()+"is woken up");
        }
        go=false;
    }

    private synchronized void go(){
        while (go==false){
            System.out.println(Thread.currentThread()+" is going to notify all or one thread waiting on this object");
            go=true;
            //notify();
            notifyAll();
        }
    }
}

9.Thread.yield()方法

讓當前正在運行的線程回到可運行狀態,以允許具有相同優先級的其他線程獲得運行的機會。因此,使用yield()的目的是讓具有相同優先級的線程之間能夠適當的輪換執行。但是,實際中無法保證yield()達到讓步的目的,因爲,讓步的線程可能被線程調度程序再次選中。yield()不會導致線程轉到等待、阻塞、終止等狀態。

10.synchronized

線程安全問題的主要誘因

  • 存在共享數據(也稱臨界資源)
  • 存在多條線程共同操作這些共享數據
    解決問題的根本方法:
    同一時刻有且只有一個線程在操作共享數據,其他線程必須等到該線程處理完數據後再對共享數據進行操作。

互斥鎖的特性:

  • 互斥性:即在同一時間只允許一個線程持有某個對象鎖,通過這種特性來實現多線程的協調機制,這樣在同一時間只有一個線程對需要同步的代碼塊(複合操作)進行訪問。互斥性也稱爲操作的原子性。
  • 可見性:必須確保在鎖被釋放之前,對共享變量所作的修改,對於隨後獲得該鎖的另一個線程是可見的(即在獲得鎖時應獲得最新共享變量的值),否則另一個線程可能是在本地緩存的某個副本上繼續操作,從而引起不一致。

synchronized 鎖的不是代碼,鎖的都是對象。

根據獲取的鎖的分類:獲取對象鎖獲取類鎖
獲取對象鎖的兩種用法

  • 同步代碼塊(synchronized(this)),synchronized(類實例對象),鎖是小括號()中的實例對象。
  • 同步非靜態方法(synchronized method),鎖是當前對象的實例對象。

獲取類鎖的兩種用法

  • 同步代碼塊(synchronized(類.class)),鎖是小括號()中的類對象(Class對象)。
  • 同步靜態方法(synchronized static method),鎖是當前對象的類對象(Class對象)。

對象鎖和類鎖的總結

  1. 有線程訪問對象的同步代碼塊時,另外的線程可以訪問該對象的非同步代碼塊;
  2. 若鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊時,另外一個訪問對象的同步代碼塊的線程會被堵塞;
  3. 若鎖住的是同一個對象,一個線程在訪問對象的同步方法時,另外一個訪問對象同步方法的線程會被堵塞;
  4. 若鎖住的是同一個對象,一個線程在訪問對象的同步代碼塊時,另一個訪問對象的同步方法的線程會被堵塞,反之亦然;
  5. 同一個 類的不同對象的對象鎖互不干擾;
  6. 類鎖由於也是一種特殊的對象鎖,因此表現和上述1、2、3、4一致,而由於一個類只有一把對象鎖,所以同一個類的不同對象使用類鎖將會是同步的;
  7. 類鎖和對象鎖互補干擾;
public class SynchRunnable implements Runnable{
    @Override
    public void run() {
        String threadName=Thread.currentThread().getName();
        if(threadName.startsWith("A")){
            asyc();
        }else if(threadName.startsWith("B")){
            synchObjectBlock1();
        }else if(threadName.startsWith("C")){
            synchObjectBlock2();
        }else if(threadName.startsWith("D")){
            synchClassBlock1();
        }else if(threadName.startsWith("E")){
            synchClassBlock2();
        }
    }
    public void asyc(){
        System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
    }
    public void synchObjectBlock1(){
        System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (this){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
        }
    }
    public synchronized void synchObjectBlock2(){
        System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
    }
    public void synchClassBlock1(){
        System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
        synchronized (SynchRunnable.class){
            try {
                Thread.sleep(1000);
            } catch (InterruptedException e) {
                e.printStackTrace();
            }
            System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
        }
    }
    public static synchronized void synchClassBlock2(){
        System.out.println(Thread.currentThread().getName()+" start "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
        try {
            Thread.sleep(1000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        System.out.println(Thread.currentThread().getName()+" end "+new SimpleDateFormat("HH:mm:ss").format(new Date()));
    }
    public static void main(String[] args) {
        SynchRunnable synchRunnable=new SynchRunnable();
        Thread a1=new Thread(synchRunnable,"A1");
        Thread a2=new Thread(synchRunnable,"A2");
        Thread b1=new Thread(synchRunnable,"B1");
        Thread b2=new Thread(synchRunnable,"B2");
        Thread c1=new Thread(synchRunnable,"C1");
        Thread c2=new Thread(synchRunnable,"C2");
        Thread d1=new Thread(new SynchRunnable(),"D1");
        Thread d2=new Thread(new SynchRunnable(),"D2");
        Thread e1=new Thread(new SynchRunnable(),"E1");
        Thread e2=new Thread(new SynchRunnable(),"E2");
        a1.start();
        a2.start();
        b1.start();
        b2.start();
        c1.start();
        c2.start();
        d1.start();
        d2.start();
        e1.start();
        e2.start();
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章