文章目錄
一、程序、進程、線程的關係
- 程序(program)
是爲完成特定任務、用某種語言編寫的一組指令的集合。即指一段靜態的代碼。 - 進程(process)
程序的一次執行過程,或是正在運行的一個程序。
進程作爲資源分配的單位,系統在運行時會爲每個進程分配不同的內存區域 - 線程(thread)
概念:進程可進一步細化爲線程,是一個程序內部的一條執行路徑。
說明:線程作爲調度和執行的單位,每個線程擁獨立的運行棧和程序計數器(pc),線程切換的開銷小。
參考上圖
進程可以細化爲多個線程。
每個線程,擁有自己獨立的:棧、程序計數器
多個線程,共享同一個進程中的結構:方法區、堆。
二、並行與併發
- 並行: 多個人幹不同的事,如工廠的多條生產線。
- 併發:多個人同時做一個事情,如春運搶票。
三、創建多線程
方式一:繼承java.lang.Thread類
步驟分析:
- 創建一個類,繼承Thread。
- 重寫run方法。
- 創建繼承Thread的對象。
- 啓動線程。
代碼實現:
/**
* 1. 通過 extends Thread 創建線程
* 2. 創建匿名子類
*/
public class ThreadExtendsTest {
public static void main(String[] args) {
//3. 創建繼承Thread的對象
MyThread thread1 = new MyThread();
MyThread thread2 = new MyThread();
//4. 啓動線程
thread1.start();
thread2.start();
//附:創建匿名子類
new Thread(() -> {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}).start();
}
}
//1. 創建一個類,繼承Thread
class MyThread extends Thread {
//2. 重寫run方法
@Override public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
方式二:實現java.lang.Runnable接口
步驟分析
- 創建實現Runnable接口的實現類。
- 重寫run()方法。
- 創建實現Runnable接口的實現類。
- 創建線程對象,將實現Runnable的實現類的實例化對象傳入構造器。
- 啓動線程。
代碼實現
/**
* 1. 通過implements Runnable 創建線程
* 2. priority設置線程優先級
*/
public class ThreadImplementsTest {
public static void main(String[] args) {
//3. 創建實現Runnable接口的實現類
ThreadRunnable tr = new ThreadRunnable();
//4. 創建線程對象,將實現Runnable的實現類的實例化對象傳入構造器
//tips: 如將tr傳入
Thread thread1 = new Thread(tr);
Thread thread2 = new Thread(tr);
//5. 啓動線程
thread1.start();
thread2.start();
}
}
// 1. 創建實現Runnable接口的實現類
class ThreadRunnable implements Runnable {
//2. 重寫run()方法
@Override public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
方式三:實現java.util.concurrent.Callable接口
步驟分析
- 創建一個實現Callable的實現類。
- 實現call方法,將此線程需要執行的操作聲明在call()中。
- 創建Callable接口實現類的對象
- 創建FutureTask的對象,將Callable接口實現類的對象傳遞到FutureTask構造器中
- 將FutureTask的對象作爲參數傳遞到Thread類的構造器中,創建Thread對象,並調用start()
代碼實現
import java.util.concurrent.Callable;
import java.util.concurrent.FutureTask;
public class CallableThread {
public static void main(String[] args) {
//3.創建Callable接口實現類的對象
CallableThreads callableThreads = new CallableThreads();
//4.創建FutureTask的對象,將Callable接口實現類的對象傳遞到FutureTask構造器中
FutureTask futureTask1 = new FutureTask(callableThreads);
FutureTask futureTask2 = new FutureTask(callableThreads);
//5.將FutureTask的對象作爲參數傳遞到Thread類的構造器中,創建Thread對象,並調用start()
Thread thread1 = new Thread(futureTask1);
Thread thread2 = new Thread(futureTask2);
thread1.start();
thread2.start();
}
}
//1.創建一個實現Callable的實現類
class CallableThreads implements Callable {
//2.重寫call方法,將此線程需要執行的操作聲明在call()中
@Override public Object call() throws Exception {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
return null;
}
}
方式四:使用線程池
步驟分析
- 創建實現Runnable接口的實現類。
- 重寫run方法。
- 創建指定線程數量的線程池。
- 執行指定的線程的操作。需要提供實現Runnable接口或Callable接口實現類的對象。
- 關閉線程池。
代碼實現
import java.util.concurrent.ExecutorService;
import static java.util.concurrent.Executors.newFixedThreadPool;
public class ThreadPool {
public static void main(String[] args) {
//3. 創建指定線程數量的線程池
ExecutorService service = newFixedThreadPool(20);
//4.執行指定的線程的操作。需要提供實現Runnable接口或Callable接口實現類的對象
service.execute(new ThreadRunnable2());
service.execute(new ThreadRunnable2());
//5.關閉線程池
service.shutdown();
}
}
//1. 創建實現Runnable接口的實現類。
class ThreadRunnable2 implements Runnable {
//2. 重寫run方法
@Override public void run() {
for (int i = 0; i < 100; i++) {
if (i % 2 == 0) {
System.out.println(Thread.currentThread().getName() + ":" + i);
}
}
}
}
四、Thread類中的常用的方法
- start():啓動當前線程;調用當前線程的run()
- run(): 通常需要重寫Thread類中的此方法,將創建的線程要執行的操作聲明在此方法中,在Thread對象中調用則在當前線程中直接調用run()方法,不會創建新線程。
- currentThread():靜態方法,返回執行當前代碼的線程
- getName():獲取當前線程的名字
- setName():設置當前線程的名字
- yield():釋放當前cpu的執行權
- join():在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,直到線程b完全執行完以後,線程a才結束阻塞狀態。
- stop():已過時。當執行此方法時,強制結束當前線程。
- sleep(long millitime):讓當前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內,當前線程是阻塞狀態。
- isAlive():判斷當前線程是否存活
- setPriority():設置線程的優先級(優先級從1-10,默認爲5)
- getPriority():獲取線程的優先級
說明:高優先級的線程要搶佔低優先級線程cpu的執行權。但是隻是從概率上講,高優先級的線程高概率的情況下被執行。並不意味着只當高優先級的線程執行完以後,低優先級的線程才執行。
下面這三個方法定義在java.lang.Object類中。 - wait():使當前線程暫停執行並釋放對象鎖
- notify():將喚醒被wait()方法暫停執行的一個線程,如果有多個線程被wait,就喚醒優先級高的那個。
- notifyAll():將喚醒被wait()方法暫停的所有線程
代碼實現
public class ThreadMethodTest {
public static void main(String[] args) {
ThreadMethod threadMethod = new ThreadMethod();
Thread thread = new Thread(threadMethod);
// 5. setName():設置當前線程的名字
thread.setName("線程一");
// 11. setPriority():設置線程的優先級(優先級從1-10,默認爲5)
thread.setPriority(7);
//12. getPriority():獲取線程的優先級
System.out.println(thread.getName()+" 的優先級爲:"+thread.getPriority());
// 1. start():啓動當前線程;調用當前線程的run()
thread.start();
// 2. run(): 通常需要重寫Thread類中的此方法,將創建的線程要執行的操作聲明在此方法中
// thread.run();
// 10. isAlive():判斷當前線程是否存活
System.out.println(thread.isAlive());
}
}
class ThreadMethod implements Runnable {
@Override public void run() {
//6. yield():釋放當前cpu的執行權
Thread.yield();
//7. join():在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,
// 直到線程b完全執行完以後,線程a才結束阻塞狀態。
try {
Thread.currentThread().join();
} catch (InterruptedException e) {
e.printStackTrace();
}
// 8. stop():已過時。當執行此方法時,強制結束當前線程。
//Thread.currentThread().stop();
//9. sleep(long millitime):讓當前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內,當前線程是阻塞狀態。
try {
Thread.sleep(1000);
} catch (InterruptedException e) {
e.printStackTrace();
}
//3. currentThread():靜態方法,返回執行當前代碼的線程
//4. getName():獲取當前線程的名字
System.out.println(Thread.currentThread().getName());
}
}
五、線程的生命週期
- NEW(新建狀態)
- RUNNABLE(就緒狀態)
- RUNNING(運行狀態)
- BLOCKED(阻塞狀態)
- TERMINATED(死亡狀態)
六、線程的同步之Synchronized
方式一:同步代碼塊
- 操作共享數據的代碼,即爲需要被同步的代碼。儘量只包含需要同步執行的代碼。
- 共享數據:多個線程共同操作的變量。
- 同步監視器,俗稱:鎖。任何一個類的對象,都可以充當鎖。
要求:多個線程必須要共用同一把鎖。 - 補充:①在實現Runnable接口創建多線程的方式中,我們可以考慮使用this充當同步監視器。
②在繼承Thread類創建多線程的方式中,慎用this充當同步監視器,考慮使用當前類充當同步監視器。
語法
synchronized(同步監視器){
//需要被同步的代碼
}
例
class Thread1 extends Thread {
private static int ticker = 100;
private static String str = " ";
@Override
public void run() {
while (true) {
//------------同步代碼塊--------------
synchronized (str) {
if (ticker > 0) {
System.out.println(Thread.currentThread().getName() + "->" + ticker);
ticker--;
} else {
break;
}
}
//------------同步代碼塊--------------
}
}
}
方式二:同步代碼方法
如果操作共享數據的代碼完整的聲明在一個方法中,可以將整個方法體聲明爲同步的
- 同步方法仍然涉及到同步監視器,只是不需要我們顯式的聲明。
- 非靜態的同步方法,同步監視器是:this
靜態的同步方法,同步監視器是:當前類本身
語法
訪問修飾符 static synchronized 返回值 方法名(){
//代碼塊
}
例
class Thread2 extends Thread {
private static int ticker = 100;
@Override
public void run() {
while (ticker > 0) {
show();
}
}
//同步方法 synchronized
private static synchronized void show(){
if (ticker > 0) {
System.out.println(Thread.currentThread().getName() + "->" + ticker);
ticker--;
}
}
}
方式三:Lock
步驟
- 實例化Reentrantlock
- 調用鎖定方法lock();
- 調用解鎖unlock();
示例
import java.util.concurrent.locks.ReentrantLock;
public class ThreadLockTest {
public static void main(String[] args) {
ThreadLock threadLock = new ThreadLock();
Thread thread = new Thread(threadLock);
thread.start();
}
}
class ThreadLock implements Runnable {
private static int ticker = 100;
//1. 實例化Reentrantlock
private static ReentrantLock lock = new ReentrantLock();
@Override public void run() {
while (ticker > 0) {
//2. 調用鎖定方法lock();
lock.lock();
try {
show();
} finally {
lock.unlock();
}
}
}
private static void show() {
if (ticker > 0) {
System.out.println(Thread.currentThread().getName() + "->" + ticker);
ticker--;
}
}
}
七、線程通信
線程通信涉及到的三個方法:
- wait():一旦執行此方法,當前線程就進入阻塞狀態,並釋放同步監視器。
- notify():一旦執行此方法,就會喚醒被wait的一個線程。如果有多個線程被wait,就喚醒優先級高的那個。
- notifyAll():一旦執行此方法,就會喚醒所有被wait的線程。
說明:
- 1.wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中。
- 2.wait(),notify(),notifyAll()三個方法的調用者必須是同步代碼塊或同步方法中的同步監視器。
否則,會出現IllegalMonitorStateException異常 - 3.wait(),notify(),notifyAll()三個方法是定義在java.lang.Object類中。