OIer數學基礎
程式:一段靜態的程式碼
程序:正在執行的一個程式,系統在執行時為每一個程序分配不同的記憶體區域。
執行緒:程序的進一步細化,與程序共享一個記憶體區域,但是有獨立的執行棧和程式計數器。
單核CPU:假的多執行緒,一個執行緒執行一次,時間間隔較短,類似多車道,一個收費站。
多核CPU:更好的發揮多執行緒的效率。
一個java程式至少有三個執行緒:main()、gc()垃圾回收、異常處理執行緒。
並行:多個CPU執行多個任務
併發:一個CPU執行多個任務,如:秒殺,多人做同一件事
多執行緒的優點:
- 提高程式的相應,對於圖形化介面,提高使用者的體驗
- 提高cpu的利用率。
- 將程式拆分成多個任務,方便程式碼的獨立執行。
1、執行緒的建立和使用
執行緒的建立
方式一:繼承於Thread
- 建立一個繼承Thread類的子類
- 重寫run()方法
- 建立Thread類的子類
- 通過子類物件呼叫start()方法
start()方法:①啟動該執行緒 ②呼叫run()方法
注意:
- 直接呼叫run()方法並不會開啟新的執行緒,是主執行緒正常執行run()方法。
- 不可以兩次執行(同一物件呼叫2次start方法)相同的執行緒,必須重新new 執行緒物件。
方式二:實現Runnable介面
- 建立一個實現Runnable介面的類。
- 實現類實現run()方法。
- 建立實現類物件
- new Thread(實現類物件)
- 通過Thread類的物件呼叫 start()
Thread中的常用方法:
- start(),啟用執行緒,呼叫run()
- run(),將需要多執行緒需要執行的方法寫裡面
- currentThread(),靜態方法,返回當前執行程式碼的執行緒
- getName(),獲取執行緒名
- setName(),設定執行緒名 / new Thread("執行緒名");
- yield(),釋放當前cpu執行權
- join():線上程A中呼叫執行緒B的join方法,此時A就進入阻塞狀態,直到B執行完,A才繼續執行。
- sleep(long millitime):讓當前執行緒睡眠一定的毫秒時間
- isAlive():判斷當前執行緒是否存活
執行緒的排程 :
- 執行緒的優先順序:
MAX_PRIORITY :10
NORM_PRIORITY :5 預設優先順序
MIN_PRIORITY :1
注意:高優先順序的執行緒要搶佔低優先順序的執行緒cpu執行權,但是隻是從概率上講,並不是一定。
- 獲取或設定當前執行緒的優先順序
getPriority(): 獲取
setPriority();設定
比較兩種建立執行緒的方式:
開發中,優先選擇實現Runnable介面的方式。
原因:
- 實現的方式沒有類單繼承的侷限性
- 實現的方式更適合處理多個執行緒共有資料的情況
2、執行緒的生命週期
- 新建:呼叫start()
- 就緒:等待取得cpu資源
- 執行:
- 阻塞:①sleep() ②join() ③等待同步鎖 ④wait()
- 死亡:①執行完 ②stop() ③error/exception且沒有處理。
3、執行緒的同步
在java中,我們通過同步機制,來解決執行緒的安全問題。
同步的方式:
好處:解決了執行緒的安全問題
侷限性:操作同步程式碼時,只有一個執行緒能操作,效率比較低。
方式一:同步程式碼塊
synchronized(同步監視器){
//需要被同步的程式碼
}
說明:
- 需要操作共享資料的,就視為同步的程式碼。
- 同步監視器(鎖),任何一個類的物件都可以充當鎖。要求:多個執行緒共用一把鎖
- 在實現Runnable介面的方式中,可以使用this充當鎖。
- 在繼承Thread的方式中,慎用this充當鎖。可考慮當前物件
方式二:同步方法
操作共享資料的程式碼完整宣告在一個方法中,可使用同步方法的方式。
實現Runnable介面的方式
public synchronize void a(){
}
繼承Thread的方式
public static synchronize void a(){
}
注意:
- 在該方式中,沒有顯式的鎖。
- 非靜態的同步方法,預設鎖是this。
- 靜態的同步方法,瑣是當前類本身。
改寫單例模式中的懶漢式為執行緒安全:
class Bank{
private Bank(){}
private static Bank instance = null;
private static Bank getInstance(){
if (instance == null){
synchronized (Bank.class){
if (instance == null){
instance = new Bank();
}
}
}
return instance;
}
}
4、執行緒的死鎖
理解:不同執行緒佔用對方需要的資源不釋放,處於僵持的狀態,都在互相等待。
說明:出現死鎖後程序並不會出現異常,只是所有執行緒都處於阻塞的狀態。
解決方法:
- 專門的演算法、原則
- 儘量減少同步資源的定義
- 避免巢狀同步
5、執行緒的同步--鎖
Lock鎖-----jdk5.0新增
-
例項化 ReentrantLock()物件
ReentrantLock lock = new ReentrantLock();
-
呼叫鎖定方法lock.lock();
-
解鎖lock.unlock();
synchronize 與 Lock 異同? (面試題)
-
相同:都可以解決執行緒安全問題
-
不同:synchronize 機制在執行完相應的同步程式碼後,自動釋放鎖。
Lock需要手動上鎖(lock())和解鎖(unLock());
使用順序(建議/非必須)
Lock-->同步程式碼塊-->同步方法
如何解決執行緒安全問題?有幾種方式?(面試題)
三種:同步塊,同步方法,鎖Lock
6、執行緒的通訊
執行緒通訊的三個方法:
- wait() : 一旦執行此方法,當前執行緒就進入阻塞狀態,並釋放鎖
- notify() : 執行此方法,喚醒被wait()的執行緒,如果有多個,就喚醒優先順序高的
- notifyAll() : 執行此方法,喚醒全部wait()的執行緒
注意:
- 三個方法只能使用在同步程式碼塊或同步方法中。
- 三個方法的呼叫者,必須是同步程式碼塊或同步方法中的監視器(鎖),否者會出現異常。
- 三個方法定義在Object中,不是Tread中。
例項:兩個執行緒交替列印1-100
public class Test {
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();
}
}
class Number implements Runnable{
int number = 1 ;
@Override
public void run() {
while (true){
synchronized (this) {
notify();
if (number<=100){
System.out.println(Thread.currentThread().getName()+":"+number);
number++;
}
else{
break;
}
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
}
sleep()和 wait()方法的異同? (面試題)
- 相同:都可以使執行緒阻塞
- 不同:
- 兩者宣告的位置不同,sleep()宣告在Thread類,而wait()宣告在Object類。
- sleep可以在任何位置使用,而wait()只能在同步程式碼塊和同步方法中使用。
- 當兩者都使用在同步程式碼塊和同步方法中時,sleep()不會釋放鎖,wait()會釋放鎖。
例題生產者消費者
產品小於0個不能購買,大於0個可購買。
產品大於20不能再生產,不足20繼續生產。
銷售類
class Clerk{
private int num = 0;
public synchronized void produceProduct() {
if(num<20){
num++;
try {
Thread.sleep(20);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":開始生產第"+num+"個產品");
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
public synchronized void customerProduct() {
if(num>0){
try {
Thread.sleep(10);
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println(Thread.currentThread().getName()+":開始消費第"+num+"個產品");
num--;
notify();
}else{
try {
wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
生產者
class Producter extends Thread{
private Clerk clerk;
public Producter(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true){
clerk.produceProduct();
}
}
}
消費者
class Customer extends Thread{
private Clerk clerk;
public Customer(Clerk clerk) {
this.clerk = clerk;
}
@Override
public void run() {
while (true){
clerk.customerProduct();
}
}
}
測試
public class ProductTest {
public static void main(String[] args) {
Clerk clerk = new Clerk();
Producter p1 = new Producter(clerk);
Customer c1 = new Customer(clerk);
Customer c2 = new Customer(clerk);
p1.setName("生產者1");
c1.setName("消費者1");
c2.setName("消費者2");
p1.start();
c1.start();
c2.start();
}
}
7、新增執行緒的建立方式
方式一:實現Callable介面
JDK5.0 新增
與實現runnable介面相比更加強大:
- call方法中可以有返回值。
- call()可以丟擲異常,被外面的操作捕獲。
- Callable支援泛型。
步驟:
- 建立Callable的實現類
- 將要執行的操作寫在Callable實現類的call方法中。
- 建立callable的實現類物件
- 建立FutureTask 物件 ,callable的實現類物件作為引數。
- new Thread(FutureTask 物件).start()開啟執行緒;
- 可使用FutureTask 物件的get()方法,獲取call()方法的返回值。
方式二:使用執行緒池
思路:提前建立好多個執行緒,放入執行緒池,隨用隨取。
好處:①提高響應速度②降低資源消耗
③便於執行緒管理:
- 執行緒池大小
- 執行緒數
- 沒有任務的持續時間
步驟:
//1.提供指定數量的執行緒池
ExecutorService executorService = Executors.newFixedThreadPool(10);
//2.執行指定執行緒操作,需要提供實現runnable/callable類的物件
executorService.execute(new Number());
//executorService.submit(callable callable);
//3.關閉執行緒池
executorService.shutdown();
建立多執行緒有幾種方式?(面試題)
4種:繼承Tread
實現Runnable
實現Callable
執行緒池