談談Java中的多線程和同步

1. 多線程

1.1 多線程的優缺點

優點

  1. 提升資源利用率
  2. 提高用戶體驗

缺點:

  1. 降低了其他線程的執行概率
  2. 用戶會感受到軟件的卡頓問題
  3. 增加的系統,資源壓力
  4. 多線程情況下的共享資源問題,線程衝突,線程安全問題

1.2 創建自定義線程類的兩種方式

class Thread類
	Java中的一個線程類
	Thread類是Runnable接口的實現類,同時提供了很多線程的操作使用的方法。
    	
    	interface Runnable接口
    	這裏規定了what will be run?
    	裏面只有一個方法 run方法
    
    方式一:
    	自定義線程類,繼承Thread類,重寫run方法
    	創建自定義線程對象,直接調用start方法,開啓線程
    	
    方式二:
    	自定義線程類,遵從Runnable接口
    	使用自定義遵從接口Runnable實現類對象,作爲Thread構造方法參數
    	藉助於Thread類對象和start方法,開啓線程
    
    【推薦】
    	以上兩種方式,推薦使用方拾二,遵從Runnable接口來完成自定義線程,不影響正常的繼承邏輯,並且可以使用匿名內部類來完成線程代碼塊的書寫
    package com.qfedu.a_thread;
    
    /*
     * 自定義線程類MyThread1繼承Thread類
     */
    class MyThread1 extends Thread {
    	@Override
    	public void run() {
    		for (int i = 0; i < 100; i++) {
    			System.out.println("繼承Thread類自定義線程類");
    		}
    	}
    }
    
    /*
     * 自定義線程類MyThread2遵從Runnable接口
     */
    class MyThread2 implements Runnable {
    
    	@Override
    	public void run() {
    		for (int i = 0; i < 100; i++) {
    			System.out.println("遵從Runnable接口實現自定義線程類");
    		}
    	}
    }
    
    public class Demo1 {
    	public static void main(String[] args) {
    		new Thread(new Runnable() {
    
    			@Override
    			public void run() {
    				for (int i = 0; i < 100; i++) {
    					System.out.println("匿名內部類方式創建對象,作爲線程執行代碼");
    				}
    			}
    		}).start();
    		// 創建一個繼承Thread類自定義線程類對象
    		MyThread1 myThread1 = new MyThread1();
    		// 這裏不是啓動線程,而且將run方法做出一個普通方法執行。
    		// myThread1.run();
    		myThread1.start();
    		
    		// 創建一個Thread類對象,使用遵從Runnable接口的實現類作爲構造方法參數
    		Thread thread = new Thread(new MyThread2());
    		// 藉助於Thread類內的start方法開啓線程
    		thread.start();
    
    		for (int i = 0; i < 100; i++) {
    			System.out.println("main線程");
    		}
    	}
    }

1.3 自定義線程執行流程簡述

在這裏插入圖片描述

1.4 Thread類需要了解的方法

    構造方法 Constructor
    	Thread();
    		分配一個新的線程對象,無目標,無指定名字
    	Thread(Runnable target);
    		創建一個新的線程對象,並且在創建線程對象的過程中,使用Runnable接口的實現類
    		對象作爲執行的線程代碼塊目標
    	Thread(String name);
    		創建一個新的線程,無指定目標,但是指定當前線程的名字是什麼
    	Thread(Runnable target, String name);
    		創建一個線程的線程對象,使用Runnable接口實現類對象,作爲執行目標,並且指定
    		name作爲線程名
    	
    成員方法:
    	void setName(String name);
    	String getName();
    		以上兩個是name屬性setter和getter方法
    	void setPriority(int Priority);
    		設置線程的優先級,非一定執行要求,只是增加執行的概率
    		優先級數值範圍 [1 - 10] 10最高 1最低 5默認
    	int getPriority();
    		獲取線程優先級
    	void start();
    		啓動線程對象
    	
    	public static void sleep(int ms);
    		當前方法是靜態方法,通過Thread類調用,要求是當前所在線程代碼塊對應的線程,
    		進行休眠操作,休眠指定的毫秒數
    	public static Thread currentThread();
    		當前方法是靜態方法,通過Thread類調用,獲取當前所處代碼塊對應的線程對象。

2. 線程安全問題和解決方案

2.1 線程安全問題–共享資源能使用問題

    <<湄公河行動>>
    	100張票
    
    淘票票CGV 美團 貓眼
    三個銷售渠道,100張票是一個共享資源!!!
    三個銷售渠道,可以認爲是三個銷售線程!!!
    
    
    問題一:
    	100張票共享資源問題,選什麼來保存
    	局部變量:
    		在方法內,如果run方法執行,存在,run方法當前執行完畢,銷燬。
    		每一個線程對象中都有run方法,無法滿足共享問題
    	成員變量:
    		每一個線程對象中,都有一個對應的成員變量,非共享資源。
    	靜態成員變量:
    		屬於類變量,所有的當前類對象,使用的靜態成員變量都是一個,而且一處修改,處處
    		受影響。【共享資源】
    
    問題二:
    	資源衝突問題

2.2 同步代碼塊

    synchronized (/* 鎖對象 */) {
        
    }
    
    /*
    特徵:
     	1. synchronized 小括號裏面的對象是鎖對象,並且要求如果是多線程的情況下,鎖對象必須是同一個對象。
     	2. synchronized 大括號中的代碼塊就是需要進行同步的代碼,或者說是加鎖的代碼,大括號裏面的內容,有且只允許一個線程進入。
     	3. 同步代碼塊越短越好,在保證安全的情況下,提高性能
     
    問題:
    	1. 目前鎖對象感覺很隨意,存在一定的隱患
    	2. 代碼層級關係很複雜,看着有點麻煩
    */

2.3 同步方法

    synchronized 作爲關鍵字來修飾方法,修飾的方法就是對應的同步方
    有且只允許一個線程進入,到底是誰來完成的加鎖操作?
    
    1. 靜態成員方法
    	鎖對象,是當前類對應的字節碼文件.class 類名.class
    2. 非靜態成員方法
    	鎖對象就是當前類對象 this
    
    選擇同步方法是否使用static修飾問題
    	1. 如果非static修飾,要保證執行的線程對象有且只有一個,因爲鎖對象就是當前線程對
    象
    	
    	2. 如果是static修飾,鎖對象具有唯一性,多個線程使用的鎖是同一個鎖。
    	

2.4 Lock鎖

    Java提供了一個對於線程安全問題,加鎖操作相對於同步代碼塊和同步方法更加廣泛的一種操作方式。
    1. 對象化操作。
    	創建Lock構造方法
    		Lock lock = new ReentrantLock();
    2. 方法化操作。
    	開鎖:
    		unlock();
    	加鎖:
    		lock();

2.5 三種加鎖方式的總結

  1. 一鎖一線程,一鎖多線程問題。
    使用對應的鎖操作對應的線程,考慮靜態和非靜態問題。
    同步方法和Lock鎖使用。
    靜態是一鎖多目標,非靜態是一鎖一目標

  2. 涉及到同步問題時,要考慮好鎖對象的選擇問題
    同步代碼塊,同步方法,Lock對象。

  3. 守護線程

    守護線程,也稱之爲後臺線程,如果當前主線程GG思密達,守護線程也就GG思密達。

    守護線程一般用於:
    1. 自動下載
    2. 操作日誌
    3. 操作監控

    方法是通過線程對象
    setDeamon(boolean flag);
    true爲守護線程
    false缺省屬性,正常線程

  4. 線程狀態

4.1 六種線程狀態

線程有如果按照java.lang.Thread.State枚舉方式來考慮,一共提供了6種狀態

狀態 導致狀態的發生條件
NEW(新建) 線程剛剛被創建,沒有啓動,沒有調用start方法
RUNNABLE(可運行) 線程已經可以在JVM中運行,但是是否運行不確定,看當前線程是否擁有CPU執行權
BLOCKED(鎖阻塞) 當前線程進入一個同步代碼需要獲取對應的鎖對象,但是發現當前鎖對象被其他線程持有,當前線程會進入一個BLOCKED。如果佔用鎖對象的線程打開鎖對象,當前線程持有對應鎖對象,進入Runnable狀態
WAITING(無限等待) 通過一個wait方法線程進入一個無限等待狀態,這裏需要另外一個線程進行喚醒操作。進入無限等待狀態的線程是無法自己回到Runnable狀態,需要其他線程通過notify或者notifyAll方法進行喚醒操作
TIMED_WAITING(計時等待) 當前線程的確是等待狀態,但是會在一定時間之後自動回到Runnable狀態,例如 Thread.sleep() 或者是Object類內的wait(int ms);
TERMINATED(被終止) 因爲Run方法運行結束正常退出線程,或者說在運行的過程中因爲出現異常導致當前線程GG思密達

4.2 TIMED_WAITING(計時等待)

Thread.sleep(int ms);
在對應線程代碼塊中,當前線程休眠指定的時間。
Object類內 wait(int ms);
讓當前線程進入一個計時等待狀態

  1. 規定的時間及時完畢,線程回到可運行狀態
  2. 在等待時間內,通過其他線程被notify或者notifyAll喚醒Sleep方法
  3. 調用之後休眠指定時間
  4. sleep方法必須執行在run方法內,纔可以休眠線程
  5. sleep不會打卡當前線程佔用的鎖對象。

4.3 BLOCKED(鎖阻塞)

線程中有鎖存在,線程需要進入帶有鎖操作的同步代碼,如果鎖對象被別人持有,只能在鎖外等待
鎖阻塞狀態的線程是否能夠搶到鎖對象有很多因素

  1. 優先級問題,非決定因素
  2. CPU執行概率問題。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章