Java是一種可以撰寫(xiě)跨平臺(tái)應(yīng)用軟件的面向?qū)ο蟮某绦蛟O(shè)計(jì)語(yǔ)言。Java 技術(shù)具有卓越的通用性、高效性、平臺(tái)移植性和安全性,廣泛應(yīng)用于PC、數(shù)據(jù)中心、游戲控制臺(tái)、科學(xué)超級(jí)計(jì)算機(jī)、移動(dòng)電話和互聯(lián)網(wǎng),同時(shí)擁有全球最大的開(kāi)發(fā)者專(zhuān)業(yè)社群。
給你學(xué)習(xí)路線:html-css-js-jq-javase-數(shù)據(jù)庫(kù)-jsp-servlet-Struts2-hibernate-mybatis-spring4-springmvc-ssh-ssm

一. 線程安全
對(duì)于線程安全問(wèn)題,首先舉個(gè)例子:
1234567891011121314151617181920212223242526272829303132//窗口售票問(wèn)題class MyThreadDemo implements Runnable { int ticket = 100; @Override public void run() { while (true){ if (ticket>0) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"售票,票號(hào)為:"+ticket--); }else{ break; } } }}public class ThreadTest { public static void main(String[] args){ MyThreadDemo m = new MyThreadDemo(); Thread t1 = new Thread(m); Thread t2 = new Thread(m); Thread t3 = new Thread(m); t1.start(); t2.start(); t3.start(); }}
問(wèn)題描述:
上面的多線程售票出現(xiàn)了一下錯(cuò)誤現(xiàn)象
重票:
錯(cuò)票:
小編推薦一個(gè)學(xué)Java的學(xué)習(xí)裙【 六五零,五五四,六零七 】,無(wú)論你是大牛還是小白,是想轉(zhuǎn)行還是想入行都可以來(lái)了解一起進(jìn)步一起學(xué)習(xí)!裙內(nèi)有開(kāi)發(fā)工具,很多干貨和技術(shù)資料分享!
首先,我們分析一下為什么出現(xiàn)這種問(wèn)題呢?
我們所期望的理想狀態(tài)為:
但是,會(huì)出現(xiàn)一個(gè)極端狀態(tài):
這是由于一個(gè)線程在操作共享數(shù)據(jù)過(guò)程中,未執(zhí)行完畢的情況下,例外的線程參與進(jìn)來(lái),導(dǎo)致了共享數(shù)據(jù)存在了安全問(wèn)題,本實(shí)例的共享數(shù)據(jù)為ticket。
那么,如何處理程序的線程安全問(wèn)題呢?
必須讓一個(gè)線程操作共享數(shù)據(jù)完畢以后,其它線程才有機(jī)會(huì)參與共享數(shù)據(jù)的操作。
原理我們也理解了,那java如何去實(shí)現(xiàn)線程的安全呢?
使用線程的同步機(jī)制
實(shí)現(xiàn)方式有兩種:
同步代碼塊
synchronized(同步監(jiān)視器){//需要被同步的代碼塊(即操作共享數(shù)據(jù)的代碼)}
共享數(shù)據(jù):多個(gè)線程共同操作的同一個(gè)數(shù)據(jù)(變量)
同步監(jiān)視器:由任何一個(gè)類(lèi)的對(duì)象來(lái)充當(dāng),哪個(gè)線程獲取此監(jiān)視器,誰(shuí)就執(zhí)行大括號(hào)里被同步的代碼。俗稱(chēng):鎖。
代碼實(shí)現(xiàn):
1234567891011121314151617181920212223class MyThreadDemo implements Runnable { int ticket = 100; Object object = new Object(); @Override public void run() { //Object object = new Object();//不行,由成員變量變?yōu)榫植孔兞浚縿?chuàng)建一個(gè)線程,都會(huì)創(chuàng)建一個(gè)對(duì)象 while (true){ synchronized (object){//同步監(jiān)視器(鎖)可以由任何一個(gè)類(lèi)的對(duì)象來(lái)充當(dāng),也可以使用this,表示當(dāng)前對(duì)象,只new了一次。但是在繼承的方式中,一定要注意使用this,可能創(chuàng)建了多個(gè)線程對(duì)象,要求是多個(gè)線程使用同一個(gè)鎖,即使用同一個(gè)對(duì)象。 if (ticket>0) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName()+"售票,票號(hào)為:"+ticket--); }else{ break; } } } }}
同步方法
同步的方法實(shí)現(xiàn)在java提升2相關(guān)章節(jié)/)已經(jīng)給出,可以前去查看。
它是將操作共享數(shù)據(jù)的方法聲明為
synchronized
,即此方法為同步方法,能夠保證當(dāng)其中一個(gè)線程執(zhí)行此方法時(shí),其它線程在外等待直至此線程執(zhí)行完此方法。
那么同步方法有沒(méi)有鎖呢,答案是有的,鎖為this。但是同樣要注意,在使用繼承創(chuàng)建的線程的方式中,同樣要慎用同步方法的方式,因?yàn)樗逆i為this。必須保證多個(gè)線程共用一把鎖。
使用鎖的方式(了解)

小編推薦一個(gè)學(xué)Java的學(xué)習(xí)裙【 六五零,五五四,六零七 】,無(wú)論你是大牛還是小白,是想轉(zhuǎn)行還是想入行都可以來(lái)了解一起進(jìn)步一起學(xué)習(xí)!裙內(nèi)有開(kāi)發(fā)工具,很多干貨和技術(shù)資料分享!
二. 互斥鎖之單例模式之懶漢式的線程安全問(wèn)題
互斥鎖
指的是一次最多只能有一個(gè)線程持有的鎖. 在jdk1.5之前, 我們通常使用synchronized機(jī)制控制多個(gè)線程對(duì)共享資源的訪問(wèn). 關(guān)鍵字
synchronized
來(lái)與對(duì)象的互斥鎖聯(lián)系。當(dāng)某個(gè)對(duì)象用
synchronized
修飾時(shí),表明該對(duì)象在任一時(shí)刻只能由一個(gè)線程訪問(wèn)。
同步的局限性:導(dǎo)致程序的執(zhí)行效率要降低
同步方法(非靜態(tài)的)的鎖為this。
同步方法(靜態(tài)的)的鎖為當(dāng)前類(lèi)本身。
單例模式懶漢式實(shí)現(xiàn):
12345678910111213141516171819202122232425class Singleton { private Singleton() { } private static Singleton instance = null; public static Singleton getInstance() { if (instance == null) { instance = new Singleton(); } return instance; }}public class TestSingleton { public static void main(String[] args) { Singleton s1 = Singleton.getInstance(); Singleton s2 = Singleton.getInstance(); System.out.println(s1 == s2); }}output:true
會(huì)出現(xiàn)線程安全問(wèn)題,多個(gè)線程同時(shí)執(zhí)行時(shí),第一個(gè)線程調(diào)用
getInstance()
進(jìn)入if語(yǔ)句內(nèi),還未實(shí)例化,出現(xiàn)線程掛起,后面的線程也會(huì)進(jìn)入會(huì)再創(chuàng)建一個(gè)對(duì)象,最終導(dǎo)致可能返回了不同Singleton對(duì)象。
解決線程安全問(wèn)題實(shí)現(xiàn):
1234567891011public static Singleton getInstance() {//如果第一個(gè)線程獲取了單例的實(shí)例對(duì)象,后面的線程再獲取實(shí)例的時(shí)候就不需要進(jìn)入同步代碼塊了 if (instance == null) {//多線程執(zhí)行時(shí),會(huì)先判斷對(duì)象是否為null,如果為null,直接返回,無(wú)需等待進(jìn)去同步代碼塊線程執(zhí)行完畢,然后再去執(zhí)行,提高了效率 synchronized (Singleton.class) {//對(duì)于靜態(tài)方法,使用但錢(qián)勒本身充當(dāng)鎖 if (instance == null) { instance = new Singleton(); } } } return instance;}
綜上,用這種方式解決了懶漢式的線程安全問(wèn)題,也提高了效率,但是在實(shí)際開(kāi)發(fā)中還是用餓漢式的比較多,因?yàn)檫@種方式,相對(duì)復(fù)雜,不適合應(yīng)用。
三. 線程的死鎖問(wèn)題
死鎖
,是指不同的線程分別占用對(duì)方需要的同步資源不放棄,都再等待對(duì)方放棄自己需要的同步資源,就形成了線程的死鎖。
死鎖的實(shí)例:
123456789101112131415161718192021222324252627282930313233343536373839public class TestDeadLock { static StringBuffer sb1 = new StringBuffer(); static StringBuffer sb2 = new StringBuffer(); public static void main(String[] args){ new Thread(){ public void run(){ synchronized (sb1){ try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } sb1.append("A"); synchronized (sb2){ sb2.append("B"); System.out.println(sb1); System.out.println(sb2); } } } }.start(); new Thread(){ public void run(){ synchronized (sb2){ sb1.append("C"); synchronized (sb1){ sb2.append("D"); System.out.println(sb1); System.out.println(sb2); } } } }.start(); }}
四. 線程通信
三個(gè)方法
wait(): 令當(dāng)前線程掛起并放棄CPU,同步資源,使別的線程可訪問(wèn)并修改共享資源,而當(dāng)前線程排隊(duì)等候再次對(duì)資源的訪問(wèn)
notify(): 喚醒正在排隊(duì)等待同步資源的線程中優(yōu)先級(jí)最高者結(jié)束等待
notifyAll(): 喚醒正在排隊(duì)等待資源的所有線程結(jié)束等待。
注:Java.lang.Object提供的這三個(gè)方法只有再synchronized方法或synchronized代碼塊中才能使用,否則會(huì)報(bào)java.lang.lllegalMonitorStateException異常。

小編推薦一個(gè)學(xué)Java的學(xué)習(xí)裙【 六五零,五五四,六零七 】,無(wú)論你是大牛還是小白,是想轉(zhuǎn)行還是想入行都可以來(lái)了解一起進(jìn)步一起學(xué)習(xí)!裙內(nèi)有開(kāi)發(fā)工具,很多干貨和技術(shù)資料分享!
實(shí)例:
12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061/*** 線程通信* 使用兩個(gè)線程打印1-100,線程1,線程2,交替打印*/class PrintNum implements Runnable { int num = 1; @Override public void run() { while (true) { synchronized (this){ notify(); if (num <= 100) { try { Thread.currentThread().sleep(10); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + ":" + num); num++; } else { break; } try { wait(); } catch (InterruptedException e) { e.printStackTrace(); } } } }}public class TestCommunication { public static void main(String[] args) { PrintNum p = new PrintNum(); Thread t1 = new Thread(p); Thread t2 = new Thread(p); t1.setName("甲"); t2.setName("乙"); t1.start(); t2.start(); }}output:甲:1乙:2甲:3乙:4甲:5乙:6...甲:97乙:98甲:99乙:100
關(guān)于多線程的相關(guān)知識(shí),暫時(shí)先到這,后續(xù)有學(xué)習(xí)的內(nèi)容,會(huì)持續(xù)更新,喜歡的,關(guān)注白呲牙。


