java多線程創(chuàng)建及線程安全詳解
什么是線程
- 線程被稱為輕量級進程,是程序執(zhí)行的最小單位,它是指在程序執(zhí)行過程中,能夠執(zhí)行代碼的一個執(zhí)行單位。每個程序程序都至少有一個線程,也即是程序本身。
線程的狀態(tài)
- 新建(New):創(chuàng)建后尚未啟動的線程處于這種狀態(tài)
- 運行(Runable):Runable包括了操作系統線程狀態(tài)的Running和Ready,也就是處于此狀態(tài)的線程有可能正在執(zhí)行,也有可能正在等待著CPU為它分配執(zhí)行時間。
- 等待(Wating):處于這種狀態(tài)的線程不會被分配CPU執(zhí)行時間。等待狀態(tài)又分為無限期等待和有限期等待,處于無限期等待的線程需要被其他線程顯示地喚醒,沒有設置Timeout參數的Object.wait()、沒有設置Timeout參數的Thread.join()方法都會使線程進入無限期等待狀態(tài);有限期等待狀態(tài)無須等待被其他線程顯示地喚醒,在一定時間之后它們會由系統自動喚醒,Thread.sleep()、設置了Timeout參數的Object.wait()、設置了Timeout參數的Thread.join()方法都會使線程進入有限期等待狀態(tài)。
- 阻塞(Blocked):線程被阻塞了,“阻塞狀態(tài)”與”等待狀態(tài)“的區(qū)別是:”阻塞狀態(tài)“在等待著獲取到一個排他鎖,這個時間將在另外一個線程放棄這個鎖的時候發(fā)生;而”等待狀態(tài)“則是在等待一段時間或者喚醒動作的發(fā)生。在程序等待進入同步區(qū)域的時候,線程將進入這種狀態(tài)。
- 結束(Terminated):已終止線程的線程狀態(tài),線程已經結束執(zhí)行。
多線程創(chuàng)建方法
繼承Thread
/** * @Author GocChin * @Date 2021/5/11 11:56 * @Blog: itdfq.com * @QQ: 909256107 * @Descript: */ class MyThread extends Thread{ @Override public void run() { System.out.println(currentThread().getName()+"運行了"); } } class Test{ public static void main(String[] args) { MyThread myThread = new MyThread(); System.out.println(Thread.currentThread().getName()+":運行了"); myThread.start(); } }
實現Runable接口創(chuàng)建多線程
/** * @Author GocChin * @Date 2021/5/11 12:37 * @Blog: itdfq.com * @QQ: 909256107 * @Descript: 實現Runable接口的方式創(chuàng)建多線程 * 1.創(chuàng)建一個實現了Runable接口的類 * 2.實現類去實現Runable中的抽象方法,run(); * 3.創(chuàng)建實現類的對象 * 4.將此對象作為參數傳遞到Thread類的構造器中,創(chuàng)建Thread類的對象 * 5.通過Thread類的對象調用start() */ class MThread implements Runnable{ @Override public void run() { for (int i = 0; i<100;i++){ if (i%2!=0){ System.out.println(i); } } } } public class ThreadTest1 { public static void main(String[] args) { //3.創(chuàng)建實現類的對象 MThread mThread = new MThread(); //4.將此對象作為參數傳遞到Thread類的構造器中,創(chuàng)建Thread類的對象 Thread thread = new Thread(mThread); thread.start(); } }
Thread和Runable創(chuàng)建多線程對比
開發(fā)中:優(yōu)先使用Runable
1.實現的方式沒有類的單繼承的局限性。
2.實現的方式跟適合處理多個線程有共享數據的情況。
聯系:Thread類中也實現了Runable,兩種方式都需要重寫run()。
實現Callable接口
import java.util.concurrent.Callable; import java.util.concurrent.ExecutionException; import java.util.concurrent.FutureTask; /** * @Author GocChin * @Date 2021/5/11 13:03 * @Blog: itdfq.com * @QQ: 909256107 * @Descript: */ class MCallable implements Callable<Integer> { @Override public Integer call() throws Exception { int sum=0; for(int i=0;i<100;i++){ sum+=i; } return sum; } } public class CallableTest { public static void main(String[] args) { //執(zhí)行Callable 方式,需要FutureTask 實現實現,用于接收運算結果 FutureTask<Integer> integerFutureTask = new FutureTask<Integer>(new MCallable()); new Thread(integerFutureTask).start(); //接受線程運算后的結果 Integer integer = null; try { integer = integerFutureTask.get(); System.out.println(integer); } catch (InterruptedException e) { e.printStackTrace(); } catch (ExecutionException e) { e.printStackTrace(); } } }
與Runable相比,Callable功能更強大
相比run()方法可以有返回值
方法可以拋出異常
支持泛型的返回值
需要借助FutureTask類,比如獲取返回結果
使用線程池進行創(chuàng)建
線程池創(chuàng)建的好處
提高響應速度(減少了創(chuàng)建新線程的時間)
降低資源消耗(重復利用線程池中線程,不需要每次都創(chuàng)建)
便于線程管理:
- corePoolSize:核心線程池的大小
- maximumPoolSize:最大線程數
- keepAliveTime:線程沒有任務時最多保持多長時間后悔中止
import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; /** * @Author GocChin * @Date 2021/5/11 13:10 * @Blog: itdfq.com * @QQ: 909256107 * @Descript: */ class Thread1 implements Runnable{ @Override public void run() { for (int i=1;i<30;i++){ System.out.println(Thread.currentThread().getName() + ":" + i); } } } public class ThreadPool { public static void main(String[] args) { //創(chuàng)建線程池 ExecutorService executorService= Executors.newFixedThreadPool(10); Thread1 threadPool = new Thread1(); for (int i=0;i<5;i++){ //為線程池分配任務 executorService.submit(threadPool); } //關閉線程池 executorService.shutdown(); } }
Thread中的常用方法
- start():啟動當前線程;調用當前線程的run();
- run():通常需要重寫Thread類中的此方法,將創(chuàng)建的線程要執(zhí)行的操作聲明在此方法中。
- currentThread():靜態(tài)方法,返回當前代碼的線程。
- getName():獲取當前線程的名字。
- setName():設置當前線程的名字。
- yield():釋放當前cpu的執(zhí)行權,切換線程執(zhí)行。
- join():在線程a中調用線程b的join(),此時線程a會進入阻塞狀態(tài),知道線程b完全執(zhí)行完畢,線程a 才結束阻塞狀態(tài)。
- stop():強制線程生命期結束。(過時了,不建議使用)
- isAlive():判斷線程是否還活著。
- sleep(long millitime):讓當前線程睡眠指定的事milltime毫秒。在指定的millitime毫秒時間內,當前線程是阻塞狀態(tài)。
線程的優(yōu)先級
線程的優(yōu)先級等級
- MAX_PRIORITY:10
- MIN_PRIORITY:1
- NORM_PRIORITY:5
涉及的方法
- getPriority():返回線程的優(yōu)先值
- setPriority(int newPriority):改變線程的優(yōu)先級
說明
- 線程創(chuàng)建時繼承父線程的優(yōu)先級
- 低優(yōu)先級知識獲得調度的概率低,并非一定是在高優(yōu)先級線程之后才被調用
線程的同步
多線程賣票
基于實現Runable的方式實現多線程買票
package demo2; /** * @Author GocChin * @Date 2021/5/11 13:37 * @Blog: itdfq.com * @QQ: 909256107 * @Descript: 創(chuàng)建三個窗口買票,總票數為100張,使用Runable接口的方式 * 存在線程安全問題,待解決 */ class Thread2 implements Runnable{ private int ticket=100; @Override public void run() { while (true){ if (ticket>0) { System.out.println(Thread.currentThread().getName() + ":買票,票號為:" + ticket); ticket--; }else { break; } } } } public class Test1 { public static void main(String[] args) { Thread2 thread2 = new Thread2(); Thread t1 = new Thread(thread2); Thread t2 = new Thread(thread2); Thread t3 = new Thread(thread2); t1.setName("窗口一"); t2.setName("窗口二"); t3.setName("窗口三"); t1.start(); t2.start(); t3.start(); } }
實現結果,存在重復的票
如果在買票方法中加入sleep函數
public void run() { while (true){ if (ticket>0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":買票,票號為:" + ticket); ticket--; }else { break; } } }
則運行結果可能會出現-1,表示也是不正常的
理想情況
極端情況
在java中,我們通過同步機制,來解決線程的安全問題。
同步代碼塊
synchronized(同步監(jiān)視器){ //需要被同步的代碼 }
說明
- 操作共享數據的代碼就是需要被同步的代碼。
- 共享數據:多個線程共同操作的變量,比如本題中的ticket就是共享數據。
- 同步監(jiān)視器:俗稱:鎖。任何一個類的對象都可以充當鎖。要求:多個線程必須要共用統一把鎖。
- 同步的方式,解決了線程的安全問題—好處。但是操作同步代碼時,只能有一個線程參與,其他線程等待。相當于是一個單線程的過程,效率低。-----局限性
- 使用Runable接口創(chuàng)建多線程的方式中,可以使用this關鍵字;在繼承Thread類中創(chuàng)建多線程中,慎用this充當同步監(jiān)視器,可以考慮使用當前類充當同步監(jiān)視器。Class clazz = Windows.class 因此 類也是一個對象
- 包裹操作共享數據的代碼 不能多也不能少
修改之后的代碼:
package demo2; /** * @Author GocChin * @Date 2021/5/11 13:37 * @Blog: itdfq.com * @QQ: 909256107 * @Descript: 創(chuàng)建三個窗口買票,總票數為100張,使用Runable接口的方式 * 存在線程安全問題,待解決 */ class Thread2 implements Runnable{ private int ticket=100; Object object = new Object(); @Override public void run() { while (true){ synchronized(object) { //括號中的內容可以直接使用當前對象this去充當 if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":買票,票號為:" + ticket); ticket--; } else { break; } } } } } public class Test1 { public static void main(String[] args) { Thread2 thread2 = new Thread2(); Thread t1 = new Thread(thread2); Thread t2 = new Thread(thread2); Thread t3 = new Thread(thread2); t1.setName("窗口一"); t2.setName("窗口二"); t3.setName("窗口三"); t1.start(); t2.start(); t3.start(); } }
結果
繼承Thread的方式,去使用同步代碼塊,需要將聲明的鎖對象設為statci,否則創(chuàng)建的對象的同步監(jiān)視器不唯一,就無法實現。
package demo2; /** * @Author GocChin * @Date 2021/5/11 14:45 * @Blog: itdfq.com * @QQ: 909256107 * @Descript: */ class WindowsTest2 extends Thread{ private static int ticket=100; private static Object obj = new Object(); @Override public void run() { while (true){ synchronized (obj){ //這里不能使用this去充當,可以直接寫一個Test.class 類也是對象 if (ticket>0){ try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(getName()+":買票,票號為:"+ticket); ticket--; }else { break; } } } } } public class Test2{ public static void main(String[] args) { WindowsTest2 w1 = new WindowsTest2(); WindowsTest2 w2 = new WindowsTest2(); WindowsTest2 w3 = new WindowsTest2(); w1.setName("窗口一"); w2.setName("窗口二"); w3.setName("窗口三"); w1.start(); w2.start(); w3.start(); } }
同步方法
如果操作共享數據的代碼完整的聲明在一個方法中,可以將此方法聲明為同步的。
通過實現Runable的方式實現同步方法。
package demo2; /** * @Author GocChin * @Date 2021/5/11 13:37 * @Blog: itdfq.com * @QQ: 909256107 * @Descript: 創(chuàng)建三個窗口買票,總票數為100張,使用Runable接口的方式 * 存在線程安全問題,待解決 */ class Thread3 implements Runnable { private int ticket = 100; @Override public void run() { while (true) { show(); } } private synchronized void show(){ if (ticket > 0) { try { Thread.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":買票,票號為:" + ticket); ticket--; } } } public class Test3 { public static void main(String[] args) { Thread3 thread3 = new Thread3(); Thread t1 = new Thread(thread3); Thread t2 = new Thread(thread3); Thread t3 = new Thread(thread3); t1.setName("窗口一"); t2.setName("窗口二"); t3.setName("窗口三"); t1.start(); t2.start(); t3.start(); } }
通過實現繼承Thread的方式實現同步方法。使用的同步監(jiān)視器是this,則不唯一,就會報錯。所以將該方法定義為static。當前的同步換時期就變成Test4.class了
package demo2; /** * @Author GocChin * @Date 2021/5/11 14:45 * @Blog: itdfq.com * @QQ: 909256107 * @Descript: */ class WindowsTest4 extends Thread{ private static int ticket=100; private static Object obj = new Object(); @Override public void run() { while (true){ show(); } } public static synchronized void show(){//同步監(jiān)視器不是this了,而是當前的類 // public synchronized void show(){//同步監(jiān)視器是this ,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 Test4{ public static void main(String[] args) { WindowsTest4 w1 = new WindowsTest4(); WindowsTest4 w2 = new WindowsTest4(); WindowsTest4 w3 = new WindowsTest4(); w1.setName("窗口一"); w2.setName("窗口二"); w3.setName("窗口三"); w1.start(); w2.start(); w3.start(); } }
總結
- 同步方法仍然設計到同步監(jiān)視器,只是不需要我們去顯示的聲明。
- 非靜態(tài)的同步方法,同步監(jiān)視器是:this靜態(tài)的同步方法中,同步監(jiān)視器是類本身。
Lock鎖解決線程安全問題
synchronize與lock的異同
相同
- 都可以解決線程安全問題
不同
- synchronize機制在執(zhí)行相應的同步代碼以后,自動的釋放同步監(jiān)視器;Lock需要手動的啟動同步lock(),同時結束同步也需要手動的實現unlock()。
建議優(yōu)先使用順序
Lock------>同步代碼塊(已經進入了方法體,分配了相應資源)---->同步方法(在方法體之外)
package demo2; import java.util.concurrent.locks.ReentrantLock; /** * @Author GocChin * @Date 2021/5/11 15:58 * @Blog: itdfq.com * @QQ: 909256107 * @Descript: */ class Lock1 implements Runnable{ private int ticket=50; //1.實例化 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.調用解鎖方法 lock.unlock(); } } } } public class LockTest1 { public static void main(String[] args) { Lock1 lock1 = new Lock1(); Thread t1 = new Thread(lock1); Thread t2 = new Thread(lock1); Thread t3 = new Thread(lock1); t1.setName("窗口一"); t2.setName("窗口二"); t3.setName("窗口三"); t1.start(); t2.start(); t3.start(); } }
到此這篇關于java多線程創(chuàng)建及線程安全的文章就介紹到這了,更多相關java多線程創(chuàng)建內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!
相關文章
簡單了解spring bean作用域屬性singleton和prototype的區(qū)別
這篇文章主要介紹了簡單了解spring bean作用域屬性singleton和prototype的區(qū)別,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下2019-12-12