程序師世界是廣大編程愛好者互助、分享、學習的平台,程序師世界有你更精彩!
首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫知識
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
 程式師世界 >> 編程語言 >> JAVA編程 >> JAVA編程入門知識 >> Java基礎--多線程的方方面面

Java基礎--多線程的方方面面

編輯:JAVA編程入門知識

1,什麼是線程?線程和進程的區別是什麼?

2,什麼是多線程?為什麼設計多線程?

3,Java種多線程的實現方式是什麼?有什麼區別?

4,線程的狀態控制有哪些方法?

5,線程安全、死鎖和生產者--消費者

6,線程的優化有哪些方法?

1,什麼是線程?線程和進程的區別是什麼?

  線程是程序執行的最小單元。

    區別: 進程是操作系統進行資源處理和分配的最小單位,而一個進程可以包含多個線程,並共享進程的資源。

2,什麼是多線程?為什麼設計多線程?

  介紹之前,我們需要理解並行和並發的定義:

  並行:同一個時刻有多個線程進行。

      並發:同一個時間段內有多個線程進行。

  多線程指的是一個進程可以包含多個並發的線程(同一個時刻只有一個線程運行)。例如酷狗,我們可以一邊聽歌一邊搜索自己喜歡的歌曲。多線程的存在能夠讓進程及時處理我們多項的請求,提高應用程序的利用率。

  多線程編程需要了解到多線程運行面臨的問題。

      • 既然一個進程的多個線程共享進程的資源,怎樣保證有多個線程訪問同一資源時單個線程的訪問不受其它線程的干擾。這是線程安全問題。
      • 多線程怎麼控制線程的執行順序,這是線程調度問題。ps:Java對多線程調度執行搶占式,每個線程有個優先級屬性(1--10,10最高),優先級高的有限執行。

3,Java種多線程的實現方式是什麼?有什麼區別?

  Java實現多線程有兩個方式。

    • 繼承Thread類,重寫run()方法,代碼如下:

    線程類MyThread:

public class MyThread extends Thread{
    @Override
    public void run() {
        for(int x=0;x<200;x++){
            System.out.println(x);
        }
    }    
}
View Code

          主類Demo:

public class Demo {
    public static void main(String[] args) {
        MyThread mt = new MyThread();//新建線程類對象
        MyThread mt1 = new MyThread();
        mt.start();//調用start()方法
        mt1.start();
    }
}
View Code

       ps,為什麼不直接調用線程類的run()方法,而調用start()方法?

      run()方法只是封裝了多線程執行的操作,只是一個普通方法。

                start()方法是啟動線程執行的方法,由JVM自動調用run()方法。

    • 實現Runnable 接口,重寫run()方法,重點是主類調用的時候不同。

步驟:1,編寫實現Runnable 接口的類,重寫run()方法

              public class ThreadRunnable implements Runnable { public void run(){}}

        2,在主類中新建線程類對象,obj

              ThreadRunnable tr = new ThreadRunnable(); 

        3新建Thread類t,將obj作為t的構造參數

               Thread t = new Thread(tr);              

        4,調用t的start()方法。

    • 繼承類Thread和實現Runnable接口對比

      由於Java只允許單類繼承,故多選用實現Runnable接口的方法創建多線程,事實上Thread類也是接口Runnable的實現類。

         public class Thread extends Object implements Runnable    

4,線程的狀態控制有哪些方法?

       線程狀態控制常用到的方法如下:

    • 線程睡眠sleep(long millis)

      t.sleep(1000);讓線程t睡眠1000毫秒,即1秒。

    • 線程加入join()   

      A.start();

           A.join();//try catch

      B.start();

      C.start();

      A執行完之後B和C才可以執行

    • 線程禮讓static void yield()

      A.yield();A暫停一下,時間不確定,讓同等級的線程優先運行。

    • 線程中斷interrupt()

      A.interrupt();把線程的狀態中止,並拋出 InterruptedException 。跳出阻塞的部分可以繼續執行接下來的代碼。    

    interrupt()只是改變中斷狀態而已. interrupt()不會中斷一個正在運行的線程。這一方法實際上完成的是,給受阻塞的線程拋出一個中斷信號,這樣受阻線程就得以退出阻塞的狀態。更確切 的說,如果線程被Object.wait, Thread.join和Thread.sleep三種方法之一阻塞, 它將接收到一個中斷異常(InterruptedException),從而提早地終結被阻塞狀態。

     如果線程沒有被阻塞,這時調用interrupt()將不起作用;否則,線程就將得到InterruptedException異常(該線程必須事先預備好處理此狀況),接著逃離阻塞狀態。

          ps:對於一個正在運行的線程如果想要其結束運行,可以使用標志位,讓線程跳出從而結束運行,示例如下:

public class ThreadFlag extends Thread 
{ 
    public volatile boolean exit = false; 

    public void run() 
    { 
        while (!exit); 
    }
}
View Code
    • 線程等待喚醒wait() notify()。這兩個方法並不是線程類的方法,而是鎖的方法,在接下一節介紹。

5,線程安全、死鎖和生產者--消費者

  我們來看下面的一段代碼:

public class ThreadRunnable implements Runnable {
    private static int D = 100;//D是靜態變量由多個線程共享。public void run() {
        
            while(true){if(D>0){
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"---"+(D--));
                }
                
           }    
    }//run
}
View Code

 

    由於D是靜態變量,它由ThreadRunnable的所有對象訪問,每個對象對它進行輸出,並減1的操作,直到D為0(可將D假想成某直達列車的票,每  個ThreadRunnable對象是一個售票窗口)假設有三個窗口如下:

public class ThreadRunnableDemo {
    public static void main(String[] args) {
        ThreadRunnable tr = new ThreadRunnable();//創建接口對象
        Thread t1 = new Thread(tr,"窗口1");//創建Thread類,將上述對象作為構造參數
        Thread t2 = new Thread(tr,"窗口2");//創建Thread類,將上述對象作為構造參數
        Thread t3 = new Thread(tr,"窗口3");//創建Thread類,將上述對象作為構造參數
        t1.start();//啟動start方法
        t2.start();
        t3.start();
        
    }
}
View Code

        輸出:

....
窗口1---97
窗口2---97
....
....
窗口3---2
窗口1---1
窗口2---0
窗口3----1
View Code

 

 出同號票的原因分析:

 

出現0號和負號票的原因分析:

     這就產生了線程不安全的問題,產生線程不安全的場景:

     多個線程訪問同一資源,並對資源進行多條語句操作就有可能引發線程不安全。

         概括:多個線程;同一資源;不是原子操作

    前兩個條件我們無法改變,我們有的解決思路就是將線程對資源操作語句封裝成原子操作(不會被打斷)。將操作封裝成原子操作。

          Java使用synchronized關鍵字。

          使用規范:

    •  對共享代碼塊進行鎖:synchronized(鎖對象){共享代碼塊} 鎖對象可以是任意的
public void run() {
            while(true){
            synchronized(new Object()){
                if(D>0){
                    try {
                        Thread.currentThread().sleep(100);
                    } catch (InterruptedException e1) {
                        e1.printStackTrace();
                    }
                    System.out.println(Thread.currentThread().getName()+"---"+(D--));
                }  
           }
        }
}//run()
View Code
    • 對方法進行鎖:private synchronized void function_name(){} 此時鎖對象是this
    • 當方法是靜態時private static synchronized void function_name(){},鎖對象是線程類字節碼文件對象。

      下面考慮更復雜的情況------死鎖

       死鎖顧名思義就是加的鎖打不開,一般發生在當兩個線程互相拿著對方的鎖即鎖嵌套,造成兩個線程一直處於等待中。死鎖示例:

      

           經典的生產者和消費者問題

           問題描述:生產者producer生產資源,消費者customer消耗資源呢,我們設計的程序最低保證消費者在消耗資源時必須保證有資源。

           設計思想:消費者和生產者共用一個鎖,消費者一直消費資源,直到剩余資源數小於規定(0或者業務目標),消費者線程進入等待,直到生產者生產資源後將自己喚醒。

           實現:

    Producer類

package producer;

import java.util.ArrayList;

public class Producer implements Runnable {
    
    private ArrayList<String> al;
    public Producer(ArrayList<String> al){
        this.al = al;
    }
    
    @Override
    public void run() {
        while(true){
            synchronized (al) {
                while(al.size()>0){
                    try {
                        al.wait();//只要有資源,生產線程就wait()
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                for(int i=0;i<10;i++){
                    al.add(""+i);
                }
                al.notify();//生產完成,喚醒等待線程即消費者線程
            }
        }    
    }
}
View Code

          Customer類

package producer;

import java.util.ArrayList;

public class Customer implements Runnable {
    private ArrayList<String> al;
    public Customer(ArrayList<String> al){
        this.al = al;
    }
    @Override
    public void run() {
        while(true){
            synchronized (al) {
                while(al.size()<1){
                    try {
                        al.wait();//如果沒資源,消費者線程就wait()
                    } catch (InterruptedException e) {
                        e.printStackTrace();
                    }
                }
                while(al.size()>0){
                    System.out.println("消費者:"+al.size());
                    al.remove(0);
                    
                }
                al.notify();//消費沒了,喚醒等待線程即生產者線程
            }
        }        
    }
}
View Code

           調用類Demo

package producer;

import java.util.ArrayList;

public class Demo {
    private static ArrayList<String> al= new ArrayList<String>();
    public static void main(String[] args) {
        
        Producer p = new Producer(al);
        Customer c = new Customer(al);
        
        Thread producer = new Thread(p);
        Thread customer = new Thread(c);
        
        producer.start();
        customer.start();
        
    }
}
View Code

6,線程的優化有哪些方法?

    • 線程池

      實際業務場景中線程的壽命都很短暫,例如對於網站訪問,每個用戶請求是一個線程,如果來一個用戶,進行一套線程的創建、就緒等動作會嚴重影響

服務器的響應效率,鑒於此,Java中有了線程池的解決辦法,它的思想是程序初始運行時在一個容器內新建固定數量的線程,當用到時從容器內取出一個線程,

線程執行完之後再放回到容器內,實質是以空間換時間,這個容器在Java中就被稱為線程池。

      Java實現線程池

      Executors類工廠類

     方法:

      public static ExecutorService new FixedThreadPool(int nThreads);//該方法返回一個含有n個線程的線程池接口

      線程池接口:ExecutorService

      方法:

  submit(Runnable task);//將一個線程類加入到線程池

     結束:shutdown()

 

ps:福利:

 

 

 

  1. 上一頁:
  2. 下一頁:
Copyright © 程式師世界 All Rights Reserved