利用synchronized實(shí)現(xiàn)線程同步的案例講解
一、前期基礎(chǔ)知識(shí)儲(chǔ)備
(1)線程同步的定義:多線程之間的同步。
(2)多線程同步原因:一個(gè)多線程的程序如果是通過(guò)Runnable接口實(shí)現(xiàn)的,則意味著類中的屬性將被多個(gè)線程共享,由此引出資源的同步問(wèn)題,即當(dāng)多個(gè)線程要操作同一資源時(shí),有可能出現(xiàn)錯(cuò)誤。
(3)實(shí)現(xiàn)多線程同步的方式——引入同步機(jī)制:在線程使用一個(gè)資源時(shí)為其加鎖,這樣其他的線程便不能訪問(wèn)那個(gè)資源了,直到解鎖后才可以訪問(wèn)?!@樣做的結(jié)果,所有線程間會(huì)有資源競(jìng)爭(zhēng),但是所有競(jìng)爭(zhēng)的資源是同步的,刷新的,動(dòng)態(tài)的,不會(huì)因?yàn)榫€程間的競(jìng)爭(zhēng),導(dǎo)致資源“過(guò)度消耗”或者“虛擬消耗”。
上代碼,具體展示“過(guò)度消耗/虛擬消耗”問(wèn)題:
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 //過(guò)度消耗 Thread-1賣票:ticket = 1 Thread-2賣票:ticket = 0
如上所見,票一共5張,三個(gè)線程調(diào)用買票,線程1網(wǎng)上賣了售票第1張,緊接著線程2線下也賣了“第一張”出現(xiàn)了“虛擬消耗”的問(wèn)題;線程3黃牛黨賣了最后1張票,線程1網(wǎng)上又賣了最后1張,出現(xiàn)了“過(guò)度消耗”的問(wèn)題,這兩種問(wèn)題都是實(shí)際生活中不可能發(fā)生的,但是在這個(gè)3個(gè)線程執(zhí)行中卻出現(xiàn)了,那一定是有問(wèn)題的,問(wèn)題的根源就在于,三個(gè)渠道的“售票員”不在一個(gè)頻道上辦事,或者說(shuō)沒有相互之間同步所共享的資源,導(dǎo)致這一問(wèn)題的根本原因,就是相互之間實(shí)現(xiàn)方式不同步。
二、使用synchronized實(shí)現(xiàn)同步機(jī)制
synchronized關(guān)鍵字:Java語(yǔ)言的關(guān)鍵字,可用來(lái)給對(duì)象和方法或者代碼塊加鎖,當(dāng)它鎖定一個(gè)方法或者一個(gè)代碼塊的時(shí)候,同一時(shí)刻最多只有一個(gè)線程執(zhí)行這段代碼。
當(dāng)兩個(gè)并發(fā)線程訪問(wèn)同一個(gè)對(duì)象object中的這個(gè)加鎖同步代碼塊時(shí),一個(gè)時(shí)間內(nèi)只能有一個(gè)線程得到執(zhí)行。另一個(gè)線程必須等待當(dāng)前線程執(zhí)行完這個(gè)代碼塊以后才能執(zhí)行該代碼塊。
它包括兩種用法:synchronized 方法和 synchronized 塊。
即實(shí)現(xiàn)線程間同步的方式有兩種:
①使用synchronized同步代碼塊;
②使用synchronized關(guān)鍵字創(chuàng)建synchronized()方法
下面分別進(jìn)行解析,對(duì)上面售票的代碼進(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同步代碼塊只是鎖定了該代碼塊,代碼塊外面的代碼還是可以被訪問(wèn)的。
synchronized方法是粗粒度的并發(fā)控制,某一個(gè)時(shí)刻只能有一個(gè)線程執(zhí)行該synchronized方法。
synchronized同步代碼塊是細(xì)粒度的并發(fā)控制,只會(huì)將塊中的代碼同步,代碼塊之外的代碼可以被其他線程同時(shí)訪問(wèn)。
補(bǔ)充:多線程同步鎖synchronized(對(duì)象鎖與全局鎖)總結(jié)
1.synchronized同步鎖的引入
/*
* 非線程安全
* */
//多個(gè)線程共同訪問(wèn)一個(gè)對(duì)象中的實(shí)例變量,則會(huì)出現(xiàn)"非線程安全"問(wè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();
}
}

上例說(shuō)明兩個(gè)線程同時(shí)訪問(wèn)一個(gè)沒有同步的方法,如果兩個(gè)線程同時(shí)操作業(yè)務(wù)對(duì)象中的實(shí)例變量,則會(huì)出現(xiàn)“線程不安全”問(wèn)題。
由此我們引入synchronized關(guān)鍵字來(lái)實(shí)現(xiàn)同步問(wèn)題:
在Java中使用synchronized關(guān)鍵字控制線程同步,控制synchronized代碼段不被多個(gè)線程同時(shí)執(zhí)行,synchronized即可以使用在方法上也可以使用在代碼塊中。
2. 對(duì)象鎖
(1)synchronized方法(對(duì)當(dāng)前對(duì)象進(jìn)行加鎖)
若我們對(duì)如上代碼進(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é)論:若兩個(gè)線程同時(shí)訪問(wèn)同一個(gè)對(duì)象中的同步方法時(shí)一定是線程安全的。
(2)synchronized代碼塊(對(duì)某一個(gè)對(duì)象進(jìn)行加鎖)
如果要使用同步代碼塊必須設(shè)置一個(gè)要鎖定的對(duì)象,所以一般可以鎖定當(dāng)前對(duì)象: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鎖多對(duì)象
/*
* synchronized鎖多對(duì)象
* */
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鎖多個(gè)對(duì)象時(shí)不能實(shí)現(xiàn)同步操作,由此可以得出關(guān)鍵字synchronized取得的鎖都是對(duì)象鎖,而不是將一段代碼或者方法(函數(shù))當(dāng)作鎖。哪個(gè)線程先執(zhí)行帶synchronized關(guān)鍵字的方法或synchronized代碼塊,哪個(gè)線程就有該方法或該代碼塊所持有的鎖,其他線程只能呈現(xiàn)等待狀態(tài),前提是多個(gè)線程訪問(wèn)同一個(gè)對(duì)象。
只有共享資源的讀寫需要同步化,如果不是共享資源,那么就不需要同步化操作。
3.全局鎖
實(shí)現(xiàn)全局鎖有兩種方式:
(1) 將synchronized關(guān)鍵字用在static方法上
synchronized加到static靜態(tài)方法上是對(duì)Class類上鎖,而synchronized加到非static方法上是給對(duì)對(duì)象上鎖。Class鎖可以對(duì)類的所有對(duì)象實(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對(duì)類的Class對(duì)象進(jìn)行上鎖
synchronized(class)代碼塊的作用與synchronized static方法的作用一樣。
/*
* synchronized對(duì)類的Class對(duì)象上鎖
* */
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();
}
}
}

以上為個(gè)人經(jīng)驗(yàn),希望能給大家一個(gè)參考,也希望大家多多支持腳本之家。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教。
相關(guān)文章
Springboot項(xiàng)目啟動(dòng)時(shí)如何用命令動(dòng)態(tài)指定環(huán)境
這篇文章主要介紹了Springboot項(xiàng)目啟動(dòng)時(shí)如何用命令動(dòng)態(tài)指定環(huán)境的操作,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教2021-06-06
java 中的static關(guān)鍵字和final關(guān)鍵字的不同之處
java 中的static關(guān)鍵字和final關(guān)鍵字的不同之處,需要的朋友可以參考一下2013-03-03
Spring Security如何使用URL地址進(jìn)行權(quán)限控制
這篇文章主要介紹了Spring Security如何使用URL地址進(jìn)行權(quán)限控制,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下2019-12-12
使用Java校驗(yàn)SQL語(yǔ)句的合法性五種解決方案
這篇文章主要介紹了如何用java校驗(yàn)SQL語(yǔ)句的合法性(提供五種解決方案),使用JDBC?API和JSqlParser庫(kù)、正則表達(dá)式、ANTLR解析器生成器或Apache?Calcite庫(kù)都可以實(shí)現(xiàn)校驗(yàn)SQL語(yǔ)句的合法性,需要的朋友可以參考下2023-04-04

