Java多線程編程

課程概要:

進(jìn)程與線程

  1. 線程中單獨(dú)順序的控制流
    線程本身依靠程序進(jìn)行運(yùn)行
  1. 進(jìn)程:執(zhí)行中的程序
    一個(gè)進(jìn)程可以包含一個(gè)或者多個(gè)線程
    一個(gè)進(jìn)程至少要包含一個(gè)線程
  2. 單線程
    程序中只存在一個(gè)線程,實(shí)際主方法就是一個(gè)主線程
  3. 多線程:
    多線程是在一個(gè)程序中運(yùn)行多個(gè)任務(wù)
    多線程的目的是更好地使用CPU資源

線程的實(shí)現(xiàn)

第一種方式:通過繼承thread方式實(shí)現(xiàn)進(jìn)程(需要調(diào)用thread.start()方法才能實(shí)現(xiàn)線程的并發(fā)

public class MyThread  extends Thread{
            private String name;//用來標(biāo)示線程
            public MyThread(String name) {
                        this.name=name;
                        }
            @Override
            public void run() {//必須重寫的方法
                        for(int i=1;i<=1000;i++)
                        {
                            System.out.print(name+i);
                            if(i%10==0){
                              System.out.println();
                             }
            }
                        super.run();
            }
}


            MyThread t1=new MyThread("A");
                        MyThread t2=new MyThread("b");
                        t1.start();
                        t2.start();
}


第二種方式:
通過實(shí)現(xiàn)runnable 方法,然后獲得runnable對(duì)象,將runnable對(duì)象傳遞給Thread 的構(gòu)造方法,創(chuàng)建thread對(duì)象。然后調(diào)用thread.start()
即可實(shí)現(xiàn)。

示例代碼:

public class MyRunnable implements Runnable{
            private String name;
            public MyRunnable(String name) {
                        this.name=name;
                        
            }
            @Override
            public void run() {
                        for(int i=1;i<=1000;i++)
                        {
                            System.out.print(name+i);
                             if(i%10==0){
                              System.out.println();
                                      }
                        
            }
}
            }

            MyRunnable r1=new MyRunnable("a");
                        MyRunnable r2=new MyRunnable("b");
                        Thread t1=new Thread(r1);
                        Thread t2=new Thread(r2);
                        t1.start();
                        t2.start();
            }

輸出結(jié)果:(并發(fā)機(jī)制誰搶到CPU資源就誰執(zhí)行)

線程的狀態(tài)

線程的常用方法

  1. 取得線程名稱
    getName()
  1. 取得當(dāng)前線程對(duì)象
    currentThread()
  2. 判斷線程是否啟動(dòng)
    isAlive()
  3. 線程的強(qiáng)行運(yùn)行
    join()
  4. 線程的休眠
    sleep()
    將使任務(wù)終止執(zhí)行給定的時(shí)間。
  5. 線程的讓步
    yield()
    這個(gè)方法按時(shí)這個(gè)線程的工作已經(jīng)完成得差不多了,可以讓別的線程使用CPU了,同事也是在建議其他具有相同優(yōu)先級(jí)的線程可以運(yùn)行。

示例代碼(1 2 3):

class MyRunnable2 implements Runnable{
   private String name;
            public MyRunnable2(String name) {
                        this.name=name;
            }
            @Override
            public void run() {   
                        for(int i=0;i<5;i++)
                        {
                           System.out.println("線程正在運(yùn)行~~~");                 
                        }
            
            }
            
}
public class ThreadDemo02 {
            public static void main(String[] args) {
                        MyRunnable2 r1=new MyRunnable2("jun");
                        Thread t1=new Thread(r1);
                        t1.start();
                        System.out.println("線程是否啟動(dòng):"+t1.isAlive());
                        System.out.println("當(dāng)前線程對(duì)象是:"+Thread.currentThread());
                        System.out.println("取得線程名稱:"+t1.getName());
                        }
}


輸出結(jié)果:

4.join
在其他線程里使用join,能強(qiáng)行讓方法對(duì)象線程執(zhí)行。
例如在主線程里邊強(qiáng)行執(zhí)行自定義線程
示例代碼:

public class ThreadDemo02 {
            public static void main(String[] args) {
                        MyRunnable2 r1=new MyRunnable2("jun");
                        Thread t1=new Thread(r1);
                        t1.start();
               for(int i=0;i<=50;i++){
                      if(i>10){
                          try {
                             t1.join();
                        } catch (InterruptedException e) {
                              e.printStackTrace();
                           }
                           }
                           System.out.println("主線程"+i);
               }
                        }
}

運(yùn)行結(jié)果:


sleep:
示例代碼:
在run方法里邊調(diào)用

public void run() {   
        for(int i=0;i<5;i++)
            {
                try {
                Thread.sleep(1000);
                 System.out.println("線程正在運(yùn)行~~~");  
                   } catch (InterruptedException e) {
                       e.printStackTrace();
                     }                                                                                   
                   }
            
            }

結(jié)果:控制臺(tái)每隔一秒輸出
線程正在運(yùn)行~~~

線程的優(yōu)先級(jí)


常用方法:
getPriority()讀取現(xiàn)有線程的優(yōu)先級(jí)
setPriority()設(shè)置或者修改線程的優(yōu)先級(jí)

  • 注意:
    1.java調(diào)度器傾向于讓優(yōu)先級(jí)別高的線程先執(zhí)行(不保證),這并不意味著優(yōu)先級(jí)別比較低的線程就不執(zhí)行(優(yōu)先級(jí)別并不會(huì)導(dǎo)致死鎖)。優(yōu)先級(jí)別低的線程僅僅是執(zhí)行頻率比較低。
    2.jdk有10個(gè)優(yōu)先級(jí),但與多數(shù)操作系統(tǒng)都不能映射得很好(比如Windows有七個(gè)優(yōu)先級(jí))。推薦可移植的方法是調(diào)整優(yōu)先級(jí)的時(shí)候只是用MAX_PRIORITY 、 NORM_PRIORITY 、 MIN_PRIORITY

示例代碼:

public class ThreadDemo02 {

    public static void main(String[] args) {
        MyRunnable1 r1=new MyRunnable1("min");
        MyRunnable1 r2=new MyRunnable1("normal");
        MyRunnable1 r3=new MyRunnable1("max");

        Thread t1=new Thread(r1);
        Thread t2=new Thread(r2);
        Thread t3=new Thread(r3);
        t1.setPriority(Thread.MIN_PRIORITY);
        t2.setPriority(Thread.NORM_PRIORITY);
        t3.setPriority(Thread.MAX_PRIORITY);        
        t1.start();
        t2.start();
        t3.start();
        }
}


輸出結(jié)果:

一開始按順序執(zhí)行,后來優(yōu)先級(jí)別高的線程先執(zhí)行。

同步與死鎖


多線程線程執(zhí)行一個(gè)runnable對(duì)象,由于線程無法共享,會(huì)產(chǎn)生無法預(yù)計(jì)的錯(cuò)誤。
一般在資源需要共享的時(shí)候需要同步。
示例代碼:
創(chuàng)建三個(gè)線程完成一個(gè)run方法,當(dāng)ticket數(shù)為0的時(shí)候停止,預(yù)計(jì)結(jié)果為5、4、3、2、1

class MyRunnable1 implements Runnable{
   private String name;
   private int tickets=5;
    public MyRunnable1(String name) {
        this.name=name;
    }
    @Override
    public void run() { 
        for(int i=0;i<5;i++)
        {
            if(tickets>0){
                try {
                    Thread.sleep(500);
                    System.out.println(Thread.currentThread()+"車票"+tickets--);
                } catch (InterruptedException e) {
                    // TODO Auto-generated catch block
                    e.printStackTrace();
                }
                
            }
                    
        }
    
    }
    
}


public class ThreadDemo02 {

    public static void main(String[] args) {
        MyRunnable1 r1=new MyRunnable1("me");
    

        Thread t1=new Thread(r1);
        Thread t2=new Thread(r1);
        Thread t3=new Thread(r1);
        t1.start();
        System.out.println(t1.getName());
        t2.start();
        System.out.println(t2.getName());
        t3.start();
        System.out.println(t3.getName());
        }
}

輸出結(jié)果:

為了解決這個(gè)問題實(shí)現(xiàn)線程同步,可以使用synchronized關(guān)鍵字

synchronized關(guān)鍵字

它的修飾對(duì)象有幾種:

  1. 修飾一個(gè)類,其作用的范圍是synchronized后面括號(hào)括起來的部分,作用的對(duì)象是這個(gè)類的所有對(duì)象
  1. 修飾一個(gè)方法,被修飾的方法稱為同步方法,其作用的范圍是整個(gè)方法,作用的對(duì)象是調(diào)用這個(gè)方法的對(duì)象;
  2. 修改一個(gè)靜態(tài)的方法,其作用的范圍是整個(gè)靜態(tài)方法,作用的對(duì)象是這個(gè)類的所有對(duì)象;
  3. 修飾一個(gè)代碼塊,被修飾的代碼塊稱為同步語句塊,其作用的范圍是大括號(hào){}括起來的代碼,作用的對(duì)象是調(diào)用這個(gè)代碼塊的對(duì)象;
    修飾一個(gè)類
    其作用的范圍是synchronized后面括號(hào)括起來的部分,作用的對(duì)象是這個(gè)類的所有對(duì)象,如下代碼:
 public void method() {
 synchronized(ClassName.class) {
 // todo
 }
 }
}

修飾一個(gè)方法
Synchronized修飾一個(gè)方法很簡(jiǎn)單,就是在方法的前面加synchronized,例如:
public synchronized void method(){ // todo}

另外,有幾點(diǎn)需要注意:

  1. 在定義接口方法時(shí)不能使用synchronized關(guān)鍵字。
  2. 構(gòu)造方法不能使用synchronized關(guān)鍵字,但可以使用synchronized代碼塊來進(jìn)行同步。
  3. synchronized 關(guān)鍵字不能被繼承。如果子類覆蓋了父類的 被 synchronized 關(guān)鍵字修飾的方法,那么子類的該方法只要沒有 synchronized 關(guān)鍵字,那么就默認(rèn)沒有同步,也就是說,不能繼承父類的 synchronized。
    修飾靜態(tài)方法
    我們知道靜態(tài)方法是屬于類的而不屬于對(duì)象的。同樣的,synchronized修飾的靜態(tài)方法鎖定的是這個(gè)類的所有對(duì)象。如下:
    public synchronized static void method() { // todo}

修飾代碼塊

//todo
}```
* 當(dāng)兩個(gè)并發(fā)線程訪問同一個(gè)對(duì)象object中的這個(gè)synchronized(this)同步代碼塊時(shí),一個(gè)時(shí)間內(nèi)只能有一個(gè)線程得到執(zhí)行。另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。

* 當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),另一個(gè)線程仍然可以訪問該object中的非synchronized(this)同步代碼塊。

* 尤其關(guān)鍵的是,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),其他線程對(duì)object中所有其它synchronized(this)同步代碼塊的訪問將被阻塞。

第三個(gè)例子同樣適用其它同步代碼塊。也就是說,當(dāng)一個(gè)線程訪問object的一個(gè)synchronized(this)同步代碼塊時(shí),它就獲得了這個(gè)object的對(duì)象鎖。結(jié)果,其它線程對(duì)該object對(duì)象所有同步代碼部分的訪問都被暫時(shí)阻塞。
以上規(guī)則對(duì)其它對(duì)象鎖同樣適用.

##線程的生命周期

![](http://upload-images.jianshu.io/upload_images/2916604-5f15789491d27af7.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容