一、線程的4種創建和使用
方式一:繼承Thread類
1. 創建一個繼承於Thread類的子類
2. 重寫Thread類的run() --> 將此線程執行的操作聲明在run()中
3. 創建Thread類的子類的對象
4. 通過此對象調用start()
class MyThread extends Thread { //2. 重寫Thread類的run() @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest { public static void main(String[] args) { //3. 創建Thread類的子類的對象 MyThread t1 = new MyThread(); //4.通過此對象調用start():①啟動當前線程 ② 調用當前線程的run() t1.start(); //問題一:我們不能通過直接調用run()的方式啟動線程。 // t1.run(); //問題二:再啟動一個線程,遍歷100以內的偶數。不可以還讓已經start()的線程去執行。會報IllegalThreadStateException // t1.start(); //我們需要重新創建一個線程的對象 MyThread t2 = new MyThread(); t2.start(); } }
方式二:實現Runnable接口
- 創建一個實現了Runnable接口的類
- 實現類去實現Runnable中的抽象方法:run()
- 創建實現類的對象
- 將此對象作為參數傳遞到Thread類的構造器中,創建Thread類的對象
- 通過Thread類的對象調用start()
class MThread implements Runnable{ //2. 實現類去實現Runnable中的抽象方法:run() @Override public void run() { for (int i = 0; i < 100; i++) { if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } } public class ThreadTest1 { public static void main(String[] args) { //3. 創建實現類的對象 MThread mThread = new MThread(); //4. 將此對象作為參數傳遞到Thread類的構造器中,創建Thread類的對象 Thread t1 = new Thread(mThread); t1.setName("線程1"); //5. 通過Thread類的對象調用start():① 啟動線程 ②調用當前線程的run()-->調用了Runnable類型的target的run() t1.start(); //再啟動一個線程,遍歷100以內的偶數 Thread t2 = new Thread(mThread); t2.setName("線程2"); t2.start(); } }
比較創建線程的兩種方式:
開發中:優先選擇:實現Runnable接口的方式
原因:1. 實現的方式沒有類的單繼承性的局限性
2. 實現的方式更適合來處理多個線程有共享數據的情況。
聯系:public class Thread implements Runnable
相同點:兩種方式都需要重寫run(),將線程要執行的邏輯聲明在run()中。
方式三:實現Callable接口 --- JDK 5.0新增
如何理解實現Callable接口的方式創建多線程比實現Runnable接口創建多線程方式強大?
- call()可以有返回值的。
- call()可以拋出異常,被外面的操作捕獲,獲取異常的信息
- Callable是支持泛型的
- 需要借助FutureTask類,比如獲取返回結果( FutrueTask是Futrue接口的唯一的實現類)
class NumThread implements Callable{ //2.實現call方法,將此線程需要執行的操作聲明在call()中 @Override public Object call() throws Exception { int sum = 0; for (int i = 1; i <= 100; i++) { if(i % 2 == 0){ System.out.println(i); sum += i; } } return sum; } } public class ThreadNew { public static void main(String[] args) { //3.創建Callable接口實現類的對象 NumThread numThread = new NumThread(); //4.將此Callable接口實現類的對象作為傳遞到FutureTask構造器中,創建FutureTask的對象 FutureTask futureTask = new FutureTask(numThread); //5.將FutureTask的對象作為參數傳遞到Thread類的構造器中,創建Thread對象,並調用start() new Thread(futureTask).start(); try { //6.獲取Callable中call方法的返回值 //get()返回值即為FutureTask構造器參數Callable實現類重寫的call()的返回值。 Object sum = futureTask.get();//會發生阻塞情況 System.out.println("總和為:" + sum); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
方式四:使用線程池
好處:
1.提高響應速度(減少了創建新線程的時間)
2.降低資源消耗(重復利用線程池中線程,不需要每次都創建)
3.便於線程管理
corePoolSize:核心池的大小
maximumPoolSize:最大線程數
keepAliveTime:線程沒有任務時最多保持多長時間后會終止
class NumberThread implements Runnable{ @Override public void run() { for(int i = 0;i <= 100;i++){ if(i % 2 == 0){ System.out.println(Thread.currentThread().getName() + ": " + i); } } } } public class ThreadPool { public static void main(String[] args) { //1. 提供指定線程數量的線程池 ExecutorService service = Executors.newFixedThreadPool(10); ThreadPoolExecutor service1 = (ThreadPoolExecutor) service; //設置線程池的屬性 // System.out.println(service.getClass()); // service1.setCorePoolSize(15); // service1.setKeepAliveTime(); //2.執行指定的線程的操作。需要提供實現Runnable接口或Callable接口實現類的對象 service.execute(new NumberThread());//適合適用於Runnable // service.submit(Callable callable);//適合使用於Callable //3.關閉連接池 service.shutdown(); } }
Thread中的常用方法:
1. start():啟動當前線程;調用當前線程的run() 2. run(): 通常需要重寫Thread類中的此方法,將創建的線程要執行的操作聲明在此方法中 3. currentThread():靜態方法,返回執行當前代碼的線程 4. getName():獲取當前線程的名字 5. setName():設置當前線程的名字 6. yield():釋放當前cpu的執行權 7. join():在線程a中調用線程b的join(),此時線程a就進入阻塞狀態,直到線程b完全執行完以后,線程a才 結束阻塞狀態。 8. stop():已過時。當執行此方法時,強制結束當前線程。 9. sleep(long millitime):讓當前線程“睡眠”指定的millitime毫秒。在指定的millitime毫秒時間內,當前 線程是阻塞狀態。 10. isAlive():判斷當前線程是否存活
線程優先級的設置:
- MAX_PRIORITY:10
- MIN _PRIORITY:1
- NORM_PRIORITY:5 -->默認優先級
getPriority():獲取線程的優先級
setPriority(int p):設置線程的優先級
說明:高優先級的線程要搶占低優先級線程cpu的執行權。但是只是從概率上講,高優先級的線程高概率的情況下被執行。並不意味着只有當高優先級的線程執行完以后,低優先級的線程才執行
二、線程的生命周期
Java語言使用Thread類 及其子類的對象來表示線程,在它的一個完整的生命周期中通常要經歷如下的五 種狀態:
新建: 當一個Thread類或其子類的對象被聲明並創建時,新生的線程對象處於新建 狀態
就緒:處於新建狀態的線程被start()后,將進入線程隊列等待CPU時間片,此時它已 具備了運行的條件,只是沒分配到CPU資源
運行:當就緒的線程被調度並獲得CPU資源時,便進入運行狀態, run()方法定義了線 程的操作和功能
阻塞:在某種特殊情況下,被人為掛起或執行輸入輸出操作時,讓出 CPU 並臨時中 止自己的執行,進入阻塞狀態
死亡:線程完成了它的全部工作或線程被提前強制性地中止或出現異常導致結束
三、線程的同步
在Java中,我們通過同步機制,來解決線程的安全問題。
方式一:synchronized
1. 同步代碼塊:
synchronized (對象){
// 需要被同步的代碼;
} 2. synchronized還可以放在方法聲明中,表示整個方法為同步方法。
例如: public synchronized void show (String name){ …. }
說明:
1.操作共享數據的代碼,即為需要被同步的代碼。 -->不能包含代碼多了,也不能包含代碼少了。
2.共享數據:多個線程共同操作的變量。比如:ticket就是共享數據。
3.同步監視器,俗稱:鎖。任何一個類的對象,都可以充當鎖。
要求:多個線程必須要共用同一把鎖。
4.同步方法的鎖:靜態方法(類名.class)、非靜態方法(this)
同步代碼塊:自己指定,很多時候也是指定為this或類名.class
例:
1.使用同步代碼塊解決Runnable接口的方式的同步問題
class Window1 implements Runnable{ private int ticket = 100; @Override public void run() { while(true){ synchronized (this){//此時的this:唯一的Window1的對象 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket); ticket--; } else { break; } } } } } public class WindowTest1 { public static void main(String[] args) { Window1 w = new Window1(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
2.使用同步方法解決繼承thread方式的同步問題
private static int ticket = 100; @Override public void run() { while (true) { show(); } } private static synchronized void show(){//同步監視器:Window4.class //private synchronized void show(){ //同步監視器:t1,t2,t3。此種解決方式是錯誤的 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":賣票,票號為:" + ticket); ticket--; } } } public class WindowTest4 { public static void main(String[] args) { Window4 t1 = new Window4(); Window4 t2 = new Window4(); Window4 t3 = new Window4(); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
3.單例模式之懶漢式(線程安全)
public class BankTest { } class Bank{ private Bank(){} private static Bank instance = null; public static Bank getInstance(){ //方式一:效率稍差 // synchronized (Bank.class) { // if(instance == null){ // // instance = new Bank(); // } // return instance; // } //方式二:效率更高 if(instance == null){ synchronized (Bank.class) { if(instance == null){ instance = new Bank(); } } } return instance; } }
方式二·:Lock鎖
1.java.util.concurrent.locks.Lock接口是控制多個線程對共享資源進行訪問的 工具。鎖提供了對共享資源的獨占訪問,每次只能有一個線程對Lock對象 加鎖,線程開始訪問共享資源之前應先獲得Lock對象。
2. ReentrantLock 類實現了 Lock ,它擁有與 synchronized 相同的並發性和 內存語義,在實現線程安全的控制中,比較常用的是ReentrantLock,可以 顯式加鎖、釋放鎖
class Window implements Runnable{ private int ticket = 100; //1.實例化ReentrantLock private ReentrantLock lock = new ReentrantLock(); @Override public void run() { while(true){ try{ //2.調用鎖定方法lock() lock.lock(); if(ticket > 0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":售票,票號為:" + ticket); ticket--; }else{ break; } }finally { //3.調用解鎖方法:unlock() lock.unlock(); } } } } public class LockTest { public static void main(String[] args) { Window w = new Window(); Thread t1 = new Thread(w); Thread t2 = new Thread(w); Thread t3 = new Thread(w); t1.setName("窗口1"); t2.setName("窗口2"); t3.setName("窗口3"); t1.start(); t2.start(); t3.start(); } }
1.synchronized 與 Lock的異同?
相同:二者都可以解決線程安全問題
不同:synchronized機制在執行完相應的同步代碼以后,自動的釋放同步監視器
Lock需要手動的啟動同步(lock()),同時結束同步也需要手動的實現(unlock())
2.優先使用順序:
Lock 同步代碼塊(已經進入了方法體,分配了相應資源) 同步方法(在方法體之外)
四、線程通信
例:使用兩個線程打印 1-100。線程1, 線程2 交替打印
*/ class Number implements Runnable{ private int number = 1; private Object obj = new Object(); @Override public void run() { while(true){ synchronized (obj) { obj.notify(); if(number <= 100){ try { Thread.sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + number); number++; try { //使得調用如下wait()方法的線程進入阻塞狀態 obj.wait(); } catch (InterruptedException e) { e.printStackTrace(); } }else{ break; } } } } } public class CommunicationTest { public static void main(String[] args) { Number number = new Number(); Thread t1 = new Thread(number); Thread t2 = new Thread(number); t1.setName("線程1"); t2.setName("線程2"); t1.start(); t2.start(); } }
涉及到的三個方法:
- wait():一旦執行此方法,當前線程就進入阻塞狀態,並釋放同步監視器。
- notify():一旦執行此方法,就會喚醒被wait的一個線程。如果有多個線程被wait,就喚醒優先級高的那個。
- notifyAll():一旦執行此方法,就會喚醒所有被wait的線程。
說明:
1.wait(),notify(),notifyAll()三個方法必須使用在同步代碼塊或同步方法中。
2.wait(),notify(),notifyAll()三個方法的調用者必須是同步代碼塊或同步方法中的同步監視器。否則,會出現IllegalMonitorStateException異常
3.wait(),notify(),notifyAll()三個方法是定義在java.lang.Object類中。
sleep() 和 wait()的異同?
1.相同點:一旦執行方法,都可以使得當前的線程進入阻塞狀態。
2.不同點:1)兩個方法聲明的位置不同:Thread類中聲明sleep() , Object類中聲明wait()
2)調用的要求不同:sleep()可以在任何需要的場景下調用。 wait()必須使用在同步代碼塊或同步方法中
3)關於是否釋放同步監視器:如果兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖。
4)sleep()自動喚醒,wait()需要手動調用notify()和notifyAll()