目錄
一、基礎信息
- 程序(program):完成特定任務、用某種語言編寫的一組指令的集合,即一段靜態的代碼
- 進程(process):程序的一次執行過程,即正在運行的一個程序
- 線程(thrad):一個程序內部的一條執行路徑
二、Thread的創建方式
- 繼承Thrad類
/**
* @author zac
* @data 2020-04-2020/4/21 12:25
*
* 多線程的創建:方式一:繼承於Thread類
* 1. 創建一個繼承於Thread類的子類
* 2. 創建Thread類的run() --> 將磁線程的操作聲明在run()
* 3. 創建Thread類的子類的對象
* 4. 通過此對象調用start()
*/
//1. 創建繼承於Thrad的子類
class Mythread extends Thread {
//2. 重寫Thread類的run()
@Override
public void run() {
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i) ;
}
}
}
public class ThreadTest1 {
public static void name(int i) {
System.out.println("hello") ;
}
public static void main(String[] args) {
//3. 創建Thread類的子類對象
Mythread m1 = new Mythread();
//4. 通過此對象調用start():1)啓動線程 2)調用當前線程的run方法
m1.start();
//問題一:我們不能通過直接調用run()的方式啓動線程
//m1.run() ;
//問題二:再啓動一個線程,遍歷100以內的偶數,不可以讓已經start()的線程去執行。會報IllegalThreadStateException異常
//m1.start();
//需要重新創建一個線程的對象
Mythread m2 = new Mythread();
m2.start();
//如下的方法仍然在main線程中執行的
for(int i=0; i<100; i++) {
System.out.println(Thread.currentThread().getName() + ":" + i + "*") ;
}
}
}
- 實現Runnable接口
/**
* @author zac
* @Classname ThreadTest2
* @Description
* @date 2020-04-21 20:29
*
* 創建多線程的方式二:實現Runnable接口
* 1. 創建一個實現了Runnable接口的類
* 2. 實現類去實現Runnable中的抽象方法:run()
* 3. 創建實現類的對象
* 4. 將此對象作爲參數傳遞到Thread類的構造器中,創建Thread類的對象
* 5. 通過Thrad類的對象調用start()
*
*/
//1. 創建一個實現了Runnable接口的類
class MyThrad2 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 ThreadTest2 {
public static void main(String[] args) {
//3. 創建實現類的對象
MyThrad2 m12 = new MyThrad2();
//4. 將此對象作爲參數傳遞到Thread類的構造器中,創建Thread類的對象
Thread t1 = new Thread(m12);
t1.setName("線程1");
//5. 通過Thread類的對象調用start():1)啓動線程 2)調用當前線程的run方法
t1.start();
Thread t2 = new Thread(m12);
t2.setName("線程2");
t2.start();
}
}
比較創建線程的兩種方式:
- 開發中:優先選擇實現Runnable接口的方式
- 原因:
- 實現的方式沒有類的單繼承性的侷限性
- 實現的方式更適合來處理多個線程共享數據的情況
- 詳細:public class Thread implements Runnbale
- 相同點:兩種方式都需要重寫run(),將線程要執行的邏輯聲明在run()中
三、Thread有關方法
測試Thread中常用的方法:
- start():啓動當前線程,調用當前線程的run()
- run():通常重寫Thrad類中的此方法,將創建的線程要執行的操作聲明在此方法
- currentThread():靜態方法,返回執行當前代碼的線程
- getName():獲取當前線程的名字
- setName():設置當前線程的名字
- yield():釋放當前CPU的執行權
- join() :在線程A中調用線程B的join(),此時線程A就進入阻塞狀態,知道線程B完成執行完以後線程A才結束阻塞狀態
- stop():已過時,當執行此方法時,強制結束當前線程 9.sleep(long millitime):讓當前線程"睡眠"指定的millitime毫秒。在指定的millitime毫秒時間內,當前線程是阻塞狀態
- isLive():判斷當前線程是否存活
線程的優先級
-
- MIN_PRIORITY = 1;
- NORM_PRIORITY = 5;
- MAX_PRIORITY = 10;
- 如何設置當前線程優先級:
- getPriority():
- setPriority(int p):
- 說明:高優先級的線程要搶佔低優先級線程cpu的執行權,但是隻是從概率上講,高優先級的線程高概念的情況下被執行。並不意味着只有高優先級的線程執行完完以後,低優先級線程才執行
- 線程通信:wait()、notify()、notifyAll()在Object()中
四、線程的狀態
- 五種狀態
- 新建(new)
- 就緒:調用star()之後
- 運行
- 阻塞
- 死亡
五、同步
-
線程的安全問題:賣票過程中,出現了重票、錯票
-
出現的原因:當某個線程操作車票的過程中,尚未操作完成時, 其他線程操作進來,也操作車票
-
如何解決:當一個線程操作ticket的時候,其他線程不能參與進來。 直到線程a操作完ticket時,線程纔可以操作纔可以操作ticket。這種情況即使線程a出現了阻塞,已不能被改變
-
在java中,通過同步機制,來解決線程的安全問題
- 方式一:同步代碼塊
synchronized(同步監視器) {
//需要被同步的代碼
}- 說明:
- 操作共享數據的代碼,即爲需要被同步的代碼
- 共享數據:多個線程共同操作的變量,比如:ticket就是共享數據
- 同步監視器:俗稱:鎖。任何類的對象,都可充當鎖
- 要求:多個線程必須共用一把鎖
- 補充:在實現Runnable接口創建多線程的方式中,我們可以考慮使用this充當同步監視器
- 在繼承Thread類創建多線程的方式中,慎用this充當同步監視器,考慮使用當前類當同步監視器(Xxx.class)
- 說明:
- 方式二:同步方法
-
- 如果操作共享數據的代碼完整的聲明在一個方法中,我們不妨將此方法聲明同步的
- 關於同步方法的總結:
- 同步方法仍然涉及到同步監視器,只是不需要我們顯式的聲明。
- 非靜態的同步方法,同步監視器是:this
- 靜態的同步方法,同步監視器是:當前類本身(Xxx.class)
- 同步方法仍然涉及到同步監視器,只是不需要我們顯式的聲明。
- 方式一:同步代碼塊
-
- 同步的方式,解決了線程的安全問題。---好處
- 操作同步代碼時,只能有一個線程參與,其他線程等待。相當於是一個單線程的過程,效率低。---侷限性
-
解決線程安全問題的方式三:Lock鎖---JDK5.0新增
-
面試題:synchronized與Lock的異同?
- 相同:二者都可以解決線程安全問題
- 不同:synchronized機制在執行完相應的同步代碼以後,
自動的釋放同步監視器Lock需要手動的啓動同步(Lock()),同時結束同步也需要手動的實現(unlock())
-
優先使用順序:
Lock→同步代碼塊(已經進入了方法體,分配了相應資源)→同步方法(在方法體之外)
-
如何解決線程安全問題?有幾種?
3種 synchronized 同步代碼塊和同步方法 lock(ReentrantLock)
-
-
線程'通信'
-
案例:線程通信的例子:使用兩個線程打印1-100。線程1,線程2交替打印
-
使用到的三個方法:
說明:
1.wait(),notify(),notifyAL()三個方法必須使用在同步代碼塊或同步方法中。 2.wait(),notify(),notifyAL()三個方法的調用者必須是同步代碼塊或同步方法中的同步監視器否則,會出現ILLegaLMonitorStateException異常 3.wait(), notify(), notifyAll()三個方法是定義在java.Lang.Object類中。
- wait():一旦執行此方法,當前線程就進入阻塞狀態,並釋放同步監視器
- notify():一旦執行此方法,就會喚醒被wait的一個線程。如果有多個線程被wait,就喚醒優先級高的那個。
- notifyALL():一旦執行此方法,就會喚醒所有被wait的線程。
-
面試題:sleep()和wait()的異同?
- 相同點:一旦執行方法,都可以使得當前的線程進入阻塞狀態。
- 不同點:
- 兩個方法聲明的位置不同:Thread類中聲明sleep(),Object類中聲明wait()
- 調用的要求不同:sleep()可以在任何需要的場景下調用。wait()必須使用在同步代碼塊中
- 關於是否釋放同步監視器:如果兩個方法都使用在同步代碼塊或同步方法中,sleep()不會釋放鎖,wait()會釋放鎖
-
-
第三種創建線程方式:Callable(有返回值)
/**
* @author zac
* @Classname ThreadNew
* @Description
* @date 2020-04-26 23:03
*
* 如何理解實現callable接口的方式創建多線程比實現Runnable接口創建多線程方式強大
* 1. call()可以有返回值的。
* 2. call()可以拋出異常,被外面的操作捕獲,獲取異常的信息
* 3. Callable是支持泛型的
*
*/
//1創建一個Callable的實現類
class NewTread implements Callable{
//2.把需要執行的操作聲明在call中
@Override
public Object call() throws Exception {
int sum = 0 ;
for (int i=0; i<=100; i++){
if (i % 2 == 0){
System.out.print(i + "\t") ;
sum += i ;
}
}
return sum;
}
}
public class ThreadNew {
public static void main(String[] args) {
//3.創建Callable實現類的對象
NewTread n1 = new NewTread();
//4.將實現類的對象作爲FutureTask構造器的對象傳入,創建FutureTask類對象
FutureTask f1 = new FutureTask(n1);
//5.將FuTureTask對象作爲Thread構造器的對象傳入,創建Thrad對象,調用start方法
new Thread(f1).start();
try {
//6.獲取Callable中call方法的返回值
//get()返回值即爲FutureTask構造器參數Callable實現類重寫的call()的返回值。
Object sum = f1.get();
System.out.println("\n" + sum) ;
} catch (InterruptedException e) {
e.printStackTrace();
} catch (ExecutionException e) {
e.printStackTrace();
}
}
}
- 第四種創建線程方式:線程池
/**
* @author zac
* @Classname ThreadPool
* @Description
* @date 2020-04-27 0:07
*
* 創建線程的方式四:使用線程池
* 好處:
* 1.提高響應速度(減少了創建新線程的時間)
* 2.降低資源消耗(重複利用線程池中線程,不需要每次都創建)
* 3.便於線程管理
* HorePoolsize:核心池的大小
* maximumPooLSize:最大線程數
* keepAliveTime:線程沒有任務時最多保持多長時間後會終止
*
*/
class NumberThread implements Runnable{
@Override
public void run() {
for (int i=0; i<=100; i++){
if (i % 2 == 0){
System.out.print(Thread.currentThread().getName()+ ":" + i + "\t") ;
}
}
}
}
class NumberThread1 implements Runnable{
@Override
public void run() {
for (int i=0; i<=100; i++){
if (i % 2 != 0){
System.out.print(Thread.currentThread().getName()+ ":" + i + "\t") ;
}
}
}
}
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.指定線程的操作,需要提供實現Runable接口或Callable接口實現類的對象
service.execute(new NumberThread());//適合使用於Runnable
service.execute(new NumberThread1());//適合使用於Runnable
// service.submit(Callable callable);//適合使用於Callable
//3.關閉連接池
service.shutdown();
}
}