1. 進程與線程的區別
進程:是系統進行分配和管理資源的基本單位
-
線程:進程的一個執行單元,是進程內調度的實體、是CPU調度和分派的基本單位,是比進程更小的獨立運
行的基本單位。線程也被稱爲輕量級進程,線程是程序執行的最小單位。
一個程序至少一個進程,一個進程至少一個線程。
-
進程有自己的獨立地址空間,每啓動一個進程,系統就會爲它分配地址空間,建立數據表來維護代碼段、堆棧
段和數據段,這種操作非常昂貴。 而線程是共享進程中的數據的,使用相同的地址空間,因此CPU切換一個
線程的花費遠比進程要小很多,同時創建一個線程的開銷也比進程要小很多。 線程之間的通信更方便,同一
進程下的線程共享全局變量、靜態變量等數據,而進程之間的通信需要以通信的方式進行。 如何處理好同步
與互斥是編寫多線程程序的難點。 多進程程序更健壯,進程有獨立的地址空間,一個進程崩潰後,在保護模
式下不會對其它進程產生影響, 而線程只是一個進程中的不同執行路徑。線程有自己的堆棧和局部變量,但
線程之間沒有單獨的地址空間,所以可能一個線程出現問題,進而導致整個程序出現問題
2.線程的狀態及其相互轉換
初始(NEW):新創建了一個線程對象,但還沒有調用start()方法。
運行(RUNNABLE):處於可運行狀態的線程正在JVM中執行,但它可能正在等待來自操作系統的其他資源,例
如處理器。
阻塞(BLOCKED):線程阻塞於synchronized鎖,等待獲取synchronized鎖的狀態。
等待(WAITING):Object.wait()、join()、 LockSupport.park(),進入該狀態的線程需要等待其他線程做出一些特
定動作(通知或中斷)。
超時等待(TIME_WAITING):Object.wait(long)、Thread.join()、LockSupport.parkNanos()、
LockSupport.parkUntil,該狀態不同於WAITING,它可以在指定的時間內自行返回。
終止(TERMINATED):表示該線程已經執行完畢。
2.1創建線程的方式(上)
繼承Thread,並重寫父類的run方法
實現Runable接口,並實現run方法
實際開發中,選第2種:java只允許單繼承 增加程序的健壯性,代碼可以共享,代碼跟數據獨立
2.2創建線程的方式(下)
使用匿名內部類
Lambda表達式
線程池
線程阻塞Demo:
public class ThreadStateDemo {
public static void main(String[] args) throws InterruptedException {
/**
* Block
*/
// Object obj = new Object();
// Thread thread = new Thread(()->{
//
// synchronized (obj){
//
// try {
// Thread.sleep(100000000L);
// } catch (InterruptedException e) {
// e.printStackTrace();
// }
// }
//
// });
//
// thread.start();
//
// Thread.sleep(2000L);
//
// Thread thread2 = new Thread(()->{
//
// synchronized (obj){
//
// }
//
// });
// thread2.start();
//
// }
/**
* waiting
*/
Object obj = new Object();
Thread thread = new Thread(() -> {
synchronized (obj) {
try {
obj.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
});
thread.start();
}
}
線程池開啓單線程:
public class ThreadPool {
public static void main(String[] args) {
ExecutorService executorService = Executors.newSingleThreadExecutor();
executorService.execute(() -> {
System.out.println(Thread.currentThread().getName());
});
}
}
相應的圖示如下:
3.線程的掛起跟恢復
什麼是掛起線程? 線程的掛起操作實質上就是使線程進入“非可執行”狀態下,在這個狀態下CPU不會分給線程
時間片,進入這個狀態可以用來暫停一個線程的運行。 在線程掛起後,可以通過重新喚醒線程來使之恢復運
行
3.1 爲什麼要掛起線程?
cpu分配的時間片非常短、同時也非常珍貴。避免資源的浪費。
如何掛起線程?
-
被廢棄的方法 thread.suspend() 該方法不會釋放線程所佔用的資源。如果使用該方法將某個線程掛起,則可
能會使其他等待資源的線程死鎖 thread.resume() 方法本身並無問題,但是不能獨立於suspend()方法存在
可以使用的方法 wait() 暫停執行、放棄已經獲得的鎖、進入等待狀態
notify() 隨機喚醒一個在等待鎖的線程
notifyAll() 喚醒所有在等待鎖的線程,自行搶佔cpu資源
被廢棄的相關掛起Demo:
/**
* 掛起操作的Demo
*/
public class SuspendDemo implements Runnable{
@Override
public void run() {
System.out.println(Thread.currentThread().getName()+"進入Run()方法,準備調用SusPend");
Thread.currentThread().suspend(); //執行掛起
System.out.println(Thread.currentThread().getName()+"進入Run()方法,調用SusPend結束");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new SuspendDemo());
thread.start();
Thread.sleep(3000L);
thread.resume(); //執行恢復
}
}
這裏要注意的是掛起的操作是不安全的,被廢棄也是因爲因爲他容易造成長時間阻塞:
這裏阻塞的原因是因爲他在掛起前就進行來了喚醒的操作,導致後來的線程一直掛起(未睡眠導致喚醒的代碼提前於掛起執行了)
**
* suspend死鎖演示
*/
public class DeadDemo implements Runnable{
private static final Object object =new Object();
@Override
public void run() {
//持有資源
synchronized (object){
System.out.println(Thread.currentThread().getName()+"佔用資源");
Thread.currentThread().suspend();
}
System.out.println(Thread.currentThread().getName()+"釋放資源");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new DeadDemo(),"對比線程");
thread.start();
thread.sleep(1000L);
thread.resume();
Thread deadThread = new Thread(new DeadDemo(),"死鎖線程");
deadThread.start();
// deadThread.sleep(3000L);
deadThread.resume();
}
}
使用推薦的wait和notify方法操作:
package xianchengxuexi.chapter02.hang;
import com.sun.jndi.toolkit.ctx.StringHeadTail;
/**
* 等待方法
*/
public class WaitDemo implements Runnable {
private static Object object = new Object();
private static Object waitObj = new Object();
@Override
public void run() {
//持有資源
//鎖住的和wait()的需要時同一個對象
synchronized (waitObj){
System.out.println(Thread.currentThread().getName()+"佔用資源");
try {
waitObj.wait();//這個會釋放鎖
} catch (InterruptedException e) {
e.printStackTrace();
}
}
System.out.println(Thread.currentThread().getName()+"釋放資源");
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new WaitDemo(),"對比線程");
thread.start();
Thread thread2 = new Thread(new DeadDemo(),"對比線程2");
thread2.start();
thread.sleep(3000L);
synchronized (waitObj){
waitObj.notify();
}
}
}
3.2 什麼時候適合使用掛起線程?
我等的船還不來(等待某些未就緒的資源),我等的人還不明白。直到notify方法被調用
3.3線程的中斷操作
stop() 廢棄方法,開發中不要使用。因爲一調用,線程就立刻停止,此時有可能引發相應的線程安全性問題
/**
* 不安全的stop
*/
public class UnSafeWithStop extends Thread {
private int a =0;
private int b =0;
@Override
public void run() {
a++;
try {
sleep(2000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
b++;
}
public void printf(){
System.out.println("a的值=======>"+a);
System.out.println("b的值=======>"+b);
}
public static void main(String[] args) throws InterruptedException {
UnSafeWithStop unSafeWithStop = new UnSafeWithStop();
unSafeWithStop.start();
Thread.sleep(1000L);
unSafeWithStop.stop();
unSafeWithStop.printf();
}
}
Thread.interrupt方法
/**
* 使用Interrupt終止線程
*/
public class InterruptDemo implements Runnable {
@Override
public void run() {
while (!Thread.currentThread().isInterrupted()){
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new InterruptDemo());
thread.start();
thread.sleep(1000L);
thread.interrupt();
}
}
自行定義一個標誌,用來判斷是否繼續執行
/**
* 自定義線程終止
*/
public class MyInterruptDemo implements Runnable{
private static volatile boolean FLAG = true;
@Override
public void run() {
while (FLAG){
System.out.println(Thread.currentThread().getName());
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new MyInterruptDemo());
thread.start();
Thread.sleep(1000L);
FLAG=false;
}
}
4.線程的優先級
線程的優先級告訴程序該線程的重要程度有多大。如果有大量線程都被堵塞,都在等候運行,程序會儘可能地先運行優先級的那個線程。
但是,這並不表示優先級較低的線程不會運行。若線程的優先級較低,只不過表示它被准許運行的機會小一些而已。
線程的優先級設置可以爲1-10的任一數值,Thread類中定義了三個線程優先級,分別是:
MIN_PRIORITY(1)、NORM_PRIORITY(5)、MAX_PRIORITY(10),
一般情況下推薦使用這幾個常量,不要自行設置數值。
/**
* 線程優先級Demo
*/
public class ProrityDemo {
public static void main(String[] args) {
Thread thread = new Thread(() -> {
while (true){
System.out.println(Thread.currentThread().getName());
}
},"線程1");
Thread thread2 = new Thread(() -> {
while (true){
System.out.println(Thread.currentThread().getName());
} },"線程2");
thread.setPriority(Thread.MIN_PRIORITY);
thread2.setPriority(Thread.MAX_PRIORITY);
thread.start();
thread2.start();
}
}
不同平臺,對線程的優先級的支持不同。 編程的時候,不要過度依賴線程優先級,如果你的程序運行是否正
確取決於你設置的優先級是否按所設置的優先級運行,那這樣的程序不正確
任務:
快速處理:設置高的優先級 慢慢處理:設置低的優先級
5.守護線程
線程分類
用戶線程、守護線程 守護線程:任何一個守護線程都是整個程序中所有用戶線程的守護者,只要有活着的用
戶線程,守護線程就活着。當JVM實例中最後一個非守護線程結束時,也隨JVM一起退出
守護線程的用處:jvm垃圾清理線程
建議: 儘量少使用守護線程,因其不可控不要在守護線程裏去進行讀寫操作、執行計算邏輯
/**
* 守護線程Demo
*/
public class DaemonThreadDemo implements Runnable{
@Override
public void run() {
while (true) {
System.out.println(Thread.currentThread().getName());
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public static void main(String[] args) throws InterruptedException {
Thread thread = new Thread(new DaemonThreadDemo());
thread.start();
thread.setDaemon(true);
Thread.sleep(2000L);
}
}