欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

詳解Java并發(fā)編程之內(nèi)置鎖(synchronized)

 更新時(shí)間:2021年03月04日 08:45:50   投稿:mrr  
這篇文章主要介紹了Java并發(fā)編程之內(nèi)置鎖(synchronized)的相關(guān)知識(shí),本文給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下

簡介

synchronized在JDK5.0的早期版本中是重量級(jí)鎖,效率很低,但從JDK6.0開始,JDK在關(guān)鍵字synchronized上做了大量的優(yōu)化,如偏向鎖、輕量級(jí)鎖等,使它的效率有了很大的提升。

synchronized的作用是實(shí)現(xiàn)線程間的同步,當(dāng)多個(gè)線程都需要訪問共享代碼區(qū)域時(shí),對(duì)共享代碼區(qū)域進(jìn)行加鎖,使得每一次只能有一個(gè)線程訪問共享代碼區(qū)域,從而保證線程間的安全性。

因?yàn)闆]有顯式的加鎖和解鎖過程,所以稱之為隱式鎖,也叫作內(nèi)置鎖、監(jiān)視器鎖。

如下實(shí)例,在沒有使用synchronized的情況下,多個(gè)線程訪問共享代碼區(qū)域時(shí),可能會(huì)出現(xiàn)與預(yù)想中不同的結(jié)果。

public class Apple implements Runnable {
 private int appleCount = 5;

 @Override
 public void run() {
  eatApple();
 }

 public void eatApple(){
  appleCount--;
  System.out.println(Thread.currentThread().getName() + "吃了一個(gè)蘋果,還剩" + appleCount + "個(gè)蘋果");
 }

 public static void main(String[] args) {
  Apple apple = new Apple();
  Thread t1 = new Thread(apple, "小強(qiáng)");
  Thread t2 = new Thread(apple, "小明");
  Thread t3 = new Thread(apple, "小花");
  Thread t4 = new Thread(apple, "小紅");
  Thread t5 = new Thread(apple, "小黑");
  t1.start();
  t2.start();
  t3.start();
  t4.start();
  t5.start();
 }
}

可能會(huì)輸出如下結(jié)果:

小強(qiáng)吃了一個(gè)蘋果,還剩3個(gè)蘋果
小黑吃了一個(gè)蘋果,還剩3個(gè)蘋果
小明吃了一個(gè)蘋果,還剩2個(gè)蘋果
小花吃了一個(gè)蘋果,還剩1個(gè)蘋果
小紅吃了一個(gè)蘋果,還剩0個(gè)蘋果

輸出結(jié)果異常的原因是eatApple方法里操作不是原子的,如當(dāng)A線程完成appleCount的賦值,還沒有輸出,B線程獲取到appleCount的最新值,并完成賦值操作,然后A和B同時(shí)輸出。(A,B線程分別對(duì)應(yīng)小黑、小強(qiáng))

如果改下eatApple方法如下,還會(huì)不會(huì)有線程安全問題呢?

public void eatApple(){
	System.out.println(Thread.currentThread().getName() + "吃了一個(gè)蘋果,還剩" + --appleCount + "個(gè)蘋果");
}

還是會(huì)有的,因?yàn)?-appleCount不是原子操作,--appleCount可以用另外一種寫法表示:appleCount = appleCount - 1,還是有可能會(huì)出現(xiàn)以上的異常輸出結(jié)果。

synchronized的使用

synchronized分為同步方法和同步代碼塊兩種用法,當(dāng)每個(gè)線程訪問同步方法或同步代碼塊區(qū)域時(shí),首先需要獲得對(duì)象的鎖,搶到鎖的線程可以繼續(xù)執(zhí)行,搶不到鎖的線程則阻塞,等待搶到鎖的線程執(zhí)行完成后釋放鎖。

1.同步代碼塊

鎖的對(duì)象是object:

public class Apple implements Runnable {
 private int appleCount = 5;
 private Object object = new Object();

 @Override
 public void run() {
  eatApple();
 }

 public void eatApple(){
	//同步代碼塊,此時(shí)鎖的對(duì)象是object
  synchronized (object) {
   appleCount--;
   System.out.println(Thread.currentThread().getName() + "吃了一個(gè)蘋果,還剩" + appleCount + "個(gè)蘋果");
  }
 }

  //...省略main方法
}

2.同步方法,修飾普通方法

鎖的對(duì)象是當(dāng)前類的實(shí)例對(duì)象:

public class Apple implements Runnable {
 private int appleCount = 5;

 @Override
 public void run() {
  eatApple();
 }

 public synchronized void eatApple() {
  appleCount--;
  System.out.println(Thread.currentThread().getName() + "吃了一個(gè)蘋果,還剩" + appleCount + "個(gè)蘋果");
 }

 //...省略main方法
}

等價(jià)于以下同步代碼塊的寫法:

public void eatApple() {
	synchronized (this) {
		appleCount--;
		System.out.println(Thread.currentThread().getName() + "吃了一個(gè)蘋果,還剩" + appleCount + "個(gè)蘋果");
	}
}

3.同步方法,修飾靜態(tài)方法

鎖的對(duì)象是當(dāng)前類的class對(duì)象:

public class Apple implements Runnable {
 private static int appleCount = 5;

 @Override
 public void run() {
  eatApple();
 }

 public synchronized static void eatApple() {
  appleCount--;
  System.out.println(Thread.currentThread().getName() + "吃了一個(gè)蘋果,還剩" + appleCount + "個(gè)蘋果");
 }

 //...省略main方法
}

等價(jià)于以下同步代碼塊的寫法:

public static void eatApple() {
	synchronized (Apple.class) {
		appleCount--;
		System.out.println(Thread.currentThread().getName() + "吃了一個(gè)蘋果,還剩" + appleCount + "個(gè)蘋果");
	}
}

4.同步方法和同步代碼塊的區(qū)別

a.同步方法鎖的對(duì)象是當(dāng)前類的實(shí)例對(duì)象或者當(dāng)前類的class對(duì)象,而同步代碼塊鎖的對(duì)象可以是任意對(duì)象。

b.同步方法是使用synchronized修飾方法,而同步代碼塊是使用synchronized修飾共享代碼區(qū)域。同步代碼塊相對(duì)于同步方法來說粒度更細(xì),鎖的區(qū)域更小,一般鎖范圍越小效率就越高。如下情況顯然同步代碼塊更適用:

public static void eatApple() {
	//不需要同步的耗時(shí)操作1
	//...
	synchronized (Apple.class) {
		appleCount--;
		System.out.println(Thread.currentThread().getName() + "吃了一個(gè)蘋果,還剩" + appleCount + "個(gè)蘋果");
	}
	//不需要同步的耗時(shí)操作2
	//...
}

內(nèi)置鎖的可重入性

內(nèi)置鎖的可重入性是指當(dāng)某個(gè)線程試圖獲取一個(gè)它已經(jīng)持有的鎖時(shí),它總是可以獲取成功。如下:

public static void eatApple() {
	synchronized (Apple.class) {
		synchronized (Apple.class) {
			synchronized (Apple.class) {
				appleCount--;
				System.out.println(Thread.currentThread().getName() + "吃了一個(gè)蘋果,還剩" + appleCount + "個(gè)蘋果");
			}
		}
	}
}

如果鎖不是可重入的,那么假如某線程持有了該鎖,然后又需要等待持有該鎖的線程釋放鎖,這不就造成死鎖了嗎?

synchronized可以被繼承嗎?

synchronized不可以被繼承,如果子類中重寫后的方法需要實(shí)現(xiàn)同步,則需要手動(dòng)添加synchronized關(guān)鍵字。

public class AppleParent {
 public synchronized void eatApple(){

 }
}

public class Apple extends AppleParent implements Runnable {
 private int appleCount = 5;

 @Override
 public void run() {
  eatApple();
 }

 @Override
 public void eatApple() {
  appleCount--;
  System.out.println(Thread.currentThread().getName() + "吃了一個(gè)蘋果,還剩" + appleCount + "個(gè)蘋果");
 }

 //...省略main方法
}

基于內(nèi)置鎖的等待和喚醒

基于內(nèi)置鎖的等待和喚醒是使用Object類中的wait()和notify()或notifyAll()來實(shí)現(xiàn)的。這些方法的調(diào)用前提是已經(jīng)持有對(duì)應(yīng)的鎖,所以只能在同步方法或者同步代碼塊里調(diào)用。如果在沒有獲取到對(duì)應(yīng)鎖的情況下調(diào)用則會(huì)拋出IllegalMonitorStateException異常。下面介紹下相關(guān)的幾個(gè)方法:

wait():使當(dāng)前線程無限期地等待,直到另一個(gè)線程調(diào)用notify()或notifyAll()。

wait(long timeout):指定一個(gè)超時(shí)時(shí)間,超時(shí)時(shí)間過后線程將會(huì)被自動(dòng)喚醒。線程也可以在超時(shí)時(shí)間之前被notify()或notifyAll()喚醒。注意,wait(0)等同于調(diào)用wait()。

wait(long timeout, int nanos):類似于wait(long timeout),主要區(qū)別是wait(long timeout, int nanos)提供了更高的精度。

notify():隨機(jī)喚醒一個(gè)在相同鎖對(duì)象上等待的線程。

notifyAll():喚醒所有在相同鎖對(duì)象上等待的線程。

一個(gè)簡單的等待喚醒實(shí)例:

public class Apple {
 //蘋果數(shù)量
 private int appleCount = 0;

 /**
  * 買蘋果
  */
 public synchronized void getApple() {
  try {
   while (appleCount != 0) {
    wait();
   }
  } catch (InterruptedException ex) {
   ex.printStackTrace();
  }

  System.out.println(Thread.currentThread().getName() + "買了5個(gè)蘋果");
  appleCount = 5;
  notify();
 }

 /**
  * 吃蘋果
  */
 public synchronized void eatApple() {
  try {
   while (appleCount == 0) {
    wait();
   }
  } catch (InterruptedException ex) {
   ex.printStackTrace();
  }

  System.out.println(Thread.currentThread().getName() + "吃了1個(gè)蘋果");
  appleCount--;
  notify();
 }
}
/**
 * 生產(chǎn)者,買蘋果
 */
public class Producer extends Thread{
 private Apple apple;

 public Producer(Apple apple, String name){
  super(name);
  this.apple = apple;
 }

 @Override
 public void run(){
  while (true)
  apple.getApple();
 }
}

/**
 * 消費(fèi)者,吃蘋果
 */
public class Consumer extends Thread{
 private Apple apple;

 public Consumer(Apple apple, String name){
  super(name);
  this.apple = apple;
 }

 @Override
 public void run(){
  while (true)
  apple.eatApple();
 }
}
public class Demo {
 public static void main(String[] args) {
  Apple apple = new Apple();
  Producer producer = new Producer(apple,"小明");
  Consumer consumer = new Consumer(apple, "小紅");
  producer.start();
  consumer.start();
 }
}

輸出結(jié)果:

小明買了5個(gè)蘋果
小紅吃了1個(gè)蘋果
小紅吃了1個(gè)蘋果
小紅吃了1個(gè)蘋果
小紅吃了1個(gè)蘋果
小紅吃了1個(gè)蘋果
小明買了5個(gè)蘋果
小紅吃了1個(gè)蘋果
    ......

到此這篇關(guān)于Java并發(fā)編程之內(nèi)置鎖(synchronized)的文章就介紹到這了,更多相關(guān)Java內(nèi)置鎖內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • 如何解決Could not transfer artifact org.springframework.boot問題

    如何解決Could not transfer artifact org.spri

    在Maven更新過程中遇到“Could not transfer artifact org.springframework.boot”錯(cuò)誤通常是由于網(wǎng)絡(luò)問題,解決方法是在Maven的設(shè)置中忽略HTTPS,添加特定語句后,可以正常下載依賴,但下載速度可能較慢,這是一種常見的解決方案,希望對(duì)遇到相同問題的人有所幫助
    2024-09-09
  • Java利用redis zset實(shí)現(xiàn)延時(shí)任務(wù)詳解

    Java利用redis zset實(shí)現(xiàn)延時(shí)任務(wù)詳解

    zset作為redis的有序集合數(shù)據(jù)結(jié)構(gòu)存在,排序的依據(jù)就是score。本文就將利用zset score這個(gè)排序的這個(gè)特性,來實(shí)現(xiàn)延時(shí)任務(wù),感興趣的可以了解一下
    2022-08-08
  • SpringBoot @PropertySource與@ImportResource有什么區(qū)別

    SpringBoot @PropertySource與@ImportResource有什么區(qū)別

    這篇文章主要介紹了SpringBoot @PropertySource與@ImportResource有什么區(qū)別,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)吧
    2023-01-01
  • mybatis 解決將數(shù)值0識(shí)別成空字符串的問題

    mybatis 解決將數(shù)值0識(shí)別成空字符串的問題

    這篇文章主要介紹了mybatis 解決將數(shù)值0識(shí)別成空字符串的問題,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-06-06
  • Java內(nèi)存模型final的內(nèi)存語義

    Java內(nèi)存模型final的內(nèi)存語義

    這篇文章主要介紹了Java內(nèi)存模型final的內(nèi)存語義,上篇介紹volatile的內(nèi)存語義,本文講述的是final的內(nèi)存語義,相比之下,final域的讀和寫更像是普通變量的訪問。下面我們一起來看看文章學(xué)校內(nèi)容吧,需要的朋友可以參考一下
    2021-11-11
  • Java基礎(chǔ)之不簡單的數(shù)組

    Java基礎(chǔ)之不簡單的數(shù)組

    數(shù)組(Array)是有序的元素序列。 若將有限個(gè)類型相同的變量的集合命名,那么這個(gè)名稱為數(shù)組名。組成數(shù)組的各個(gè)變量稱為數(shù)組的分量,也稱為數(shù)組的元素,有時(shí)也稱為下標(biāo)變量
    2021-09-09
  • Java?Chassis3應(yīng)用視角的配置管理技術(shù)解密

    Java?Chassis3應(yīng)用視角的配置管理技術(shù)解密

    這篇文章主要為大家介紹了Java?Chassis3應(yīng)用視角的配置管理相關(guān)的機(jī)制和背后故事,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2024-01-01
  • SpringBoot利用切面注解及反射實(shí)現(xiàn)事件監(jiān)聽功能

    SpringBoot利用切面注解及反射實(shí)現(xiàn)事件監(jiān)聽功能

    這篇文章主要介紹了springboot事件監(jiān)聽,通過利用切面、注解、反射實(shí)現(xiàn),接下來將對(duì)這幾種方式逐一說明,具有很好的參考價(jià)值,希望對(duì)大家有所幫助
    2022-07-07
  • 9種Java單例模式詳解(推薦)

    9種Java單例模式詳解(推薦)

    這篇文章主要介紹了9種Java單例模式詳解,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-03-03
  • 詳解@ConditionalOnMissingBean注解的作用

    詳解@ConditionalOnMissingBean注解的作用

    這篇文章主要介紹了詳解@ConditionalOnMissingBean注解的作用,@ConditionalOnMissingBean,它是修飾bean的一個(gè)注解,主要實(shí)現(xiàn)的是,當(dāng)你的bean被注冊(cè)之后,如果而注冊(cè)相同類型的bean,就不會(huì)成功,它會(huì)保證你的bean只有一個(gè),需要的朋友可以參考下
    2023-10-10

最新評(píng)論