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); //將組內所有線程都設置成後臺線程