線程
? ? ? ? 線程(Thread)是一個程序內部的一條執行流程。
????????程序中如果只有一條執行流程,那這個程序就是單線程的程序。
線程的常用方法及構造器:
Thread提供的常用方法public void run()
線程的任務方法public void start()
啟動線程public String getName()
獲取當前線程的名稱,線程名稱默認是Thread-索引public void setName(String name)
為線程設置名稱public static Thread currentThread()
獲取當前執行的線程對象public static void sleep(long time)
讓當前執行的線程休眠多少毫秒后,再繼續執行public final void join()...
讓調用當前這個方法的線程先執行完!Thread提供的常見構造器public Thread(String name)
可以為當前線程指定名稱public Thread(Runnable target)
封裝Runnable對象成為線程對象public Thread(Runnable target, String name)
封裝Runnable對象成為線程對象,并指定線程名稱
多線程
????????多線程是指從軟硬件上實現的多條執行流程的技術(多條線程由CPU負責調度執行)。
創建線程的方式
????????多線程的創建方式一:繼承Thread類
? ? ? ? ? ? ? ? 操作步驟:
????????????????????????① 定義一個子類MyThread繼承線程類java.lang.Thread,重寫run()方法
????????????????????????② 創建MyThread類的對象
????????????????????????③ 調用線程對象的start()方法啟動線程(啟動后還是執行run方法的)
????????????????方式一優缺點:
????????????????????????優點:編碼簡單
????????????????????????缺點:線程類已經繼承Thread,無法繼承其他類,不利于功能的擴展。
????????注意事項:
????????直接調用run方法會當成普通方法執行,此時相當于還是單線程執行。 只有調用start方法才是啟動一個新的線程執行。
????????不要把主線程任務放在啟動子線程之前。 這樣主線程是一直先跑完的,相當于是一個單線程的效果了。
/*** 方式一:繼承Thread類*/
public class MyThread extends Thread{@Overridepublic void run() {for (int i = 1; i <= 20; i++) {System.out.println("新的線程執行:"+i);}}
}
/*** 創建線程方式一:繼承Thread類* 1.創建一個繼承Thread類的子類* 2.重寫Thread類中的run方法,將此線程要執行的代碼寫在run方法中* 3.創建Thread類的子類的對象,調用start方法**/
public class ThreadDemo1 {public static void main(String[] args) {MyThread myThread = new MyThread();myThread.start();// 啟動線程(啟動后還是執行run方法) 交替執行
// myThread.run();//單線程for (int i = 1; i <= 20; i++) {System.out.println("主線程執行:"+i);}}}
????????多線程的創建方式二:實現Runnable接口
? ? ? ? ? ? ? ? 操作步驟:
????????????????????????① 定義一個線程任務類MyRunnable實現Runnable接口,重寫run()方法
????????????????????????② 創建MyRunnable任務對象
????????????????????????③ 把MyRunnable任務對象交給Thread處理。
????????????????????????④ 調用線程對象的start()方法啟動線程
????????????????方式二的優缺點
????????????????????????優點:任務類只是實現接口,可以繼續繼承其他類、實現其他接口,擴展性強。
????????????????????????缺點:需要多一個Runnable對象。
/*** 方式二: 實現Runnable接口*/
public class MyRunnable implements Runnable {@Overridepublic void run() {for (int i = 1; i <= 20; i++) {System.out.println("MyRunable"+i);}}
}
public class RunnableDemo2 {public static void main(String[] args) {// 創建線程任務對象MyRunnable myRunnable = new MyRunnable();// 創建線程Thread thread = new Thread(myRunnable);// 啟動線程thread.start();/*** 使用匿名內部類, 創建線程* 寫法* ① 可以創建Runnable的匿名內部類對象。* ② 再交給Thread線程對象。* ③ 再調用線程對象的start()啟動線程。*/new Thread(new Runnable(){@Overridepublic void run() {for (int i = 1; i <= 20; i++) {System.out.println("匿名內部類1:"+i);}}}).start();// 使用Lambda表達式 簡化new Thread(()-> {for (int i = 1; i <= 20; i++) {System.out.println("匿名內部類2:"+i);}}).start();for (int i = 1; i <= 20; i++) {System.out.println("main:"+i);}}}
????????線程的創建方式三:實現Callable接口,利用FutureTask類來實現
? ? ? ? ? ? ? ? 操作步驟:
????????????????????????① 創建任務對象 ? 定義一個類實現Callable接口,重寫call方法,封裝要做的事情,和要返回的數據。 ? 把Callable類型的對象封裝成FutureTask(線程任務對象)。
????????????????????????② 把線程任務對象交給Thread對象。
????????????????????????③調用Thread對象的start方法啟動線程。
????????????????????????④ 線程執行完畢后、通過FutureTask對象的的get方法去獲取線程任務執行的結果。
????????????????線程創建方式三的優缺點
????????????????????????優點:線程任務類只是實現接口,可以繼續繼承類和實現接口,擴展性強;可以在線程執行完畢后去獲取線程執行的結 果。
????????????????????????缺點:編碼復雜一點。
import java.util.concurrent.Callable;/*** 方式三:實現Callable接口*/
public class MyCallable implements Callable<String> {@Overridepublic String call() throws Exception {for (int i = 1; i <= 20; i++) {System.out.println("約嗎"+i);}return null;}
}
import java.util.concurrent.ExecutionException;
import java.util.concurrent.FutureTask;/*** 創建線程的第三種方式:實現Callable接口* 優點:* 線程任務類只是實現接口,可以繼續繼承類和實現接口,擴展性強;* 可以在線程執行完畢后去獲取線程執行的結果。**/
public class CallableDemo {public static void main(String[] args) throws ExecutionException, InterruptedException {// 創建線程任務對象MyCallable myCallable = new MyCallable();// 創建FutureTask對象, 構造方法中傳遞線程任務對象FutureTask<String> ft = new FutureTask<>(myCallable);// 創建線程對象, 構造方法中傳遞線程對象Thread thread = new Thread(ft);// 啟動線程thread.start();/**獲取線程執行結果線程必須在啟動之后,才能獲取到線程執行結果。get()方法會阻塞當前線程,直到獲取到線程執行結果。如果get()方法在獲取到線程執行結果之前,當前線程被中斷,那么會拋出InterruptedException異常。*/String s = ft.get();System.out.println(s);for (int i = 1; i <= 20; i++) {System.out.println("吃瓜"+i);}}}
什么是線程安全問題?
????????多個線程,同時操作同一個共享資源的時候,可能會出現業務安全問題。
線程安全賣票模擬:
public class Ticket implements Runnable{private int tivket=100;@Overridepublic void run() {while (true){if (tivket <=0){break;}else{try {Thread.sleep(100);} catch (InterruptedException e) {throw new RuntimeException(e);}tivket--;System.out.println(Thread.currentThread().getName()+ "在賣票,剩余" + tivket + "張票");}}}}
? ? ? ? 測試類:? ??
public class TicketDemo {public static void main(String[] args) {Ticket ticket = new Ticket();Thread thread1 = new Thread(ticket,"Thread1");Thread thread2 = new Thread(ticket,"Thread2");Thread thread3 = new Thread(ticket,"Thread3");thread1.start();thread2.start();thread3.start();}
}
? ?部分運行結果:
Thread1在賣票,剩余98張票
Thread2在賣票,剩余98張票
Thread3在賣票,剩余98張票
Thread1在賣票,剩余95張票
Thread3在賣票,剩余96張票
Thread2在賣票,剩余96張票
Thread3在賣票,剩余94張票
Thread1在賣票,剩余94張票
Thread2在賣票,剩余93張票
Thread3在賣票,剩余92張票
......
Thread1在賣票,剩余4張票
Thread3在賣票,剩余3張票
Thread2在賣票,剩余2張票
Thread1在賣票,剩余1張票
Thread3在賣票,剩余0張票
Thread2在賣票,剩余-1張票
Thread1在賣票,剩余-2張票
從結果可以看出,有重復賣票,還有票賣超了,這就是線程安全問題。
解決線程安全問題可以通過線程同步來解決。
線程同步
????????線程同步的核心思想:讓多個線程先后依次訪問共享資源,這樣就可以避免出現線程安全問題。
? ? ? ? 解決方案:
? ? ? ? 一:同步代碼塊
????????????????作用:把訪問共享資源的核心代碼給上鎖,以此保證線程安全。
synchronized(同步鎖) {//出現線程安全問題的代碼}// 對于當前同時執行的線程來說,同步鎖必須是同一把(同一個對象),否則會出bug。
????????鎖對象的使用規范
???????? ????????建議使用共享資源作為鎖對象,對于實例方法建議使用this作為鎖對象。
????????????????對于靜態方法建議使用字節碼(類名.class)對象作為鎖對象。
????????二:同步方法
????????????????作用:把訪問共享資源的核心方法給上鎖,以此保證線程安全。
修飾符synchronized 返回值類型 方法名稱(形參列表)
{操作共享資源的代碼
}
原理:每次只能一個線程進入,執行完畢以后自動解鎖,其他線程才可以進來執行
????????同步方法底層原理
????????????????同步方法其實底層也是有隱式鎖對象的,只是鎖的范圍是整個方法代碼。
????????????????如果方法是實例方法:同步方法默認用this作為的鎖對象。
????????????????如果方法是靜態方法:同步方法默認用類名.class作為的鎖對象。
????????同步代碼塊好還是同步方法好?
????????????????范圍上:同步代碼塊鎖的范圍更小,同步方法鎖的范圍更大
????????????????可讀性:同步方法更好
? ? ? ? 三:Lock鎖
????????????????Lock鎖是JDK5開始提供的一個新的鎖定操作,通過它可以創建出鎖對象進行加鎖和解鎖,更靈活、更方便、更強大。
????????????????Lock是接口,不能直接實例化,可以采用它的實現類ReentrantLock來構建Lock鎖對象。
public ReentrantLock?() 獲得Lock鎖的實現類對象void lock() 獲得鎖void unlock() 釋放鎖
線程池
????????線程池就是一個可以復用線程的技術。
????????不使用線程池,用戶每發起一個請求,后臺就需要創建一個新線程來處理,下次新任務來了肯定又要創建新線程處理的, 創 建新線程的開銷是很大的,并且請求過多時,肯定會產生大量的線程出來,這樣會嚴重影響系統的性能。
????????創建線程池:
方式一:使用ExecutorService的實現類ThreadPoolExecutor自創建一個線程池對象。
public ThreadPoolExecutor(int corePoolSize,int maximumPoolSize,long keepAliveTime,TimeUnit unit,BlockingQueue<Runnable> workQueue,ThreadFactory threadFactory,RejectedExecutionHandler handler)//使用指定的初始化參數創建一個新的線程池對象// 創建線程池
ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3, // 線程池的核心線程數量10, // 線程池的最大線程數量60, // 臨時線程存活時間TimeUnit.SECONDS, // 臨時線程存活時間單位new ArrayBlockingQueue<>(20), // 任務隊列Executors.defaultThreadFactory(), // 線程工廠new ThreadPoolExecutor.AbortPolicy() // 拒絕策略
);
????????參數一:corePoolSize : 指定線程池的核心線程的數量。
????????參數二:maximumPoolSize:指定線程池的最大線程數量。
????????參數三:keepAliveTime :指定臨時線程的存活時間。 正式工:3 最大員工數:5 臨時工:2 臨時工空閑多久被開除
????????參數四:unit:指定臨時線程存活的時間單位(秒、分、時、天)
????????參數五:workQueue:指定線程池的任務隊列。 客人排隊的地方
????????參數六:threadFactory:指定線程池的線程工廠。 負責招聘員工的(hr)
????????參數七:handler:指定線程池的任務拒絕策略(線程都在忙,任務隊列也滿了的時候,新任務來了該怎么處理)
方式二:使用Executors(線程池的工具類)調用方法返回不同特點的線程池對象。
poolExecutor.execute();
import java.util.List;
import java.util.concurrent.*;/*** void execute(Runnable command) 執行 Runnable 任務* Future<T> submit(Callable<T> task) 執行 Callable 任務,返回未來任務對象,用于獲取線程返回的結果* void shutdown() 等全部任務執行完畢后,再關閉線程池!* List<Runnable> shutdownNow() 立刻關閉線程池,停止正在執行的任務,并返回隊列中未執行的任*/
public class Demo2 {public static void main(String[] args) {// 創建線程池ThreadPoolExecutor poolExecutor = new ThreadPoolExecutor(3,10,60,TimeUnit.HOURS,new ArrayBlockingQueue<>(20),Executors.defaultThreadFactory(),new ThreadPoolExecutor.CallerRunsPolicy());for (int i = 1; i <= 16; i++) {/*** 創建線程* 新任務提交時發現核心線程都在忙,任務隊列也滿了,* 并且還可以創建臨時線程,此時才會創建臨時線程** 核心線程和臨時線程都在忙,任務隊列也滿了(最大線程數10+隊列數20)* 新的任務過來的時候才會開始拒絕任務。**/// 創建線程, 執行Runnable任務poolExecutor.execute(new Student("小紅"+i));// 創建線程, 執行Callable任務poolExecutor.execute(new FutureTask<>(new Student2("張三"+i)));}// 關閉線程池poolExecutor.shutdown();}}class Student implements Runnable{private String name;public Student(String name){this.name=name;}@Overridepublic void run() {System.out.println(Thread.currentThread().getName()+"在教:"+name+"學游泳");}
}class Student2 implements Callable {private String name;public Student2(String name){this.name=name;}@Overridepublic Object call() throws Exception {System.out.println(Thread.currentThread().getName()+"在教:"+name+"學游泳");return null;}
}
任務拒絕策略:
ThreadPoolExecutor.AbortPolicy()
丟棄任務并拋出RejectedExecutionException異常。是默認的策略ThreadPoolExecutor. DiscardPolicy()
丟棄任務,但是不拋出異常,這是不推薦的做法ThreadPoolExecutor. DiscardOldestPolicy()
拋棄隊列中等待最久的任務 然后把當前任務加入隊列中ThreadPoolExecutor. CallerRunsPolicy()
由主線程負責調用任務的run()方法從而繞過線程池直接執行
并發/并行:
????????并發的含義 :進程中的線程是由CPU負責調度執行的,但CPU能同時處理線程的數量有限,為了保證全部線程都能往前執行, CPU會輪詢為系統的每個線程服務,由于CPU切換的速度很快,給我們的感覺這些線程在同時執行,這就是并發。
????????并行的理解: 在同一個時刻上,同時有多個線程在被CPU調度執行。
#學無止盡? ? #記錄并分享我的學習日常