利用synchronized實(shí)現(xiàn)線程同步的案例講解
一、前期基礎(chǔ)知識儲備
(1)線程同步的定義:多線程之間的同步。
(2)多線程同步原因:一個多線程的程序如果是通過Runnable接口實(shí)現(xiàn)的,則意味著類中的屬性將被多個線程共享,由此引出資源的同步問題,即當(dāng)多個線程要操作同一資源時,有可能出現(xiàn)錯誤。
(3)實(shí)現(xiàn)多線程同步的方式——引入同步機(jī)制:在線程使用一個資源時為其加鎖,這樣其他的線程便不能訪問那個資源了,直到解鎖后才可以訪問?!@樣做的結(jié)果,所有線程間會有資源競爭,但是所有競爭的資源是同步的,刷新的,動態(tài)的,不會因?yàn)榫€程間的競爭,導(dǎo)致資源“過度消耗”或者“虛擬消耗”。
上代碼,具體展示“過度消耗/虛擬消耗”問題:
public class TestTicketRunnable{ public static void main(String[] a){ TicketThread tThread = new TicketThread(); new Thread(tThread).start(); new Thread(tThread).start(); new Thread(tThread).start(); } }; class TicketThread implements Runnable { private int ticket = 5; public void run(){ for (int i = 0; i < 5; i++){ if (ticket > 0){ try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "賣票:ticket = " + ticket--); } } } };
運(yùn)行結(jié)果:
Thread-0賣票:ticket = 5 Thread-2賣票:ticket = 5 //虛擬消耗 Thread-1賣票:ticket = 4 Thread-1賣票:ticket = 2 Thread-2賣票:ticket = 3 Thread-0賣票:ticket = 3 //虛擬消耗 Thread-0賣票:ticket = -1 //過度消耗 Thread-1賣票:ticket = 1 Thread-2賣票:ticket = 0
如上所見,票一共5張,三個線程調(diào)用買票,線程1網(wǎng)上賣了售票第1張,緊接著線程2線下也賣了“第一張”出現(xiàn)了“虛擬消耗”的問題;線程3黃牛黨賣了最后1張票,線程1網(wǎng)上又賣了最后1張,出現(xiàn)了“過度消耗”的問題,這兩種問題都是實(shí)際生活中不可能發(fā)生的,但是在這個3個線程執(zhí)行中卻出現(xiàn)了,那一定是有問題的,問題的根源就在于,三個渠道的“售票員”不在一個頻道上辦事,或者說沒有相互之間同步所共享的資源,導(dǎo)致這一問題的根本原因,就是相互之間實(shí)現(xiàn)方式不同步。
二、使用synchronized實(shí)現(xiàn)同步機(jī)制
synchronized關(guān)鍵字:Java語言的關(guān)鍵字,可用來給對象和方法或者代碼塊加鎖,當(dāng)它鎖定一個方法或者一個代碼塊的時候,同一時刻最多只有一個線程執(zhí)行這段代碼。
當(dāng)兩個并發(fā)線程訪問同一個對象object中的這個加鎖同步代碼塊時,一個時間內(nèi)只能有一個線程得到執(zhí)行。另一個線程必須等待當(dāng)前線程執(zhí)行完這個代碼塊以后才能執(zhí)行該代碼塊。
它包括兩種用法:synchronized 方法和 synchronized 塊。
即實(shí)現(xiàn)線程間同步的方式有兩種:
①使用synchronized同步代碼塊;
②使用synchronized關(guān)鍵字創(chuàng)建synchronized()方法
下面分別進(jìn)行解析,對上面售票的代碼進(jìn)行改造:
①代碼——使用synchronized同步代碼塊
class TicketThread implements Runnable { private int ticket = 5; public void run(){ for (int i = 0; i < 5; i++){ synchronized(this){ if (ticket > 0){ try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "賣票:ticket = " + ticket--); } } } } }
②代碼——使用synchronized關(guān)鍵字創(chuàng)建synchronized()方法
class TicketThreadMethod implements Runnable { private int ticket = 5; public void run(){ for (int i = 0; i < 5; i++){ this.sale(); } } public synchronized void sale(){ if (ticket > 0){ try { Thread.sleep(300); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(Thread.currentThread().getName() + "賣票:ticket = " + ticket--); } } }
三、synchronized方法和synchronized同步代碼塊的區(qū)別:
synchronized同步代碼塊只是鎖定了該代碼塊,代碼塊外面的代碼還是可以被訪問的。
synchronized方法是粗粒度的并發(fā)控制,某一個時刻只能有一個線程執(zhí)行該synchronized方法。
synchronized同步代碼塊是細(xì)粒度的并發(fā)控制,只會將塊中的代碼同步,代碼塊之外的代碼可以被其他線程同時訪問。
補(bǔ)充:多線程同步鎖synchronized(對象鎖與全局鎖)總結(jié)
1.synchronized同步鎖的引入
/* * 非線程安全 * */ //多個線程共同訪問一個對象中的實(shí)例變量,則會出現(xiàn)"非線程安全"問題 class MyRunnable1 implements Runnable{ private int num = 10; public void run() { try { if(num > 0) { System.out.println(""+Thread.currentThread().getName()+"開始"+",num= "+num--); Thread.sleep(1000); System.out.println(""+Thread.currentThread().getName()+"結(jié)束"); } } catch (InterruptedException e) { e.printStackTrace(); } } }public class Test5_5{ public static void main(String[] args) { MyRunnable1 myRunnable1 = new MyRunnable1(); Thread thread1 = new Thread(myRunnable1,"線程1"); Thread thread2 = new Thread(myRunnable1,"線程2"); thread1.start(); thread2.start(); } }
上例說明兩個線程同時訪問一個沒有同步的方法,如果兩個線程同時操作業(yè)務(wù)對象中的實(shí)例變量,則會出現(xiàn)“線程不安全”問題。
由此我們引入synchronized關(guān)鍵字來實(shí)現(xiàn)同步問題:
在Java中使用synchronized關(guān)鍵字控制線程同步,控制synchronized代碼段不被多個線程同時執(zhí)行,synchronized即可以使用在方法上也可以使用在代碼塊中。
2. 對象鎖
(1)synchronized方法(對當(dāng)前對象進(jìn)行加鎖)
若我們對如上代碼進(jìn)行修改,在run()方法上加入synchronized關(guān)鍵字使其變?yōu)橥椒椒ā?/p>
/* * 同步方法 * */ class MyRunnable1 implements Runnable{ private int num = 10; public void run() { this.print(); } public synchronized void print() { if(this.num > 0) { System.out.println(""+Thread.currentThread().getName()+"開始"+",num= "+num--); try { Thread.sleep(1000); } catch (InterruptedException e) { e.printStackTrace(); } System.out.println(""+Thread.currentThread().getName()+"結(jié)束"); } } }public class Test5_5{ public static void main(String[] args) { MyRunnable1 myRunnable1 = new MyRunnable1(); Thread thread1 = new Thread(myRunnable1,"線程1"); Thread thread2 = new Thread(myRunnable1,"線程2"); thread1.start(); thread2.start(); } }
結(jié)論:若兩個線程同時訪問同一個對象中的同步方法時一定是線程安全的。
(2)synchronized代碼塊(對某一個對象進(jìn)行加鎖)
如果要使用同步代碼塊必須設(shè)置一個要鎖定的對象,所以一般可以鎖定當(dāng)前對象:this.
/* * 同步代碼塊 * */ class MyRunnable1 implements Runnable{ private int num = 10; public void run() { try { synchronized (this) { if(num > 0) { System.out.println(""+Thread.currentThread().getName()+"開始"+",num= "+num--); Thread.sleep(1000); System.out.println(""+Thread.currentThread().getName()+"結(jié)束"); } } } catch (InterruptedException e) { e.printStackTrace(); } } } public class Test5_5{ public static void main(String[] args) { MyRunnable1 myRunnable1 = new MyRunnable1(); Thread thread1 = new Thread(myRunnable1,"線程1"); Thread thread2 = new Thread(myRunnable1,"線程2"); thread1.start(); thread2.start(); } }
(3)synchronized鎖多對象
/* * synchronized鎖多對象 * */ class Sync{ public synchronized void print() { System.out.println("print方法開始:"+Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("print方法結(jié)束"+Thread.currentThread().getName()); } } class MyThread extends Thread{ public void run() { Sync sync = new Sync(); sync.print(); } } public class Test5_5{ public static void main(String[] args) { for(int i = 0; i < 3;i++) { Thread thread = new MyThread(); thread.start(); } } }
根據(jù)上例我們可以發(fā)現(xiàn)當(dāng)synchronized鎖多個對象時不能實(shí)現(xiàn)同步操作,由此可以得出關(guān)鍵字synchronized取得的鎖都是對象鎖,而不是將一段代碼或者方法(函數(shù))當(dāng)作鎖。哪個線程先執(zhí)行帶synchronized關(guān)鍵字的方法或synchronized代碼塊,哪個線程就有該方法或該代碼塊所持有的鎖,其他線程只能呈現(xiàn)等待狀態(tài),前提是多個線程訪問同一個對象。
只有共享資源的讀寫需要同步化,如果不是共享資源,那么就不需要同步化操作。
3.全局鎖
實(shí)現(xiàn)全局鎖有兩種方式:
(1) 將synchronized關(guān)鍵字用在static方法上
synchronized加到static靜態(tài)方法上是對Class類上鎖,而synchronized加到非static方法上是給對對象上鎖。Class鎖可以對類的所有對象實(shí)例起作用。
/* * synchronized用在static方法上 * */ class Sync{ static public synchronized void print() { System.out.println("print方法開始:"+Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("print方法結(jié)束"+Thread.currentThread().getName()); } } class MyThread extends Thread{ public void run() { Sync.print(); } } public class Test5_5{ public static void main(String[] args) { for(int i = 0; i < 3;i++) { Thread thread = new MyThread(); thread.start(); } } }
(2) 用synchronized對類的Class對象進(jìn)行上鎖
synchronized(class)代碼塊的作用與synchronized static方法的作用一樣。
/* * synchronized對類的Class對象上鎖 * */ class Sync{ public void print() { synchronized (Sync.class) { System.out.println("print方法開始:"+Thread.currentThread().getName()); try { Thread.sleep(1000); } catch (InterruptedException e) { // TODO Auto-generated catch block e.printStackTrace(); } System.out.println("print方法結(jié)束"+Thread.currentThread().getName()); } } } class MyThread extends Thread{ public void run() { Sync sync = new Sync(); sync.print(); } } public class Test5_5{ public static void main(String[] args) { for(int i = 0; i < 3;i++) { Thread thread = new MyThread(); thread.start(); } } }
以上為個人經(jīng)驗(yàn),希望能給大家一個參考,也希望大家多多支持腳本之家。如有錯誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Springboot項(xiàng)目啟動時如何用命令動態(tài)指定環(huán)境
這篇文章主要介紹了Springboot項(xiàng)目啟動時如何用命令動態(tài)指定環(huán)境的操作,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教2021-06-06java 中的static關(guān)鍵字和final關(guān)鍵字的不同之處
java 中的static關(guān)鍵字和final關(guān)鍵字的不同之處,需要的朋友可以參考一下2013-03-03Spring Security如何使用URL地址進(jìn)行權(quán)限控制
這篇文章主要介紹了Spring Security如何使用URL地址進(jìn)行權(quán)限控制,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價值,需要的朋友可以參考下2019-12-12