JavaSE | 多线程
?
一、實現線程的方式一:繼承java.lang.Thread類 1、步驟 (1)編寫線程類,繼承java.lang.Thread類 (2)重寫public void run(){} 在run()的方法體中,編寫的代碼就是該線程的線程體 (3)創建線程對象:new(4)啟動線程:調用start()二、實現多線程:實現java.lang.Runnable接口 1、步驟 (1)編寫線程類,實現java.lang.Runnable接口 (2)實現public void run(){} 把該線程需要完成的任務,寫在run()中 (3)創建線程對象 (4)啟動線程:start() 因為Runnable接口和Object中都沒有start方法,只有Thread類中有start 所以啟動線程必須有Thread的對象。 相當于用Thread對象做為當前線程對象的代理對象,幫我們啟動線程。共享變量--線程的交互
而進程之間的交互特別慢??1套系統即1個進程??rmi --->EJB --> spring
跳出run方法:return、throw
線程安全問題
并發和并行的概念
?
多線程并發,多個去搶,只有一個執行,單核只有并發
多個軟件進程并行,多核
?
public class TestThreadSafe {public static void main(String[] args) {final ShareData sd = new ShareData();Thread t1 = new Thread(new Runnable() {@Overridepublic void run() {sd.username = "alex";try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t1 = " + sd.username);}});Thread t2 = new Thread(new Runnable() {@Overridepublic void run() {sd.username = "kris";try {Thread.sleep(1000);} catch (InterruptedException e) {e.printStackTrace();}System.out.println("t2 = " + sd.username);}});t1.start();t2.start();System.out.println("main方法執行完畢。。。");} }class ShareData{public String username; } View Codet1、t2都要sleep 1s,則main線程最先執行; 共享內存(堆、方法區)
一個線程即一個棧空間,堆內存是共享的;?執行方法即壓棧--棧幀(方法的所有內容) ;方法執行完就會彈棧
?
線程安全問題:當多個線程使用同一份“資源”時,其中一個線程對“資源”的修改,影響了其他線程。多線程在并發執行時,對共享內存中的共享對象中的共享屬性進行修改所導致的數據沖突問題; 下次我們如何判斷我們的程序是否有線程安全問題?(1)多個線程; (2)有共享數據; (3)多條語句操作共享數據 多條語句的中間隨時可能失去CPU資源,被其他線程操作 如何避免? 加鎖; 用同步: 1、同步代碼塊 synchronized(鎖對象){ 需要加鎖的代碼 };鎖對象的選擇: (1)鎖對象可以是任意類型的對象; (2)保證多個線程要共用同一個鎖對象 ,這個鎖對象也稱為監視器對象 2、同步方法【修飾符】synchronized 返回值類型 方法名(【形參列表】)【拋出的異常列表】{ } 鎖對象沒的選: (1)靜態方法:當前類的Class對象; (2)非靜態方法:this(要謹慎) * 提醒: (1)鎖的代碼的范圍,太大或太小都不行; 一般考慮一次任務,并且保證所有操作共享數據的代碼都鎖進去了。 例如,這里的條件判斷hasTicket()和買票的buy()代碼 (2)一定要確保鎖對象是同一個 什么時候釋放鎖? (1)把同步代碼塊或同步方法的代碼執行完,自動釋放?
?繼承Thread父類
同步代碼塊
線程開始執行同步代碼塊之前,必須先獲得對同步監視器的鎖定,換句話說沒有獲得對同步監視器的鎖定,就不能進入同步代碼塊的執行,線程就會進入阻塞狀態,直到對方釋放了對同步監視器對象的鎖定。
任何時刻只能有一個線程可以獲得對同步監視器的鎖定,當同步代碼塊執行結束后,該線程自然會釋放對同步監視器對象的鎖定。
Java程序運行使用任何對象來作為同步監視器對象,只要保證共享資源的這幾個線程,鎖的是同一個同步監視器對象即可
選擇同步共享資源對象作為同步監視器對象
//方法一:同步代碼塊public void safe(){while(ts.hasTicket()){synchronized (ts) { //synchronized(同步監視器對象){ 需要加鎖的代碼 };if(ts.hasTicket()){String buy = ts.buy();System.out.println(Thread.currentThread().getName() +"賣了張票" + buy);}}}}同步方法
與同步代碼塊對應的,Java的多線程安全支持還提供了同步方法,同步方法就是使用synchronized關鍵字來修飾某個方法,則該方法稱為同步方法。對于同步方法而言,無須顯式指定同步監視器,靜態方法的同步監視器對象是當前類的Class對象,非靜態方法的同步監視器對象是調用當前方法的this對象。
//方法二:同步方法;鎖對象是當前類的Class對象Window.classpublic static synchronized void safe(){ if(ts.hasTicket()){String buy = ts.buy();System.out.println(Thread.currentThread().getName() + "賣了張票" + buy);}}?
?
?實現Runnable接口的方式
選擇this對象作為同步監視器對象
如果線程是繼承Thread類實現的,那么把同步監視器對象換成this,那么就沒有起到作用,仍然會發生線程安全問題。因為兩個線程的this對象是不同的。
但是如果線程是實現Runnable接口實現的,那么如果兩個線程共用同一個Runnable接口實現類對象作為target的話,就可以把同步監視器對象換成this。
//選擇this對象作為同步監視器對象public void safe(){while(true){synchronized (this) {if(ts.hasTicket()){String buy = ts.buy();System.out.println(Thread.currentThread().getName() + "賣了張票" + buy);}}}}同步方法
//方法二:同步方法;非靜態方法的同步監視器對象是調用當前方法的this對象。public synchronized void safe(){if(ts.hasTicket()){String buy = ts.buy();System.out.println(Thread.currentThread().getName() + "賣了張票" + buy);}}?兩種實現線程方式的區別:
?1、繼承Thread類
(1)啟動線程比較簡單
?(2)可能會遇到單繼承的限制
(3)選擇鎖對象,必須是靜態的,或當前類.class
?2、實現Runnable接口
(1)啟動線程需要借助一個Thread類的對象
(2)沒有單繼承的限制
?(3)選擇鎖對象,可以使用this,或者別的
?
?生產者與消費者問題
該問題描述了兩個(多個)共享固定大小緩沖區的線程——即所謂的“生產者”和“消費者” ——在實際運行時會發生的問題。生產者的主要作用是生成一定量的數據放到緩沖區中,然后重復此過程。 與此同時,消費者也在緩沖區消耗這些數據。 該問題的關鍵就是要保證生產者不會在緩沖區滿時加入數據,消費者也不會在緩沖區中空時消耗數據。問題: (1)數據的緩沖區是有限的-->需要協作 (2)數據是共享的-->線程安全問題
如何解決? (1)協作的問題:線程通信 wait()和notify()/notifyAll() wait()和notify()方法必須由“鎖”對象,或監視器對象來調用。 因為多個線程之間通信就可以依據這個“鎖”對象,他們直接溝通的橋梁。 IllegalMonitorStateException:非法的監視器對象 什么時候才有監視器對象?在同步代碼塊或同步方法中,選擇的鎖對象才是監視器對象 (2)線程安全問題:同步、加鎖 當有多個生產者與多個消費者時? (1)當wait()被喚醒后,要記得重新判斷條件,所以這里可以用while,或if..else(2)notify必須換成notifyAll() wait()和notify()/notifyAll()在哪里? 在java.lang.Object類中聲明的,因為調用它的對象不是線程對象,而是“鎖”對象, 因為“鎖”對象不知道是什么類型,是任意類型,所以只能聲明在Object類中。因為Object中的方法,才能保證所有對象都有。 例如: 快餐店,廚房的廚師與服務員就屬于生產者與消費者,他倆之間共同操作工作臺上的“菜”, 廚師屬于生產者(負責往工作臺上放“菜”),服務員屬于消費者(從工作臺上取走“菜”),多個生產者消費者,notify( )的是所有同一個鎖對象的,等待的線程都有可能被喚醒,因此喚醒的可能是另一個生產者;兩個生產者互相喚醒,就會一直生產了;解決方案是:1)生產者喚醒消費者,消費者喚醒生產者,但notify沒有這個功能;
2)每次被喚醒的線程,重新判斷條件,不要走下面的,再回去,用while循環;
?
?
?單例設計模式:(高頻面試題)
單例:唯一的對象 * 某個類在整個系統中只有唯一的對象,不會出現第二個對象。 如何實現單例? (1)構造器私有化:避免隨意new對象 (2)在類中創建這個唯一的對象,并且保存:供外界使用 兩種形式: 1、餓漢式 無論別人現在是否需要這個對象,我都創建,在初始化這個類時,就創建 2、懶漢式 只有在別人來拿這個對象時(不得不創建時),才會創建 * 單例唯一改變的就是創建對象的位置和獲取對象的方式而已,其他的沒有變。* 回憶:枚舉,當某個類型的對象是有限個?
?
餓漢式單例模式
class Hungry{ //餓漢模式// 在類中創建這個唯一的對象,并且保存:供外界使用public static final Hungry INSTANCE = new Hungry(); //靜態的在類初始化時初始化;//在初始化這個這個類時就創建這個對象,不管別人是否使用;private Hungry() { //構造器私有化,避免隨意私有化對象 }public void test(){System.out.println("餓漢式");}} class Hungry2{ //餓漢模式// 在類中創建這個唯一的對象,并且保存:供外界使用public static final Hungry2 INSTANCE = new Hungry2(); //在初始化這個這個類時就創建這個對象,不管別人是否使用;private Hungry2() { //構造器私有化,避免隨意私有化對象 }public void test(){System.out.println("餓漢式");}public static Hungry2 getInstance() {return INSTANCE;}}enum Hungry3{INSTANSE;public void test(){System.out.println("餓漢式模式");}}?
懶漢模式
public class TestSingle {static Lazy l1;static Lazy l2;public static void main(String[] args) { Thread t1 = new Thread(){public void run(){ //匿名內部類創建的線程對象;l1 = Lazy.getInstance();}};t1.start();//等它們執行完,再打印它 Thread t2 = new Thread(){public void run(){l2 = Lazy.getInstance();}};t2.start();//等它們執行完,再打印它try {t1.join();t2.join();} catch (InterruptedException e) {// TODO Auto-generated catch block e.printStackTrace();}System.out.println(l1); //打印hash碼看是否一樣; System.out.println(l2); //它們兩個結果不一樣,判斷出new了兩個對象 } }class Lazy{private static Lazy instance;public Lazy() {super();}public static Lazy getInstance(){ //不能寫成 return new Lazy();這樣子是調一次new一次;if(instance == null){ //有可能一個線程剛剛判斷為null,CPU被搶走了,另外一個線程也被判為null//synchronized (Lazy.class) {// if(Lazy.class == null){ //存在線程安全問題instance = new Lazy(); //保存到一個變量里邊; }//}//}return instance;}}加鎖--->>>
? InnerLazy.method(); // 調用外部類的時候就沒有創建內部類對象
InnerLazy instance = InnerLazy.Inner.instance;
//只有在別人來拿這個對象時(不得不創建時),才會創建內部類對象
?
?
wait方法和sleep方法的區別?
* (1)wait在Object類中,sleep在Thread類中聲明
* (2)wait通過“鎖,監視器”對象,sleep通過Thread類名調用
* (3)wait方法使得當前線程進入阻塞狀態后,會釋放鎖;
* sleep方法使得當前線程進入阻塞狀態后,仍然持有鎖;
yield()、sleep和wait的區別?
* (1)yield()不會阻塞線程,只是暫停線程一次,回到就緒狀態
* (2)sleep和wait會阻塞線程
?線程池
不要頻繁的去創建線程,而是給個固定的,用時拿去用,用完還回來,可以重復利用的效果;? 數據庫的連接池也是這樣;
public class TestThreadPool {public static void main(String[] args) {// 構建線程池ExecutorService executorService = Executors.newSingleThreadExecutor(); //創建單一線程,只有一個線程去執行ExecutorService executorService1 = Executors.newFixedThreadPool(5);//可以指定線程的個數ExecutorService executorService2 = Executors.newCachedThreadPool(); //循環10次,它有可能創建5/6/7/8/9/10個線程都有可能for (int i = 0; i <= 10; i++){executorService2.submit(new Runnable() {public void run() {System.out.println(Thread.currentThread().getName());}});}} }?
Executors.java return new ThreadPoolExecutor(nThreads, nThreads,0L, TimeUnit.MILLISECONDS,new LinkedBlockingQueue<Runnable>()); ThreadPoolExecutor等同于構建了線程池的構建對象;LinkedBlockingQueue阻塞式隊列; JDK1.6之后增加兩個新隊列BlockingQueue阻塞式隊列(從隊列中去取數據,取不到就一直阻塞直到取到為止;往里邊放如果放不進去就一直等待直到放進去)和Deque雙端隊列(兩頭都可以去取)---Kafka底層中用到這個?
轉載于:https://www.cnblogs.com/shengyang17/p/10101598.html
總結
以上是生活随笔為你收集整理的JavaSE | 多线程的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Asp.net中页面传值几种方式
- 下一篇: Java运行时内存