多個線程操作同一個資源
并發:
同一個對象被多個線程同時操作
上萬人同時搶100張票
兩個銀行同時取錢:銀行卡隻有1000元,你和妻子一起取錢,然後你妻子可能取到1000元,此時銀行卡裡已經沒有錢了,那麼如果不同步的話,你可能就取到-1000元,這可能嗎?
處理多線程問題時,多個線程通路同一個對象,并且某些線程還想要修改這個對象,這時候我們就需要線程同步,線程同步其實就是一種等待機制,多個需要同時通路此對象的線程進入這個對象的等待池形成隊列,等待前面的線程使用完畢,下一個線程再使用
由于同一程序的多個線程共享同一塊存儲空間,在帶來友善的同時,也帶來了通路沖突問題,為了保證資料在方法中被通路時的正确性,在通路時加入 鎖機制 synchronized,當一個線程獲得對象的排它鎖,獨占資源,其他線程必須等待,使用後釋放鎖即可,存在以下問題:
1)一個線程持有鎖會導緻其他所有需要此鎖的線程挂起
2)在多線程競争下,加鎖,釋放鎖會導緻比較多的上下文切換和排程延時,引起性能問題
3)如果一個優先級高的線程等待一個優先級低的線程釋放鎖,會導緻優先級倒置,引起性能問題
不安全的買票:為什麼會出現賣出第-1張票的情況呢?
//不安全的 買票
public class Test {
public static void main(String[] args) throws InterruptedException {
Buyticket buyticket=new Buyticket();
new Thread(buyticket,"苦逼的我").start();
new Thread(buyticket,"牛逼的你們").start();
new Thread(buyticket,"可惡的黃牛黨").start();
}
}
class Buyticket implements Runnable{
private boolean flag=true;
private int tickets=10;
//買票
public void run() {
while(flag) {
try {
isticket();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
}
private void isticket() throws InterruptedException {
//判斷是否有票
if(tickets<=0) {
flag=false;
return;
}
Thread.sleep(100);
System.out.println(Thread.currentThread().getName()+tickets--);
}
}
//輸出結果
可惡的黃牛黨10
牛逼的你們9
苦逼的我8
可惡的黃牛黨7
牛逼的你們6
苦逼的我5
可惡的黃牛黨4
牛逼的你們3
苦逼的我2
可惡的黃牛黨1
牛逼的你們0-----------------------第0張票
苦逼的我-1 ------------------看到這個結果我們思考,為什麼賣出了第-1張票
分析問題:
大家一進來都看到10張票,然後開始購買,當賣到最後一張票時,第一個人看到還有一張票,然後他就打算買,但是它剛買到這張票,程式還沒來得及執行到tickets--這一步,然後CPU又被另一個人搶占了,他沒發現票沒了,他看到的是票還剩一張,然後他又買票,當他買了之後,可能程式被上一個人執行,繼續在tickets--這一步,然後他将tickets 減減為0,此時另一個人也執行到tickets--,将tickets的0又減1變為-1
是以當線程越多的時候,不僅會出現0和-1可能也出現-2,-3,乃至-4
同步方法:
由于我們可以通過private關鍵字來保證資料對象隻能被方法通路,是以我們隻需要針對方法提出一套機制,這套機制就是synchronized關鍵字,它包括兩種用法:
synchronized方法和synchronized塊
同步方法:public synchronized void method(int a){}
synchronized方法控制對“對象”的通路,每個對象對應一把鎖,每個synchronized方法都必須或得調用該方法的對象的鎖才能執行,否則會線程阻塞,方法一旦執行,就獨占該鎖,直到該方法傳回才釋放鎖,後面被阻塞的線程才能獲得這個鎖,繼續執行
缺陷:若将一個大的方法申明為synchronized将會影響效率
同步方法的弊端:
死鎖:
死鎖的案例:
//多個線程互相抱着對方需要的資源,然後形成僵持
public class StaticProxy {
public static void main(String[] args) throws InterruptedException {
Makeup makeup1=new Makeup(0,"灰姑娘");
Makeup makeup2=new Makeup(1,"白雪公主");
makeup1.start();
makeup2.start();
}
}
class Makeup extends Thread{
//需要的資源隻有一份,用static保證隻有一份
static Lipstick lipstick=new Lipstick();
static Mirrors mirrors=new Mirrors();
int choice;//選擇
String girlName;//使用化妝品的人
Makeup(int choice,String girlName) {
this.girlName=girlName;
this.choice=choice;
}
public void run() {
makeup();
}
public void makeup(){
//這個地方灰姑娘拿着口紅想要鏡子的鎖,白雪公主拿着鏡子的鎖想要口紅,雙方都不釋放鎖,形成了一個僵持的局面
if(choice==0) {
synchronized(lipstick) {
System.out.println(this.girlName+"獲得口紅的鎖");
synchronized(mirrors) {
System.out.println(this.girlName+"獲得鏡子的鎖");
}
}
}else {
synchronized(mirrors) {
System.out.println(this.girlName+"獲得鏡子的鎖");
synchronized(lipstick) {
System.out.println(this.girlName+"獲得口紅的鎖");
}
}
}
}
}
class Lipstick{
}
class Mirrors{
}
解決方案:
//多個線程互相抱着對方需要的資源,然後形成僵持
public class StaticProxy {
public static void main(String[] args) throws InterruptedException {
Makeup makeup1=new Makeup(0,"灰姑娘");
Makeup makeup2=new Makeup(1,"白雪公主");
makeup1.start();
makeup2.start();
}
}
class Makeup extends Thread{
//需要的資源隻有一份,用static保證隻有一份
static Lipstick lipstick=new Lipstick();
static Mirrors mirrors=new Mirrors();
int choice;//選擇
String girlName;//使用化妝品的人
Makeup(int choice,String girlName) {
this.girlName=girlName;
this.choice=choice;
}
public void run() {
makeup();
}
public void makeup(){
if(choice==0) {
synchronized(lipstick) {
System.out.println(this.girlName+"獲得口紅的鎖");
}
synchronized(mirrors) {
System.out.println(this.girlName+"獲得鏡子的鎖");
}
}else {
synchronized(mirrors) {
System.out.println(this.girlName+"獲得鏡子的鎖");
}
synchronized(lipstick) {
System.out.println(this.girlName+"獲得口紅的鎖");
}
}
}
}
class Lipstick{
}
class Mirrors{
}
lock鎖
可重入鎖代碼示例
public class StaticProxy {
public static void main(String[] args) throws InterruptedException {
Buytickets buytickets=new Buytickets();
new Thread(buytickets).start();
new Thread(buytickets).start();
new Thread(buytickets).start();
}
}
class Buytickets implements Runnable{
private static int tickets=10;
private static boolean flag=true;
ReentrantLock reentrantLock=new ReentrantLock();
public void run() {
while(flag) {
try {
reentrantLock.lock();
if(tickets>0) {
try {
Thread.sleep(1000);
System.out.println(Thread.currentThread().getName()+"正在買第"+tickets--);
} catch (InterruptedException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}else {
flag=false;
break;
}
}finally {
reentrantLock.unlock();
}
}
}
}
class A{
private final ReentrantLock lock=new ReenTrantLock();
public void m(){
lock.lock();
try{
//保證線程安全的代碼;
}finally{
lock.unlock();
//如果同步代碼有異常,要将unlock()寫入finally語句塊
}
}
}
synchronized和Lock的對比
線程協作
sleep抱着鎖睡覺,wait放開鎖睡覺
生産者消費者問題:
線程池: