首頁 Java java教程 深入理解java中的synchronized關鍵字

深入理解java中的synchronized關鍵字

Dec 13, 2016 am 11:15 AM

synchronized 關鍵字,代表這個方法加鎖,相當於不管哪一個線程A每次運行到這個方法時,都要檢查有沒有其它正在用這個方法的線程B(或者C D等),有的話要等正在使用這個方法的線程B(或C D)運行完這個方法後再運行此線程A,沒有的話,直接運行它包括兩種用法:synchronized 方法和synchronized 塊。

1. synchronized 方法:
透過在方法宣告中加入 synchronized關鍵字來宣告 synchronized 方法。如:

public synchronized void accessVal(int newVal); 
登入後複製

synchronized 方法控制對類別成員變數的存取:每個類別實例對應一把鎖,每個synchronized 方法都必須獲得呼叫該方法的類別實例的鎖定方能執行,否則所屬執行緒阻塞,方法一旦執行,就獨佔該鎖,直到從該方法返回時才將鎖釋放,此後被阻塞的線程方能獲得該鎖,重新進入可執行狀態。這種機制確保了同一時刻對於每一個類別實例,其所有宣告為synchronized 的成員函數中至多只有一個處於可執行狀態(因為至多只有一個能夠獲得該類別實例對應的鎖),從而有效避免了類別成員變數的存取衝突(只要所有可能存取類別成員變數的方法均被宣告為synchronized)。  在 Java 中,不光是類別實例,每一個類別也對應一把鎖,這樣我們也可將類別的靜態成員函數宣告為 synchronized ,以控制其對類別的靜態成員變數的存取。 synchronized 方法的缺陷:若將一個大的方法宣告為synchronized 將會大大影響效率,典型地,若將執行緒類別的方法run()宣告為synchronized ,由於在執行緒的整個生命期內它一直在運行,因此將導致它對本類任何synchronized 方法的呼叫都永遠不會成功。當然我們可以透過將存取類別成員變數的程式碼放到專門的方法中,將其宣告為synchronized ,並在主方法中呼叫來解決這一問題,但是Java 為我們提供了更好的解決辦法,那就是synchronized 塊。

2. synchronized 區塊:
透過 synchronized關鍵字來聲明synchronized 區塊。語法如下: 

synchronized(syncObject)
{  
//允许访问控制的代码  
} 
登入後複製

synchronized 區塊是這樣一個程式碼區塊,其中的程式碼必須獲得物件 syncObject (如前所述,可以是類別實例或類別)的鎖方能執行,具體機制同前所述。由於可以針對任意程式碼區塊,且可任意指定上鎖的對象,故靈活性較高。

對synchronized (this)的一些理解
一、當兩個並發線程訪問同一個物件object中的這個synchronized(this)同步程式碼區塊時,一個時間內只能有一個執行緒來執行。另一個執行緒必須等待目前執行緒執行完這個程式碼區塊以後才能執行該程式碼區塊。  

二、當一個執行緒存取object的一個synchronized(this)同步程式碼區塊時,其他執行緒對object中所有其它synchronized(this)同步程式碼區塊的存取將被阻塞。  

三、然而,當一個執行緒存取object的一個synchronized(this)同步程式碼區塊時,另一個執行緒仍然可以存取該object中的除synchronized(this)同步程式碼區塊以外的部分。 

四、第三個例子同樣適用其它同步程式碼區塊。也就是說,當一個執行緒存取object的一個synchronized(this)同步程式碼區塊時,它就獲得了這個object的物件鎖定。結果,其它執行緒對該object物件所有同步程式碼部分的存取都被暫時阻塞。  

五、以上規則對其它物件鎖同樣適用。

synchronized的一個簡單例子

public class TextThread
{
/**
* @param args
*/
public static void main(String[] args)
{
// TODO 自动生成方法存根
        TxtThread tt = new TxtThread();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
        new Thread(tt).start();
}
}
class TxtThread implements Runnable
{
int num = 100;
String str = new String();
public void run()
{
while (true)
{
   synchronized(str)
   {
   if (num>0)
   {
    try
    {
     Thread.sleep(10);
    }
    catch(Exception e)
    {
     e.getMessage();
    }
    System.out.println(Thread.currentThread().getName()+ "this is "+ num--);
   }
   }
}
}
}
登入後複製

上面的例子中為了製造一個時間差,也就是出錯的機會,使用了Thread.sleep(10)Java對多線程的支持與同步機制深受大家的喜愛,似乎看起來使用了synchronized關鍵字就可以輕鬆解決多執行緒共享資料同步問題。到底如何? ――還得對synchronized關鍵字的作用進行深入了解才可定論。

總的說來,synchronized關鍵字可以作為函數的修飾符,也可作為函數內的語句,也就是平常說的同步方法和同步語句塊。如果再細的分類,synchronized可作用於instance變數、object reference(物件參考)、static函數和class literals(類別名稱字面常數)身上。在進一步闡述之前,我們需要先明確幾點:

A.無論synchronized關鍵字加在方法上或對像上,它所取得的鎖都是對象,而不是把一段程式碼或函式當作鎖――而且同步方法很可能還會被其他執行緒的對象存取。

B.每個物件只有一個鎖(lock)與之相關聯。

C.實現同步是要很大的系統開銷作為代價的,甚至可能造成死鎖,所以盡量避免無謂的同步控制。

接著來討論synchronized用到不同地方對代碼產生的影響: 假設P1、P2是同一類的不同對象,這個類中定義了以下幾種情況的​​同步塊或同步方法,P1、P2就都可以調用它們。

1.把synchronized當作函數修飾符時,範例程式碼如下:

Public synchronized void methodAAA()
{
//…
}
登入後複製

这也就是同步方法,那这时synchronized锁定的是哪个对象呢?它锁定的是调用这个同步方法对象。也就是说,当一个对象P1在不同的线程中执行这个同步方法时,它们之间会形成互斥,达到同步的效果。但是这个对象所属的Class所产生的另一对象P2却可以任意调用这个被加了synchronized关键字的方法。上边的示例代码等同于如下代码:

public void methodAAA()
{
synchronized (this)      // (1)
{
       //…..
}
}
登入後複製

(1)处的this指的是什么呢?它指的就是调用这个方法的对象,如P1。可见同步方法实质是将synchronized作用于object reference。――那个拿到了P1对象锁的线程,才可以调用P1的同步方法,而对P2而言,P1这个锁与它毫不相干,程序也可能在这种情形下摆脱同步机制的控制,造成数据混乱。


2.同步块,示例代码如下:

public void method3(SomeObject so)
{
    synchronized(so)
    {
       //…..
    }
}
登入後複製

这时,锁就是so这个对象,谁拿到这个锁谁就可以运行它所控制的那段代码。当有一个明确的对象作为锁时,就可以这样写程序,但当没有明确的对象作为锁,只是想让一段代码同步时,可以创建一个特殊的instance变量(它得是一个对象)来充当锁:

class Foo implements Runnable
{
        private byte[] lock = new byte[0]; // 特殊的instance变量
        Public void methodA()
        {
           synchronized(lock) { //… }
        }
        //…..
}
登入後複製

注:零长度的byte数组对象创建起来将比任何对象都经济――查看编译后的字节码:生成零长度的byte[]对象只需3条操作码,而Object lock = new Object()则需要7行操作码。

3.将synchronized作用于static 函数,示例代码如下:

Class Foo
{
    public synchronized static void methodAAA()   // 同步的static 函数
    {
        //….
    }
    public void methodBBB()
    {
       synchronized(Foo.class)   // class literal(类名称字面常量)
    }
}
登入後複製

代码中的methodBBB()方法是把class literal作为锁的情况,它和同步的static函数产生的效果是一样的,取得的锁很特别,是当前调用这个方法的对象所属的类(Class,而不再是由这个Class产生的某个具体对象了)。

记得在《Effective Java》一书中看到过将 Foo.class和 P1.getClass()用于作同步锁还不一样,不能用P1.getClass()来达到锁这个Class的目的。P1指的是由Foo类产生的对象。可以推断:如果一个类中定义了一个synchronized的static函数A,也定义了一个synchronized 的instance函数B,那么这个类的同一对象Obj在多线程中分别访问A和B两个方法时,不会构成同步,因为它们的锁都不一样。A方法的锁是Obj这个对象,而B的锁是Obj所属的那个Class。

小结如下:

搞清楚synchronized锁定的是哪个对象,就能帮助我们设计更安全的多线程程序。 还有一些技巧可以让我们对共享资源的同步访问更加安全:1.定义private 的instance变量+它的 get方法,而不要定义public/protected的instance变量。如果将变量定义为public,对象在外界可以绕过同步方法的控制而直接取得它,并改动它。这也是JavaBean的标准实现方式之一。2.如果instance变量是一个对象,如数组或ArrayList什么的,那上述方法仍然不安全,因为当外界对象通过get方法拿到这个instance对象的引用后,又将其指向另一个对象,那么这个private变量也就变了,岂不是很危险。这个时候就需要将get方法也加上synchronized同步,并且,只返回这个private对象的clone()――这样,调用端得到的就是对象副本的引用了  还有,比较常用的就有:Collections.synchronizedMap(new HashMap()),当然这个MAP就是生命在类中的全局变量,就是一个线程安全的HashMap,web的application是全web容器公用的,所以要使用线程安全来保证数据的正确。


本網站聲明
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn

熱AI工具

Undresser.AI Undress

Undresser.AI Undress

人工智慧驅動的應用程序,用於創建逼真的裸體照片

AI Clothes Remover

AI Clothes Remover

用於從照片中去除衣服的線上人工智慧工具。

Undress AI Tool

Undress AI Tool

免費脫衣圖片

Clothoff.io

Clothoff.io

AI脫衣器

Video Face Swap

Video Face Swap

使用我們完全免費的人工智慧換臉工具,輕鬆在任何影片中換臉!

熱門文章

<🎜>:泡泡膠模擬器無窮大 - 如何獲取和使用皇家鑰匙
3 週前 By 尊渡假赌尊渡假赌尊渡假赌
北端:融合系統,解釋
4 週前 By 尊渡假赌尊渡假赌尊渡假赌
Mandragora:巫婆樹的耳語 - 如何解鎖抓鉤
3 週前 By 尊渡假赌尊渡假赌尊渡假赌

熱工具

記事本++7.3.1

記事本++7.3.1

好用且免費的程式碼編輯器

SublimeText3漢化版

SublimeText3漢化版

中文版,非常好用

禪工作室 13.0.1

禪工作室 13.0.1

強大的PHP整合開發環境

Dreamweaver CS6

Dreamweaver CS6

視覺化網頁開發工具

SublimeText3 Mac版

SublimeText3 Mac版

神級程式碼編輯軟體(SublimeText3)

熱門話題

Java教學
1670
14
CakePHP 教程
1428
52
Laravel 教程
1329
25
PHP教程
1274
29
C# 教程
1256
24
公司安全軟件導致應用無法運行?如何排查和解決? 公司安全軟件導致應用無法運行?如何排查和解決? Apr 19, 2025 pm 04:51 PM

公司安全軟件導致部分應用無法正常運行的排查與解決方法許多公司為了保障內部網絡安全,會部署安全軟件。 ...

如何將姓名轉換為數字以實現排序並保持群組中的一致性? 如何將姓名轉換為數字以實現排序並保持群組中的一致性? Apr 19, 2025 pm 11:30 PM

將姓名轉換為數字以實現排序的解決方案在許多應用場景中,用戶可能需要在群組中進行排序,尤其是在一個用...

如何優雅地獲取實體類變量名構建數據庫查詢條件? 如何優雅地獲取實體類變量名構建數據庫查詢條件? Apr 19, 2025 pm 11:42 PM

在使用MyBatis-Plus或其他ORM框架進行數據庫操作時,經常需要根據實體類的屬性名構造查詢條件。如果每次都手動...

如何使用MapStruct簡化系統對接中的字段映射問題? 如何使用MapStruct簡化系統對接中的字段映射問題? Apr 19, 2025 pm 06:21 PM

系統對接中的字段映射處理在進行系統對接時,常常會遇到一個棘手的問題:如何將A系統的接口字段有效地映�...

IntelliJ IDEA是如何在不輸出日誌的情況下識別Spring Boot項目的端口號的? IntelliJ IDEA是如何在不輸出日誌的情況下識別Spring Boot項目的端口號的? Apr 19, 2025 pm 11:45 PM

在使用IntelliJIDEAUltimate版本啟動Spring...

Java對像如何安全地轉換為數組? Java對像如何安全地轉換為數組? Apr 19, 2025 pm 11:33 PM

Java對象與數組的轉換:深入探討強制類型轉換的風險與正確方法很多Java初學者會遇到將一個對象轉換成數組的�...

電商平台SKU和SPU數據庫設計:如何兼顧用戶自定義屬性和無屬性商品? 電商平台SKU和SPU數據庫設計:如何兼顧用戶自定義屬性和無屬性商品? Apr 19, 2025 pm 11:27 PM

電商平台SKU和SPU表設計詳解本文將探討電商平台中SKU和SPU的數據庫設計問題,特別是如何處理用戶自定義銷售屬...

如何利用Redis緩存方案高效實現產品排行榜列表的需求? 如何利用Redis緩存方案高效實現產品排行榜列表的需求? Apr 19, 2025 pm 11:36 PM

Redis緩存方案如何實現產品排行榜列表的需求?在開發過程中,我們常常需要處理排行榜的需求,例如展示一個�...

See all articles