1. 程式人生 > 實用技巧 >【leetcode】49. 字母異位詞分組

【leetcode】49. 字母異位詞分組

多執行緒

併發與並行

  • 併發:指的是兩個或者多個事件(任務)在同一時間段內發生的.
  • 並行:指的是兩個或者多個事件(任務)在同一時刻發生(同時發生)

執行緒和程序

  • 程序:是指在一個記憶體中執行的應用程式,每個程序都有一個獨立的記憶體空間,一個應用程式可以同時執行多個執行緒;程序也是程式的一次執行過程,是系統執行程式的基本單位;系統執行一個程式就是一個程序建立.執行.到消亡的過程
  • 執行緒:執行緒就是程序的一個執行單位,負責當前程序中的程式執行,一個程式中至少有一個程序,一個程序可以擁有多個執行緒,那麼這個被執行的程式也稱為多執行緒程式.
    備註:單核處理器的計算機肯定不能並行處理多個任務,只能是多個任務執行在一個cpu上併發的執行,不能並行. 執行緒執行緒排程:
    .分時排程:所有的執行緒輪流使用CPU的使用權,平均分配給每個執行緒佔用CPU的時間
    搶佔式排程:優先讓優先順序高的執行緒使用CPU,如果執行緒的優先順序相同,那麼會隨機一個執行緒執行,Java使用的就是搶佔式
    排程方式來執行執行緒程式。
    設定執行緒的優先順序
    預習:執行緒的建立,執行緒的同步,執行緒的狀態,如何保障執行緒安全,執行緒池的概念
    從巨集觀角度理解就是併發進行的,

建立多執行緒程式的第一種方式, 建立Thread類的子類

java.lang.Thread類:是描述執行緒的類,我們想要實現多執行緒程式,就必須繼承Thread類,每個執行緒的任務,實際上就是執行一個程式流,java使用執行緒執行體來完成程式流的執行

實現步驟:
  1. 建立- -個Thread類的子類
  2. 在Thread類的子類當中重寫Thread類的run方法,設定執行緒任務(開啟執行緒需要你做什麼事情? )
  3. 建立Thread類的子類物件
  4. 呼叫Thread類中的方法start方法,開啟新執行緒,執行run方法
    void start() 使該執行緒開始執行,Java虛擬機器呼叫該執行緒的run方法。
    結果是兩個執行緒併發地執行:當前執行緒(從呼叫返回給start方法)和另-一個執行緒(執行其run方法)。
    多次啟動一個執行緒是非法的。特別是當執行緒已經結束執行後,不能再重新啟動。
    Java程式屬於搶佔式排程,哪個執行緒的優先順序高,哪個執行緒就先執行

多執行緒的原理

多執行緒執行時序圖,來感受它的執行順序的魅力
插入圖:

程式啟動執行main時候,java虛擬機器啟動一個程序,主執行緒main在main呼叫的時候被建立。隨著呼叫oneThread物件的start方
法,另外一個新的執行緒也啟動了,這樣,整個應用就在多執行緒環境下執行著。
通過上面一張圖可以發現多執行緒在記憶體 當中的執行流程。
多個執行緒執行時,在棧記憶體當中,其實每一個執行緒都有一 片屬於自己的棧記憶體空間, 進行方法的壓棧和彈棧。
當執行執行緒的任務結束了,執行緒自動在棧記憶體當中釋放。當所有的執行執行緒都結束了,那麼說程也就結束了。

thread類

API幫助文件中定義了有關執行緒的一些方法,具體如下:

構造方法:
* public Thread(); 分配一個新的執行緒物件
* public  Thread(String name) : 分配一個指定名字的新的執行緒物件
* public Thread(Runnable target) : 分配一個帶有指定目標新的執行緒物件
*. public Thread(Runnable target,String name):分配一個帶有指定目標的新的執行緒 物件並且帶有指定名字的。
常用的方法:
* public String getName():獲取當前執行緒的名稱
* public void start():讓此執行緒開始執行,java虛擬機器會呼叫此執行緒的run方法
* public void run):此執行緒要執行的任務在此方法內定義。
* public static void sleep(long millis): 使當前正在執行的執行緒以指定的毫秒數暫停(臨時性暫停執行緒的執行),每隔指定的毫秒數執行一下.
* public static Thread currentThread():獲取當前正在執行的執行緒物件的引用。
通過翻閱API的得知,建立執行緒有兩種方式,-種是繼承Thread類,-種是實現Runable介面,接下來講解這兩種方式.以及各自的常用方法
##### 第一種
package com. zhiyou100.thread.demo04;
*系統預設給執行緒一個預設的執行緒名稱,但也可以自己設定

*設定執行緒的名稱:
1.使用Thread類的方法setName(名字)
void setName(String name)修改執行緒的名稱

2.使用Thread類的帶參構造方法,引數傳遞執行緒的名稱:呼叫父類的帶參構造方法,把執行緒名稱傳遞給父類,讓父類(Thread)給子類執行緒起一個名字
*/
// 1.
public class MyThread extends Thread{
public MyThread(){}
public MyThread(String name) {
super(name );/ /把執行緒名稱傳遞給父類,讓父類(Thread)給予子執行緒一個名字
}
// 2.
@Override
public void run() {
//獲取執行緒的名稱
System. out . print1n(Thread . current Thread(). getName() + "在執行任務");
}
第二種(實現Runnable介面)

採用java.long.Runnable的介面也是非常常見的一種,我們只需要重寫run方法即可,在long包下,不需要導包
步驟如下:
1 .Runnable介面應該由那些打算通過某一執行緒執行其例項的美來實現,
2. 定義Runnable介面的實現類,並重寫該介面的run方法,該run方法的方法體同樣是該執行緒執行體

1.定義一個Runnab1e介面的實現類
2.在實現類中重寫Runnable介面當中的run方法,設定執行緒任務。
3.建立Runnable介面實現類的物件
4.構建Thread類的物件,在構造方法中傳遞Runnable介面的實現類物件
5.呼叫Thread類中的start方法,開啟新執行緒執行run方法

實現Runnable介面建立多執行緒程式的好處
  1. 避免了單繼承的侷限性
    ----一個類只能繼承一個父類,子類繼承了Thread類,就不能繼承其他的父類,
    ---- 如果實現Runnable介面,還可以繼承其他的類,還可以實現其他的介面
    2.增強啦程式的擴充套件性,降低了程式的耦合性(解耦)
    ---- 實現Runnable介面,把設定執行緒任務

實現Runnable介面的方式,把設定執行緒任務和開啟新執行緒進行了分離(解耦)
實現類中,重寫了run方法:用來設定執行緒的任務
建立Thread類的物件,呼叫start方法:用來開啟新的執行緒

通過實現Runnable介面,使得該類有了多執行緒類的特徵,run方法是多執行緒程式的一個執行目標,所有的多執行緒程式碼都寫在run()方法中,Thread類實際上也是實現了Runnable介面的類
在啟動的多執行緒的時候,需要先通過Thread類的構造方法Thread(Runnable targe)構建執行緒物件,然後呼叫Thread類物件的start方法來執行多執行緒程式。
備註: Runnable物件僅僅作為Thread類物件的target, Runnable實現類裡包含 了run方法作為執行緒的執行體。而實際的執行緒物件依然是Thread類的例項
Thread類和Runnable介面的區別
如果一個類繼承了Thread類,則不適合資源的共享。但是如果實現了Runnable接的話,則很容易實現資源共享。
實現Runnable介面比繼承Thread類的所具有的優勢:
1.適合多個相同的程式程式碼的執行緒去共享同一個資源
2.可以避免java中單繼承的侷限性
3.增加了程式的健壯性,實現解耦操作,程式碼可以被多個執行緒共享,程式碼和執行緒可以實現分離。
4.執行緒池只能放入實現Runnable或者Callable類的執行緒,不能直接放入繼承Thread的類。
備註:在java中, 每次程式執行至少啟動兩個執行緒,-個是main執行緒,-個垃圾收集執行緒。因為每當使用java命令去執行個類的
時候,實際上都會啟動一個JVM,每一個JVM其實都是在作業系統中啟動了一個程序。
匿名內部類方式實現多執行緒程式的建立
使用執行緒的匿名內部類方式,可以很方便的實現每個執行緒執行不同的執行緒任務操作。
使用匿名內部類方式實現Runnable介面的run方法.
程式碼如下:

public class Demo {
	public static void main(String[] args) {
		//1.使用匿名內部類來實現多執行緒的建立
		new Thread() {
			@Override
			public void run() {
				for (int i = 0; i < 20; i++) {					System.out.println(Thread.currentThread().getName());				
				}
			}		
		}.start();
		//2.使用匿名內部類來實現多執行緒的建立
		Runnable runnable = new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 20; i++) {					System.out.println(Thread.currentThread().getName());				
				}
			}
		};
		new Thread(runnable).start();		
		//3.使用匿名內部類來實現多執行緒的建立
		new Thread(new Runnable() {
			@Override
			public void run() {
				for (int i = 0; i < 20; i++) {					System.out.println(Thread.currentThread().getName());					
				}
			}
		}).start();
		

執行緒安全【重點】

如果有多個執行緒在同時的執行,而這些執行緒可能同時在執行這段程式碼.程式每次執行的結果和單執行緒的執行結果是一樣的

如果有多個執行緒在同時的執行,而這些執行緒可能同時在執行這段程式碼。程式每次執行結果和單執行緒執行的結果是一樣的, 而且其他的變數的值也和預期的值是一樣的,就是執行緒安全的。
通過葫蘆娃大戰奧特曼的案例發現,當多個執行緒去共享同一個資源的時候出現了執行緒的不安全的問題
1.相同的票數,被賣了多次
2.不存在的票,也被賣出去了,比如說0和-1

這種問題,幾個視窗(執行緒)票數不同步,這種問題我們稱之為執行緒不安全
備註:執行緒安全問題都是由全域性變數或者靜態變數引起的,若每個執行緒中對全域性變數、靜態變數只有讀操作,而無寫的操作,一般來說,這個全域性變數是執行緒安全的;若有多個執行緒同時執行寫操作,一般都需要考 慮執行緒的同步,否則的話就很可能會引發執行緒的安全問題。

如何實現多執行緒的同步

當我們使用多執行緒訪問同一資源的時候,且這多個執行緒中對資源有寫的操作,就容易出現執行緒安全問題
要解決多執行緒併發訪問一個資源的安全問題,java中提供了同步機制(synchronized)來解決.

視窗1程序進入操作的時候,視窗2和視窗3執行緒只能在外面等著,當視窗1執行緒操作結束,視窗1,視窗2和視窗3才能再次搶佔CPU,決定執行哪個執行緒,其他執行緒在外等候,也就是說,如果一個執行緒修改共享資源 的時候,其他的執行緒不能修改該共享資源,等待修改完畢再次同步後,才能再次搶佔CPU的使用權,完成對應的操作

實現同步機制的三種方式

1.同步程式碼塊
2. 同步方法
3.鎖機制

1.同步程式碼塊
  • 同步程式碼塊:synchronized關鍵字可以用於方法中的某個程式碼塊,表示只對這個程式碼塊的資源實行互斥訪問.

格式:

synchronized(同步鎖){
//需要同步的操作程式碼
}

3.同步鎖

同步鎖是一個物件,是一個抽象的概念,可以想象成在物件上標記啦一個鎖
1.所物件是任意型別的,Object
2. 多個執行緒物件,要使用同一把鎖

注意: 在任何時候,最多允許一個執行緒擁有同步鎖,