国产探花免费观看_亚洲丰满少妇自慰呻吟_97日韩有码在线_资源在线日韩欧美_一区二区精品毛片,辰东完美世界有声小说,欢乐颂第一季,yy玄幻小说排行榜完本

首頁 > 編程 > Java > 正文

Java基礎:多線程

2019-11-06 07:20:13
字體:
來源:轉載
供稿:網友

第一講 多線程概述

1.多線程引入

由上圖中程序的調用流程可知,這個程序只有一個執行流程,所以這樣的程序就是單線程程序。假如一個程序有多條執行流程,那么,該程序就是多線程程序。

2.多線程概述

什么是進程?

進程就是正在運行的程序,是系統進行資源分配和調用的獨立單位。每一個進程都有它自己的內存空間和系統資源。

多進程有什么意義呢?

單進程的計算機只能做一件事情,而我們現在的計算機都可以做多件事情。舉例:一邊玩游戲(游戲進程),一邊聽音樂(音樂進程)。也就是說現在的計算機都是支持多進程的,可以在一個時間段內執行多個任務。并且呢,可以提高CPU的使用率,解決了多部分代碼同時運行的問題。 其實,多個應用程序同時執行都是CPU在做著快速的切換完成的。這個切換是隨機的。CPU的切換是需要花費時間的,從而導致了效率的降低。

什么是線程?

線程是程序的執行單元,執行路徑;是進程中的單個順序控制流,是一條執行路徑;一個進程如果只有一條執行路徑,則稱為單線程程序。一個進程如果有多條執行路徑,則稱為多線程程序。

多線程有什么意義呢?

多線程的存在,不是提高程序的執行速度。其實是為了提高應用程序的使用率。程序的執行其實都是在搶CPU的資源,CPU的執行權。多個進程是在搶這個資源,而其中的某一個進程如果執行路徑比較多,就會有更高的幾率搶到CPU的執行權。我們是不敢保證哪一個線程能夠在哪個時刻搶到,所以線程的執行有隨機性。

什么是并行、并發呢?

前者是邏輯上同時發生,指在某一個時間內同時運行多個程序;后者是物理上同時發生,指在某一個時間點同時運行多個程序。那么,我們能不能實現真正意義上的并發呢?答案是可以的,多個CPU就可以實現,不過你得知道如何調度和控制它們。

PS:

一個進程中可以有多個執行路徑,稱之為多線程。一個進程中至少要有一個線程。開啟多個線程是為了同時運行多部分代碼,每一個線程都有自己運行的內容,這個內容可以稱為線程要執行的任務。

3.java程序運行原理

java 命令會啟動 java 虛擬機,啟動 JVM,等于啟動了一個應用程序,也就是啟動了一個進程。該進程會自動啟動一個 “主線程” ,然后主線程去調用某個類的 main 方法。所以 main方法運行在主線程中。在此之前的所有程序都是單線程的。

思考:JVM虛擬機的啟動是單線程的還是多線程的?

答案:JVM啟動時啟動了多條線程,至少有兩個線程可以分析的出來

執行main函數的線程,該線程的任務代碼都定義在main函數中。

負責垃圾回收的線程。System類的gc方法告訴垃圾回收器調用finalize方法,但不一定立即執行。

第二講 多線程的實現方案

由于線程是依賴進程而存在的,所以我們應該先創建一個進程出來。而進程是由系統創建的,所以我們應該去調用系統功能創建一個進程。Java是不能直接調用系統功能的,所以,我們沒有辦法直接實現多線程程序。但是呢?Java可以去調用C/C++寫好的程序來實現多線程程序。由C/C++去調用系統功能創建進程,然后由Java去調用這樣的東西,然后提供一些類供我們使用。我們就可以實現多線程程序了。

1、多線程的實現方案一:繼承Thread類,重寫run()方法

定義一個類繼承Thread類。覆蓋Thread類中的run方法。直接創建Thread的子類對象創建線程。調用start方法開啟線程并調用線程的任務run方法執行。package cn.itcast;//多線程的實現方案一:繼承Thread類,重寫run()方法//1、定義一個類繼承Thread類。class MyThread extends Thread { PRivate String name; MyThread(String name) { this.name = name; } // 2、覆蓋Thread類中的run方法。 public void run() { for (int x = 0; x < 5; x++) { System.out.println(name + "...x=" + x + "...ThreadName=" + Thread.currentThread().getName()); } }}class ThreadTest { public static void main(String[] args) { // 3、直接創建Thread的子類對象創建線程。 MyThread d1 = new MyThread("黑馬程序員"); MyThread d2 = new MyThread("中關村在線"); // 4、調用start方法開啟線程并調用線程的任務run方法執行。 d1.start(); d2.start(); for (int x = 0; x < 5; x++) { System.out.println("x = " + x + "...over..." + Thread.currentThread().getName()); } }}

運行結果:

為什么要重寫run()方法?

Thread類用于描述線程,線程是需要任務的。所以Thread類也有對任務的描述。這個任務就是通過Thread類中的run方法來體現。也就是說,run方法就是封裝自定義線程運行任務的函數,run方法中定義的就是線程要運行的任務代碼。所以只有繼承Thread類,并復寫run方法,將運行的代碼定義在run方法中即可。

啟動線程使用的是那個方法

啟動線程調用的是start()方法,不是run()方法,run()方法只是封裝了被線程執行的代碼,調用run()只是普通方法的調用,無法啟動線程。

線程能不能多次啟動

不能,會出現IllegalThreadStateException非法的線程狀態異常。

run()和start()方法的區別

run():僅僅是封裝了被線程執行的代碼,直接調用就是普通方法。start():首先是啟動了線程,然后再由jvm去調用了該線程的run()方法

Thread類的基本獲取和設置方法

public final String getName():獲取線程的名稱public final void setName(String name):設置線程的名稱Thread(String name) :通過構造方法給線程起名字

思考:如何獲取main方法所在的線程名稱呢?

public static Thread currentThread() // 獲取任意方法所在的線程名稱

2、多線程的實現方案二:實現Runnable接口

定義類實現Runnable接口。覆蓋接口中的run方法,將線程的任務代碼封裝到run方法中。通過Thread類創建線程對象,并將Runnable接口的子類對象作為Thread類的構造函數的參數進行傳遞。為什么?因為線程的任務都封裝在Runnable接口子類對象的run方法中。所以要在線程對象創建時就必須明確要運行的任務。調用線程對象的start方法開啟線程。package cn.itcast;//多線程的實現方案二:實現Runnable接口//1、定義類實現Runnable接口。class MyThread implements Runnable { // 2、覆蓋接口中的run方法,將線程的任務代碼封裝到run方法中。 public void run() { show(); } public void show() { for (int x = 0; x < 5; x++) { System.out.println(Thread.currentThread().getName() + "..." + x); } }}class ThreadTest { public static void main(String[] args) { MyThread d = new MyThread(); // 3、通過Thread類創建線程對象,并將Runnable接口的子類對象作為Thread類的構造函數的參數進行傳遞。 Thread t1 = new Thread(d); Thread t2 = new Thread(d); // 4、調用線程對象的start方法開啟線程。 t1.start(); t2.start(); }}

運行結果:

如何獲取線程名稱:Thread.currentThread().getName()如何給線程設置名稱:setName()、Thread(Runnable target, String name)實現接口方式的好處: 可以避免由于Java單繼承帶來的局限性,所以,創建線程的第二種方式較為常用。適合多個相同程序的代碼去處理同一個資源的情況,把線程同程序的代碼,數據有效分離,較好的體現了面向對象的設計思想。

3、多線程程序實現方案三:實現Callable接口

創建一個線程池對象,控制要創建幾個線程對象。public static ExecutorService newFixedThreadPool(int nThreads)這種線程池的線程可以執行 可以執行Runnable對象或者Callable對象代表的線程。調用如下方法即可 Future<?> submit(Runnable task): 提交一個 Runnable 任務用于執行,并返回一個表示該任務的 Future。<T> Future<T> submit(Callable<T> task): 提交一個返回值的任務用于執行,返回一個表示任務的未決結果的 Future結束線程:shutdown():關閉線程package cn.itcast;import java.util.concurrent.ExecutorService;import java.util.concurrent.Executors;import java.util.concurrent.Callable;//Callable:是帶泛型的接口。//這里指定的泛型其實是call()方法的返回值類型。class MyCallable implements Callable { public Object call() throws Exception { for (int x = 0; x < 100; x++) { System.out.println(Thread.currentThread().getName() + ":" + x); } return null; }}/* * 多線程實現的方式3: A:創建一個線程池對象,控制要創建幾個線程對象。 public static ExecutorService * newFixedThreadPool(int nThreads) B:這種線程池的線程可以執行: * 可以執行Runnable對象或者Callable對象代表的線程 做一個類實現Runnable接口。 C:調用如下方法即可 Future<?> * submit(Runnable task) <T> Future<T> submit(Callable<T> task) D:我就要結束,可以嗎? 可以。 */public class CallableDemo { public static void main(String[] args) { // 創建線程池對象 ExecutorService pool = Executors.newFixedThreadPool(2); // 可以執行Runnable對象或者Callable對象代表的線程 pool.submit(new MyCallable()); pool.submit(new MyCallable()); // 結束 pool.shutdown(); }}

運行結果:

實現Callable的優缺點

好處:可以有返回值;可以拋出異常。弊端:代碼比較復雜,所以一般不用

4、匿名內部類方式使用多線程

new Thread(){代碼…}.start(); //新創建一個線程并啟動new Thread(new Runnable(){代碼…}).start(); //新創建一個線程并啟動

第三講 線程調度和線程控制

1、線程調度

假如我們的計算機只有一個 CPU,那么 CPU 在某一個時刻只能執行一條指令,線程只有得到 CPU時間片,也就是使用權,才可以執行指令。那么Java是如何對線程進行調用的呢?

線程有兩種調度模型:

分時調度模型:所有線程輪流使用 CPU 的使用權,平均分配每個線程占用 CPU 的時間片 搶占式調度模型:優先讓優先級高的線程使用 CPU,如果線程的優先級相同,那么會隨機選擇一個,優先級高的線程獲取的 CPU 時間片相對多一些。 Java使用的是搶占式調度模型。

如何設置和獲取線程優先級

public final intgetPriority(); //獲取線程的優先級public final voidsetPriority(int newPriority); //設置線程的優先級

注意:線程默認的優先級是5;線程優先級的范圍是:1-10;線程優先級高僅僅表示線程獲取CPU的時間片的幾率高,但是要在次數比較多,或者多次運行的時候才能卡到比較好的結果。

2、線程控制

第四講 線程的生命周期

1、線程的狀態

 2、線程的生命周期圖

 第五講 線程安全問題

1、判斷一個程序是否會有線程安全問題的標準:

是否是多線程環境是否有共享數據是否有多條語句操作共享數據

2、如何解決多線程安全問題呢?

基本思想:讓程序沒有安全問題的環境。 解決辦法:同步機制:同步代碼塊、同步方法。把多個語句操作共享數據的代碼給鎖起來,讓任意時刻只能有一個線程執行即可。

解決線程安全問題實現1:同步代碼塊,格式如下

synchronized(對象){ 需要同步的代碼;}package cn.itcast;//賣票程序的同步代碼塊實現示例 class Ticket implements Runnable { private int num = 10; Object obj = new Object(); public void run() { while (true) { // 給可能出現問題的代碼加鎖 synchronized (obj) { if (num > 0) { // 顯示線程名及余票數 System.out.println(Thread.currentThread().getName() + "...sale..." + num--); } } } }}class TicketDemo { public static void main(String[] args) { // 通過Thread類創建線程對象,并將Runnable接口的子類對象作為Thread類的構造函數的參數進行傳遞。 Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); // 調用線程對象的start方法開啟線程。 t1.start(); t2.start(); t3.start(); t4.start(); }}

運行結果:

同步可以解決安全問題的根本原因就在那個對象上。該對象如同鎖的功能。同步代碼塊的對象可以是哪些呢? 可以是任意對象,但每個線程都必須是同一對象。同步的前提:多個線程;多個線程使用的是同一個鎖對象同步的好處:同步的出現解決了多線程的安全問題。同步的弊端:當線程相當多時,因為每個線程都會去判斷同步上的鎖,這是很耗費資源的,無形中會降低程序的運行效率。

解決線程安全問題實現2:同步方法

同步方法:就是把同步關鍵字加到方法上

package cn.itcast;//賣票程序的同步代碼塊實現示例 class Ticket implements Runnable { // 定義100張票 private static int tickets = 10; Object obj = new Object(); public void run() { while (true) { sellTicket(); } } private synchronized void sellTicket() { if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票 "); } }}class TicketDemo { public static void main(String[] args) { // 通過Thread類創建線程對象,并將Runnable接口的子類對象作為Thread類的構造函數的參數進行傳遞。 Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); Thread t3 = new Thread(t); Thread t4 = new Thread(t); // 調用線程對象的start方法開啟線程。 t1.start(); t2.start(); t3.start(); t4.start(); }}

運行結果:

PS:

同步方法的鎖對象是什么呢?this對象如果是靜態方法,同步方法的鎖對象又是什么呢?該類的字節碼文件(類的class文件)那么,我們到底使用同步方法還是同步代碼塊?

如果鎖對象是this,就可以考慮使用同步方法。否則能使用同步代碼塊的盡量使用同步代碼塊。

線程安全與非線程安全的類

某些線程安全的類:StringBuffer、Vector、HashTable,雖然線程安全,但是效率較低,我們一般不用,而用Collections工具類解決線程安全的問題。 List list = Colletions.syncrinizedList(new ArrayList()):獲取線程安全的List集合

JDK5中Lock鎖的使用

雖然我們可以理解同步代碼塊和同步方法的鎖對象問題,但是我們并沒有直接看到在哪里加上了鎖,在哪里釋放了鎖,為了更清晰的表達如何加鎖和釋放鎖,JDK5以后提供了一個新的鎖對象Lock Lock接口:Lock 實現提供了比使用 synchronized 方法和語句可獲得的更廣泛的鎖定操作,此實現允許更靈活的結構。

void lock():獲取鎖

void unlock():釋放鎖

ReentrantLock:Lock的實現類

package cn.itcast;import java.util.concurrent.locks.Lock;import java.util.concurrent.locks.ReentrantLock;class SellTicket implements Runnable { // 定義票 private int tickets = 100; // 定義鎖對象 private Lock lock = new ReentrantLock(); public void run() { while (true) { try { // 加鎖 lock.lock(); if (tickets > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "正在出售第" + (tickets--) + "張票"); } } finally { // 釋放鎖 lock.unlock(); } } }}/* * 雖然我們可以理解同步代碼塊和同步方法的鎖對象問題,但是我們并沒有直接看到在哪里加上了鎖,在哪里釋放了鎖, * 為了更清晰的表達如何加鎖和釋放鎖,JDK5以后提供了一個新的鎖對象Lock。 * * Lock: void lock(): 獲取鎖。 void unlock():釋放鎖。 ReentrantLock是Lock的實現類. */public class SellTicketDemo { public static void main(String[] args) { // 創建資源對象 SellTicket st = new SellTicket(); // 創建三個窗口 Thread t1 = new Thread(st, "窗口1"); Thread t2 = new Thread(st, "窗口2"); Thread t3 = new Thread(st, "窗口3"); // 啟動線程 t1.start(); t2.start(); t3.start(); }}

運行結果:

六、死鎖

同步弊端:效率低,如果出現了同步嵌套,就容易產生死鎖問題。 死鎖問題:是指兩個或者兩個以上的線程在執行的過程中,因爭奪資源產生的一種互相等待現象

package cn.itcast;class Ticket implements Runnable { private static int num = 100; Object obj = new Object(); boolean flag = true; public void run() { if (flag) { while (true) { synchronized (obj) { show(); } } } else while (true) show(); } public synchronized void show() { synchronized (obj) { if (num > 0) { try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "...function..." + num--); } } }}class DeadLockDemo { public static void main(String[] args) { Ticket t = new Ticket(); Thread t1 = new Thread(t); Thread t2 = new Thread(t); t1.start(); try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } t.flag = false; t2.start(); }}

運行結果:

原因分析:

由上圖可以看到程序已經被鎖死,無法向下執行。run方法中的同步代碼塊需要獲取obj對象鎖,才能執行代碼塊中的show方法。而執行show方法則必須獲取this對象鎖,然后才能執行其中的同步代碼塊。當線程t1獲取到obj對象鎖執行同步代碼塊,線程t2獲取到this對象鎖執行show方法。同步代碼塊中的show方法因無法獲取到this對象鎖無法執行,show方法中的同步代碼塊因無法獲取到obj對象鎖無法執行,就會產生死鎖。

七、線程間通信

多個線程在處理統一資源,但是任務卻不同,這時候就需要線程間通信。為了實現線程間的通信Java提供了等待喚醒機制。 等待/喚醒機制涉及的方法:

wait():讓線程處于凍結狀態,被wait的線程會被存儲到線程池中。

notify():喚醒線程池中的一個線程(任何一個都有可能)。

notifyAll():喚醒線程池中的所有線程。

PS:

這些方法都必須定義在同步中,因為這些方法是用于操作線程狀態的方法。

必須要明確到底操作的是哪個鎖上的線程!

wait和sleep區別?

4、為什么操作線程的方法wait、notify、notifyAll定義在了object類中,因為這些方法是監視器的方法,監視器其實就是鎖。鎖可以是任意的對象,任意的對象調用的方式一定在object類中。 生產者-消費者問題:

package cn.itcast;class Student { String name; int age; boolean flag;}class SetThread implements Runnable { private Student s; private int x = 0; public SetThread(Student s) { this.s = s; } public void run() { while (true) { synchronized (s) { // 判斷有沒有 if (s.flag) { try { s.wait(); // t1等著,釋放鎖 } catch (InterruptedException e) { e.printStackTrace(); } } if (x % 2 == 0) { s.name = "張三"; s.age = 15; } else { s.name = "李四"; s.age = 16; } x++; // x=1 // 修改標記 s.flag = true; // 喚醒線程 s.notify(); // 喚醒t2,喚醒并不表示你立馬可以執行,必須還得搶CPU的執行權。 } // t1有,或者t2有 } }}class GetThread implements Runnable { private Student s; public GetThread(Student s) { this.s = s; } public void run() { while (true) { synchronized (s) { if (!s.flag) { try { s.wait(); // t2就等待了。立即釋放鎖。將來醒過來的時候,是從這里醒過來的時候 } catch (InterruptedException e) { e.printStackTrace(); } } System.out.println(s.name + "---" + s.age); // 林青霞---27 // 劉意---30 // 修改標記 s.flag = false; // 喚醒線程 s.notify(); // 喚醒t1 } } }}public class StudentDemo { public static void main(String[] args) { // 創建資源 Student s = new Student(); // 設置和獲取的類 SetThread st = new SetThread(s); GetThread gt = new GetThread(s); // 線程類 Thread t1 = new Thread(st); Thread t2 = new Thread(gt); // 啟動線程 t1.start(); t2.start(); }}

運行結果:

1、線程組

Java中使用ThreadGroup來表示線程組,它可以對一批線程進行分類管理,Java允許程序直接對線程組進行控制。默認情況下,所有的線程都屬于主線程組。 public final ThreadGroup getThreadGroup():返回該線程所屬的線程組。 Thread(ThreadGroup group,Runnable target, String name):給線程設置分組

2、線程池

程序啟動一個新線程成本是比較高的,因為它涉及到要與操作系統進行交互。而使用線程池可以很好的提高性能,尤其是當程序中要創建大量生存期很短的線程時,更應該考慮使用線程池。

線程池里的每一個線程代碼結束后,并不會死亡,而是再次回到線程池中成為空閑狀態,等待下一個對象來使用。在JDK5之前,我們必須手動實現自己的線程池,從JDK5開始,Java內置支持線程池。JDK5新增了一個Executors工廠類來產生線程池,有如下幾個方法 public static ExecutorService newCachedThreadPool(): 創建一個線程池對象public static ExecutorService newFixedThreadPool(int nThreads): 創建一個線程池對象,控制要創建幾個線程public static ExecutorService newSingleThreadExecutor(): 創建一個使用單個 worker 線程的 Executor 這些方法的返回值是ExecutorService對象,該對象表示一個線程池,可以執行Runnable對象或者Callable對象代表的線程。它提供了如下方法:Future

八、定時器的使用

定時器是一個應用十分廣泛的線程工具,可用于調度多個定時任務以后臺線程的方式執行。在Java中,可以通過Timer和TimerTask類來實現定義調度的功能 Timer:一種工具,線程用其安排以后在后臺線程中執行的任務。可安排任務執行一次,或者定期重復執行。

實際開發中使用的是Quartz:一個完全由java編寫的開源調度框架。

九、多線程總結

三種多選實現方案

繼承Thread類,重寫run()方法。實現Runnable接口,new Thread(new Runnable(){…}){…};實現Callable接口。和線程池結合。

實現Runnable好處

將線程的任務從線程的子類中分離出來,進行了單獨的封裝,實現數據和程序分離,按照面向對象的思想將任務封裝成對象。

避免了Java單繼承的局限性。所以,創建線程的第二種方式較為常用。

線程間的通信

多個線程在處理同一資源,但是任務卻不同,這時候就需要線程間通信。等待/喚醒機制涉及的方法 wait():讓線程處于凍結狀態,被wait的線程會被存儲到線程池中。notify():喚醒線程池中的一個線程(任何一個都有可能)。notifyAll():喚醒線程池中的所有線程。

wait、sleep區別

wait可以指定時間也可以不指定。sleep必須指定時間。在同步中時,對CPU的執行權和鎖的處理不同。 wait:釋放CPU執行權,釋放鎖。Object中的方法。sleep:釋放CPU執行權,不釋放鎖。Thread中的方法。sleep必需捕獲異常,wait,notify,notifyAll不需要捕獲異常

常用方法

String getName():獲取線程的名稱void setName(String name):設置線程的名稱static Thread currentThread():獲取當前正在執行的線程int getPriority():獲取線程優先級void setPriority(int newPriority):設置線程優先級(1-10)static void sleep(long millis):線程休眠void join():線程加入,該線程執行完畢其他線程才可以執行static void yield():線程禮讓setDaemon(boolean on):后臺線程/守護線程10.void stop( ):已過時,不建議使用;interrupt( ):中斷線程

線程的生命周期

新建,就緒,運行,阻塞(同步阻塞,等待阻塞,其他阻塞),死亡


發表評論 共有條評論
用戶名: 密碼:
驗證碼: 匿名發表
主站蜘蛛池模板: 大关县| 永胜县| 城固县| 红原县| 乌鲁木齐市| 苍溪县| 远安县| 青海省| 铅山县| 新野县| 大方县| 玛多县| 措美县| 九寨沟县| 安达市| 徐汇区| 隆林| 南充市| 应用必备| 阿坝| 图们市| 仁布县| 铜川市| 鸡泽县| 镇江市| 同心县| 韶山市| 犍为县| 卢氏县| 鄢陵县| 于都县| 泰来县| 密山市| 南皮县| 鹤庆县| 杭锦旗| 长兴县| 河曲县| 大同市| 陆河县| 那坡县|