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