Java 之多線程

1 概述

  • 進程

進程就是正在運行的程序。進程是系統進行資源分配和調度的獨立單位,每個進程都有自己的內存空間和系統資源。

多進程的意義:可以在一個時間段內執行多個任務,提高CPU的使用率。

  • 線程

在同一個進程內可以執行多個任務,而每一個任務都可以看做是一個線程。

線程是程序的執行單元(執行路徑),是程序使用CPU的最基本單元

多線程的意義:提高程序的使用率。

因爲多個線程共享一個進程的資源(堆內存和方法區),但是棧內存是獨立的,一個線程一個棧。
在一個時間點上,CPU只能執行一個線程。而線程的執行需要搶佔CPU資源,所以線程的運行具有隨機性。

  • 併發和並行

並行:邏輯上的同時發生,指在某一個時間段同時運行多個程序

併發:物理上的同時發生,指在某一個時間點同時運行多個程序

2 多線程的常見實現方式

  • 繼承 Thread 類

創建繼承 Thread 類的自定義類,並重寫其 run() 方法,該方法的方法體即爲線程執行的內容。接下來創建自定義類的對象,使用對象調用其 start() 方法即可啓動線程

public class MyThread extends Thread
{
	@Override
	public void run()
	{
		for(int i = 0; i < 10; i++)
		{
			System.out.println("hello thread");
			try
			{
				Thread.sleep(500);
			} 
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
}

public class ThreadDemo
{
    public static void main(String[] args)
    {
        MyThread myTh = new MyThread();
        myTh.start();
    }
}
  • 實現 Runnable 接口

創建一個實現 Runnable 接口的自定義類,並重寫該接口的 run() 方法,該方法的方法體即爲線程執行的內容。接下來創建該自定義類的對象,並把這個對象作爲參數傳遞給 Thread 的有參構造函數,生成 Thread 對象,最後調用 Thread 對象的 start() 方法即可啓動線程。

public class MyRunnalbe implements Runnable
{
	@Override
	public void run()
	{
		for(int i = 0; i < 10; i++)
		{
			System.out.println("hello runnable");
			try
			{
				Thread.sleep(500);
			} 
			catch (InterruptedException e)
			{
				e.printStackTrace();
			}
		}
	}
}

public class ThreadDemo
{
    public static void main(String[] args)
    {
        MyRunnalbe rb = new MyRunnalbe();
        Thread Th = new Thread(rb);
        Th.start();
    }
}
  • 使用線程池

3 Thread 類常見方法

String getName():獲取線程名稱

void setName(String name):設置線程名稱

int getPriority():獲取線程優先級

void setPriority(int newPriority):設置線程優先級,範圍爲 1~10 , 10 的優先級最高,線程優先級越高,獲取 CPU 的概率越高。

static void sleep(long millis):線程休眠

final void join():調用該方法的線程執行完畢後其他線程才能執行

static void yield():調用該方法的線程讓出 CPU 執行權,所有線程重新搶佔 CPU 執行權

void setDaemon():將調用方法的線程設置成守護線程(後臺線程),當所有的非守護線程結束時,守護線程自動結束。如果程序運行的線程全都是守護線程,JVM 將會自動退出。

void interrupt():中斷線程,原理是終止線程狀態,拋出一個InterruptException

4 線程的聲明週期

新建:創建線程對象

就緒:線程對象調用 start() 方法,可以執行,但需要搶到 CPU 資源

運行:線程搶到 CPU 資源,執行

阻塞:調用 sleep()、wait() 等操作讓線程處於阻塞狀態,此時線程不能執行,也不能搶佔 CPU;sleep() 時間到、notify() 等操作將讓線程脫離阻塞狀態,使其處於就緒狀態。

死亡:線程對象變成垃圾,等待回收。

5 線程安全

5.1 出現原因

● 多線程

● 共享數據

● 多條語句操作共享數據

由於前兩點我們無法避免,只能從第三點下手,當一個線程在對共享數據進行操作的時候,其他線程無法進行操作。

5.2 常用解決辦法

● 同步代碼塊

synchronized(Object lockObj)
{
    需要被同步的代碼;
}

注意:同步代碼塊的鎖對象是任意對象,所有線程都應該使用同一個 lockObj,同步雖然解決了線程安全問題,但當線程比較多的時候,每個線程都會去判斷同步上的鎖,這是很耗費資源的,無形之中降低了程序的運行效率,而且可能出現死鎖(同步嵌套時容易出現)。

● 同步方法

權限修飾符 synchronized 返回值類型 方法名(參數列表)
{
    需要被同步的代碼;
}

注意:同步方法的鎖對象是 this靜態同步方法的鎖對象是當前類的 Class 對象

● 使用 Lock(接口) 

Lock lock = ReentrantLock();
try
{ 
    lock.lock();
    需要被同步的代碼;
}
finally
{
    lock.unlock();
}

● 死鎖

當兩個或兩個以上的線程在執行過程中,因爭奪資源產生的一種相互等待的現象。

public class MyLock
{
    public static final Object lockObjA = new Object();
    public static final Object lockObjB = new Object();
}

public class DieLock extends Thread
{
    puoblic boolean flag;

    @Override
    public void run()
    {
        if(flag)
        {
            synchronized(MyLock.lockObjA)
            {
                System.out.println("if lockObjA");
                
                synchronized(MyLock.lockObjB)
                {
                    System.out.println("if lockObjB");
                }
            }
        }
        else
        {
            synchronized(MyLock.lockObjB)
            {
                System.out.println("if lockObjB");
                
                synchronized(MyLock.lockObjA)
                {
                    System.out.println("if lockObjA");
                }
            }
        }
    }
}

public class DieLockDemo
{
    public static void main(String[] args)
    {
        DieLock dl1 = new DieLock();
        DieLock dl2 = new DieLock();

        dl1.flag = true;
        dl2.flga = false;

        dl1.start();
        dl2.start();
    }
}

上面的例程最理想的狀態 dl1 完成執行,dl2 再執行,運行結果爲 if lockObjA; if lockObjB; else lockObjB; else lockObjA;

不過大多數情況是 dl1 已經獲取到 MyLock.lockObjA 即將獲取 MyLock.lockObjB,而 dl2 已經獲取到 MyLock.lockObjB,即將獲取 MyLock.lockObjA;這時 dl1 等待 dl2 釋放 MyLock.lockObjB,而 dl2 等待 dl1 釋放 MyLock.lockObjA,兩個線程相互等待就形成了死鎖。

6 線程通信

6.1 概述

不同種類的線程針對同一個資源的操作(生產、消費者模式)。

6.2 等待喚醒機制

void notify():喚醒調用方法的鎖對象上的單個等待線程

void notifyAll():喚醒調用方法的鎖對象上的所有等待線程

void wait():讓當前線程進入等待,並立即釋放鎖,被喚醒後將會繼續等待前的狀態。

注意:

        以上方法都需要通過鎖對象來調用

6.3 案例

public class Student
{
    public String name;
    public int age;
    public boolean hasData;
}

public class ProducerThread implements Runnable
{
    private Student student;
    private int x = 0;
    
    public ProducerThread(Student student)
    {
        this.student = student;
    }

    @Override
    public void run()
    {
        while(true)
        {
            synchronized(student)
            {
                if(student.hasData)
                {
                    student.wait();
                }
                
                if(x % 2)
                {
                    student.name = "宋子傑";
                    student.age = 18;
                }
                else
                {
                    student.name = "陳靜";
                    student.age = 20;
                }

                x++;
                student.hasData = true;
                student.notify();
            }
        }
    }
}

public class ConsumerThread implements Runnable
{
    private Student student;
    
    public ConsumerThread(Student student)
    {
        this.student = student;
    }

    @Override
    public void run()
    {
        while(true)
        {
            synchronized(student)
            {
                if(!student.hasData)
                {
                    student.wait();
                }
                
                System.out.println(s.name + "----" + s.age);
                student.hasData = false;
                student.notify();
            }
        }
    }
}

public class StudentDemo
{
    public static void main(String[] args)
    {
        Student student = new Student();

        ProducerThread pt = new ProducerThread(student);
        ConsumerThread ct = new ConsumerThread(student);
        
        pt.start();
        ct.start();
    }
}

6.4 線程狀態轉換

7 線程池

7.1 概述

每次創建線程都要和系統進行交互,比較耗費資源。而使用線程池,線程結束後不會死亡,而是被線程池回收,達到線程多次使用的目的。

7.2 使用

● 創建線程池對象

● 定義實現 Runnable 或者 Callable 接口的類

● 調用如下方法

    Future<?> submit(Runnable task)

    <T> Future<T> submit(Callable<T> task)

    調用shutdown()釋放線程池

7.3 案例

public class MyCallable<T> implements Callable<T>
{
    @Override
    public T call() throws Exception
    {
        線程執行的內容;
    }
}

public class CallableDemo
{
    public static void main(String[] args)
    {
        ExecutorService pool = Executors.newFixedThreadPool(2);
        pool.submit(new MyCallable<new Object());
    }
}

使用Runnable實現類和上述代碼類似。

注意:

     線程池的線程可以求返回值。

8 線程組

8.1 概述

java中用ThreadGroup來表示線程組,它可以對一批線程進行分類管理,java允許程序直接對線程組進行控制。

默認情況下,所有的線程都屬於主線程組,但也可以顯式的指定新建線程的線程組。

final ThreadGroup getThreadGoup():獲取調用該方法的線程對象所屬的線程組

Thread(ThreadGroup threadGroup,Runnable target,String name):通過構造方法設置線程組

8.2 用法

ThreadGroup tg = new ThreadGroup("ThreadGroup 1");

Runnable ra = new MyRunnable();

Thread th1 = new Thread(tg, ra, "thread_1");

Thread th2 = new Thread(tg, ra, "thread_2");

tg.destory(); //銷燬組內所有線程

tg.setDaemon(true); //將組內所有線程都設置成後臺線程

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