Java多執行緒技術概述
介紹多執行緒之前要介紹執行緒,介紹執行緒則離不開程式。
首先 ,
程式 :是一個正在執行中的程式,每一個程式執行都有一個執行順序,該順序是一個執行路徑,或者叫一個控制單元;
執行緒:就是程式中的一個獨立控制單元,執行緒在控制著程式的執行。一個程式中至少有一個程式。
多執行緒:一個程式中不只有一個執行緒。
一、執行緒與程式
程式:通俗來解釋就是一個程式,一個App,開啟工作管理員可以看到當前電腦中正在執行的程式。
執行緒:一個程式中一般包含多個執行緒,開啟工作管理員也可以看到當前電腦中正在執行的執行緒每個各自執行自己的任務來實現程式的執行,當一個程式中的最後一個執行緒結束時,整個程式就結束了。
執行緒的6種狀態:
- NEW(未啟動的執行緒)
- RUNNABLE(執行的執行緒)
- BLOCKED(被阻塞的執行緒)
- WAITING(無限期等待的執行緒)
- TIMED_WAITING(有限期等待的執行緒)
- TERMINATED(已結束的執行緒)
二、Java中執行緒建立的三種方式
1.Thread類: 通過建立一個類來繼承Thread類,在這個類中重寫run()方法,通過這個類建立的物件就是一個執行緒。
class MyThread extends Thread{
@Override
public void run() {
//執行的Java語句
}
}
public static void main(String[] args) {
MyThread t = new MyThread();
//執行緒啟動
t.start();
}
2.Runnable介面:通過建立一個類來實現Runnable介面,在這個類中重寫run()方法,通過這個類建立的物件是一個執行緒任務,我們將這個任務傳給一個Thread物件即可執行這個執行緒任務。(推薦使用這種方式,傳入一個Runnable任務即可執行執行緒,跟使用執行緒池有關)
class MyRunnable implements Runnable{
@Override
public void run() {
//執行的Java語句
}
}
public static void main(String[] args) {
MyRunnable r = new MyRunnable1();
Thread t = new Thread(r);
t.start();
}
3.Callable介面(很少用的方式):建立方式與Runnable相似。建立此類執行緒會產生一個返回值,如果主程式要獲取這個返回值,則主程式會在Callable執行緒執行結束後再執行,不獲取的話,則兩個執行緒並行。
三、執行緒中一些常用的方法
Thread的常用方法
1.String getName() //獲取該執行緒的名字
2.void setName(String name) //設定該執行緒的名字
3.void start() //執行緒開始
4.static Thread currentThread() //獲取當前執行緒物件
5.static void sleep(long millis) //讓當前執行緒休眠,進入阻塞狀態,傳入的引數單位為毫秒
6.void setDaemon(boolean on) //將執行緒設定為守護執行緒或者使用者執行緒
7.void interrupt() //中斷此執行緒
8.int getPriority() //返回此執行緒的優先順序
9.void setPriority(int newPriority) //更改此執行緒的優先順序
10.Thread(Runnable target) //(構造方法)傳入一個Runnable任務
11.Thread(String name) //(構造方法)為執行緒取一個名字
1、interrupt方法和stop方法
執行緒在執行的過程中兩種中斷執行緒的方法,一個是stop()方法,一個是interrupt()方法。
Sun公司在JDK1.2版本的時候將stop()方法改為過時了,因為這種中斷執行緒的方式是在外部強制的,這可能會導致在中斷過程資料丟失,所以是不安全的。
使用interrupt()方法則是一種安全的方式,當線上程外部呼叫interrupt()方法時,JVM會給執行的執行緒傳遞一個異常物件,當執行緒接收到異常物件時,執行緒就會終止。
public static void main(String[] args) {
Thread t = new Thread(new MyRunnable());
t.start();
t.interrupt();
}
2、守護執行緒和使用者執行緒的設定
void setDaemon(boolean on)
使用setDaemon()方法可以將一個執行緒設定為守護執行緒或者使用者執行緒(引數為TRUE時,是守護執行緒,引數為FALSE時為使用者執行緒),一個執行緒被建立時預設是使用者執行緒。
當使用者執行緒和守護執行緒一起執行,當使用者執行緒結束時,則守護執行緒就結束。
四、執行緒安全
執行緒非同步:即多條執行緒同時執行一個任務時,這種情況下往往是出現資料錯亂的情況,例如兩個人同時對一個銀行賬戶進行取錢,賬戶中有10000元,兩個人同時取走5000元,結果賬戶中還剩餘5000元。所以這種執行緒非同步的情況是非常不安全的。
執行緒同步:即多條執行緒同時執行一個任務時,,當一個執行緒開始執行任務執行緒後,為這個任務加鎖,其他執行緒等待次執行緒執行完任務執行緒後再進行搶奪執行任務的時間片。
1、實現執行緒安全的方法(以售票視窗買票為例)
1.1、synchronized方法(顯式鎖)
同步程式碼塊
當一個售票視窗在賣票時,其他視窗等待。
語法:
synchronized (加鎖物件){
//Java語句
}
class Runnable implements Runnable{
private int count = 10;
private Object o = new Object();
public void run() {
while(true){
synchronized (o){
if(count>0){
System.out.println(Thread.currentThread().getName()+"買票中");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("餘票:"+count);
}else{
break;
}
}
}
}
}
同步方法
當時用同步方法時,當前加鎖的物件預設為當前物件this
class Runnable implements Runnable{
private int count = 10;
public void run() {
while(true){
boolean flag = sale();
if(!flag){
break;
}
}
}
public synchronized boolean sale(){
//判斷票數是否大於0,是返回true,否返回false
if(count>0){
System.out.println(Thread.currentThread().getName()+"買票中");
try {
//此處休眠0.5秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
//票賣出,票數減一
count--;
System.out.println("餘票:"+count);
return true;
}else{
return false;
}
}
}
1.2、Lock方法(隱式鎖)
使用Lock方法需要建立Lock物件,並在需要加鎖是手動加鎖,在需要解鎖時手動解鎖
class Runnable implements Runnable{
private int count = 10;
private Lock l = new ReentrantLock();
public void run() {
while(true){
l.lock();
if(count>0){
System.out.println(Thread.currentThread().getName()+"買票中");
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
count--;
System.out.println("餘票:"+count);
l.unlock();
}else{
l.unlock();
break;
}
}
}
}
1.3、顯式鎖和隱式鎖的區別
(1)Sync:Java中的關鍵字,是由JVM來維護的。是JVM層面的鎖。 (2)Lock:是JDK5以後才出現的具體的類。使用lock是呼叫對應的API。是API層面的鎖。
(3)在使用sync關鍵字的時候,我們使用者根本不用寫其他的程式碼,然後程式就能夠獲取和釋放鎖了。那是因為當sync程式碼塊執行完成之後,系統會自動的讓程式釋放佔用的鎖。Sync是由系統維護的,如果非邏輯問題的話話,是不會出現死鎖的。 (4)在使用lock的時候,我們使用者需要手動的獲取和釋放鎖。如果沒有釋放鎖,就有可能導致出現死鎖的現象。手動獲取鎖方法:lock.lock()。釋放鎖:unlock方法。需要配合tyr/finaly語句塊來完成。
(5)Sync是不可中斷的。除非丟擲異常或者正常執行完成 (6)Lock可以中斷的。
(7)Sync:非公平鎖 (8)lock:兩者都可以的。預設是非公平鎖。ReentrantLock(boolean fair),true是公平鎖,false是不公平鎖
五、死鎖
概述 A和B兩人分別進入試衣間1和試衣間2,A想等B出來後去試衣間2,自己則在試衣間1中等待,B想等A出來後去試衣間1,自己則在試衣間2中等待,最終2個人都在等對方出來,但是對方都不出來,導致一直僵持。
public class DeadLockTest {
public static void main(String[] args) {
A a = new A();
B b = new B();
//子執行緒中需要和主執行緒中同樣的A和B物件
R r = new R(a,b);
Thread t = new Thread(r);
t.start();
b.say(a);
}
}
class B{
public synchronized void say(A a){
System.out.println("BBBBB");
a.reply();
}
public synchronized void reply(){
System.out.println("bbbbb");
}
}
class A{
public synchronized void say(B b){
System.out.println("AAAAA");
b.reply();
}
public synchronized void reply(){
System.out.println("aaaaa");
}
}
class R implements Runnable{
private A a;
private B b;
public R(A a, B b) {
this.a = a;
this.b = b;
}
public void run() {
a.say(b);
}
}
六、生產者與消費者
當廚師在做菜時,服務員休息狀態,當廚師做完菜後,廚師休息,服務員端菜出去,等服務員端空盤子回來後,服務員繼續休息,叫廚師繼續做菜。依次迴圈
public class Test {
public static void main(String[] args) {
Food f = new Food();
Cook c = new Cook(f);
Waiter w = new Waiter(f);
//廚師執行緒
new Thread(c).start();
//服務員執行緒
new Thread(w).start();
}
}
class Cook implements Runnable{
private Food f;
public Cook(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
f.cook(i);
try {
//此處休眠0.5秒
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Waiter implements Runnable{
private Food f;
private boolean flag = true;
public Waiter(Food f) {
this.f = f;
}
@Override
public void run() {
for(int i = 0;i<10;i++){
f.get();
try {
Thread.sleep(500);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
}
class Food{
private String name;
private String tasty;
private boolean flag = true;
public Food() {
}
public Food(String name, String tasty) {
this.name = name;
this.tasty = tasty;
}
public boolean isFlag() {
return flag;
}
public void setFlag(boolean flag) {
this.flag = flag;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getTasty() {
return tasty;
}
public void setTasty(String tasty) {
this.tasty = tasty;
}
//廚師做菜
public synchronized void cook(int i){
if(flag){
if(i % 2 == 0){
this.setName("番茄炒蛋");
this.setTasty("鹹");
}else{
this.setName("糖醋排骨");
this.setTasty("酸甜");
}
System.out.println("廚師做菜:"+this.getName()+",味道:"+this.getTasty());
}
flag = false;
//喚醒其他執行緒
this.notifyAll();
try {
//廚師執行緒休眠
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
//服務員端菜
public synchronized void get(){
if(!flag){
System.out.println("服務員出菜:"+this.getName()+",味道:"+this.getTasty());
}
flag = true;
//喚醒其他執行緒
this.notifyAll();
try {
//服務員執行緒休眠
this.wait();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
七、執行緒池(瞭解)
概述 當程式中需要執行許多的內容很少的執行緒時,執行緒建立所花費的時間就會多於每次執行緒執行的所耗費的時間,就是導致程式的效率大大降低。使用執行緒池的方法就可以為程式節省下建立執行緒的時間。
執行緒池的種類
ExecutorService service = Executors.建立方法
void execute(Runnable command) //指揮執行緒池執行任務
1.快取執行緒池 任務執行緒傳入時自動分配執行緒,執行緒不夠時自動建立新執行緒
Executors.newCachedThreadPool() //建立快取執行緒池
2.定長執行緒池 指定執行緒池執行緒的個數,任務執行緒傳入時自動分配執行緒,執行緒不夠時剩餘任務執行緒排隊等待執行緒池中的執行緒執行完畢
Executors.newFixedThreadPool(int nThreads) //建立定長執行緒池,傳入執行緒池中執行緒的個數
3.單執行緒執行緒池 執行緒池中只有一個執行緒,任務執行緒傳入時自動分配執行緒,一個任務執行時剩餘任務執行緒排隊等待執行緒池中的執行緒執行完畢
Executors.newSingleThreadExecutor() //建立單執行緒執行緒池
4.週期定長執行緒池 指定執行緒池執行緒的個數,任務執行緒傳入時自動分配執行緒,可以設定任務執行緒第一次執行的延時時間和之後每次執行的間隔時間
Executors.newScheduledThreadPool(int corePoolSize)
//建立週期定長執行緒池,傳入執行緒池中執行緒的個數
service.schedule(Runnable command, long delay, TimeUnit unit)
//執行緒定時執行,傳入任務、時長和時長單位
service.scheduleAtFixedRate(Runnable command, long initialDelay, long period, TimeUnit unit)
//週期性定時執行,傳入任務,初次執行延時時長,週期間隔時長和時長單位
最後
歡迎關注公眾號:前程有光,領取一線大廠Java面試題總結+各知識點學習思維導+一份300頁pdf文件的Java核心知識點總結!